Merge "Change the hashtree hash algorithm to sha256"
diff --git a/apexd/Android.bp b/apexd/Android.bp
index a6afb4a..fb60191 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -219,11 +219,6 @@
   export_header_lib_headers: [
     "libutils_headers",
   ],
-  product_variables: {
-    debuggable: {
-      cflags: ["-DDEBUG_ALLOW_BUNDLED_KEY"],
-    },
-  },
 }
 
 genrule {
@@ -310,6 +305,7 @@
   ],
   shared_libs: [
     "libbinder",
+    "libfs_mgr",
     "libutils",
   ],
   test_suites: ["device-tests"],
diff --git a/apexd/AndroidTest.xml b/apexd/AndroidTest.xml
index aea1d24..6f65f4e 100644
--- a/apexd/AndroidTest.xml
+++ b/apexd/AndroidTest.xml
@@ -28,6 +28,24 @@
          on higher levels (e.g., PackageInstaller). -->
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
 
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+      <option name="cleanup" value="true" />
+      <option name="remount-system" value="true" />
+      <option name="push" value="apex.apexd_test.apex->/system_ext/apex/apex.apexd_test.apex" />
+      <option name="push" value="apex.apexd_test_different_app.apex->/system_ext/apex/apex.apexd_test_different_app.apex" />
+      <option name="push" value="apex.apexd_test_postinstall.apex->/system_ext/apex/apex.apexd_test_postinstall.apex" />
+      <option name="push" value="apex.apexd_test_preinstall.apex->/system_ext/apex/apex.apexd_test_preinstall.apex" />
+    </target_preparer>
+
+    <!-- system_server might still hold a reference to apexservice. This means that apexd is still
+         running, and test apexes pushed in the PushFilePreparer above are not yet scanned.
+         One way to solve this is to reboot a device, but that would significantly increase
+         execution of this test module. Instead, force a GC in system_server by sending kill -10. -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+      <option name="run-command" value="shell kill -10 $(pidof system_server)" />
+      <option name="teardown-command" value="shell kill -10 $(pidof system_server)" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <!-- Note: despite how these lines read, the test will run nicely separated out
                    of a subfolder. -->
diff --git a/apexd/OWNERS b/apexd/OWNERS
deleted file mode 100644
index ed9813e..0000000
--- a/apexd/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-agampe@google.com
diff --git a/apexd/aidl/android/apex/IApexService.aidl b/apexd/aidl/android/apex/IApexService.aidl
index 57784a8..c4405b8 100644
--- a/apexd/aidl/android/apex/IApexService.aidl
+++ b/apexd/aidl/android/apex/IApexService.aidl
@@ -95,4 +95,24 @@
     * functional on user builds.
     */
    void resumeRevertIfNeeded();
+   /**
+    * Forces apexd to remount all active packages.
+    *
+    * This call is mostly useful for speeding up development of APEXes.
+    * Instead of going through a full APEX installation that requires a reboot,
+    * developers can incorporate this method in much faster `adb sync` based
+    * workflow:
+    *
+    * 1. adb shell stop
+    * 2. adb sync
+    * 3. adb shell cmd -w apexservice remountPackages
+    * 4. adb shell start
+    *
+    * Note, that for an APEX package will be successfully remounted only if
+    * there are no alive processes holding a reference to it.
+    *
+    * Not meant for use outside of testing. This call will not be functional
+    * on user builds. Only root is allowed to call this method.
+    */
+   void remountPackages();
 }
diff --git a/apexd/apex_database.cpp b/apexd/apex_database.cpp
index f8549a1..d027f95 100644
--- a/apexd/apex_database.cpp
+++ b/apexd/apex_database.cpp
@@ -34,6 +34,7 @@
 #include <unordered_map>
 #include <utility>
 
+using android::base::ConsumeSuffix;
 using android::base::ParseInt;
 using android::base::ReadFileToString;
 using android::base::Result;
@@ -168,6 +169,25 @@
   return {};
 }
 
+// This is not the right place to do this normalization, but proper solution
+// will require some refactoring first. :(
+// TODO(ioffe): introduce MountedApexDataBuilder and delegate all
+//  building/normalization logic to it.
+void NormalizeIfDeleted(MountedApexData* apex_data) {
+  std::string_view full_path = apex_data->full_path;
+  if (ConsumeSuffix(&full_path, "(deleted)")) {
+    apex_data->deleted = true;
+    auto it = full_path.rbegin();
+    while (it != full_path.rend() && isspace(*it)) {
+      it++;
+    }
+    full_path.remove_suffix(it - full_path.rbegin());
+  } else {
+    apex_data->deleted = false;
+  }
+  apex_data->full_path = full_path;
+}
+
 Result<MountedApexData> resolveMountInfo(const BlockDevice& block,
                                          const std::string& mountPoint) {
   // Now, see if it is dm-verity or loop mounted
@@ -177,9 +197,11 @@
       if (!backingFile.ok()) {
         return backingFile.error();
       }
-      return MountedApexData(block.DevPath(), *backingFile, mountPoint,
-                             /* device_name= */ "",
-                             /* hashtree_loop_name= */ "");
+      auto result = MountedApexData(block.DevPath(), *backingFile, mountPoint,
+                                    /* device_name= */ "",
+                                    /* hashtree_loop_name= */ "");
+      NormalizeIfDeleted(&result);
+      return result;
     }
     case DeviceMapperDevice: {
       auto name = block.GetProperty("dm/name");
@@ -192,6 +214,7 @@
       if (auto status = PopulateLoopInfo(block, &result); !status.ok()) {
         return status.error();
       }
+      NormalizeIfDeleted(&result);
       return result;
     }
     case UnknownDevice: {
@@ -254,7 +277,9 @@
       activeVersions[package] = version;
       SetLatest(package, mountData->full_path);
     }
-    LOG(INFO) << "Found " << mountPoint;
+    LOG(INFO) << "Found " << mountPoint << " backed by"
+              << (mountData->deleted ? " deleted " : " ") << "file "
+              << mountData->full_path;
   }
 
   LOG(INFO) << mounted_apexes_.size() << " packages restored.";
diff --git a/apexd/apex_database.h b/apexd/apex_database.h
index 5b738b7..1fb5bd3 100644
--- a/apexd/apex_database.h
+++ b/apexd/apex_database.h
@@ -38,6 +38,8 @@
     // Name of the loop device backing up hashtree or empty string in case
     // hashtree is embedded inside an APEX.
     std::string hashtree_loop_name;
+    // Whenever apex file specified in full_path was deleted.
+    bool deleted;
 
     MountedApexData() {}
     MountedApexData(const std::string& loop_name, const std::string& full_path,
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index ba1647e..9664221 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -51,11 +51,6 @@
 
 constexpr const char* kImageFilename = "apex_payload.img";
 constexpr const char* kBundledPublicKeyFilename = "apex_pubkey";
-#ifdef DEBUG_ALLOW_BUNDLED_KEY
-constexpr const bool kDebugAllowBundledKey = true;
-#else
-constexpr const bool kDebugAllowBundledKey = false;
-#endif
 
 }  // namespace
 
@@ -257,15 +252,6 @@
       return Error() << "Error verifying " << apex.GetPath() << ": "
                      << "public key doesn't match the pre-installed one";
     }
-  } else if (kDebugAllowBundledKey) {
-    // Failing to find the matching public key in the built-in partitions
-    // is a hard error for non-debuggable build. For debuggable builds,
-    // the public key bundled in the APEX itself is used as a fallback.
-    LOG(WARNING) << "Verifying " << apex.GetPath() << " with the bundled key";
-    if (!CompareKeys(pk, pk_len, apex.GetBundledPublicKey())) {
-      return Error() << "Error verifying " << apex.GetPath() << ": "
-                     << "public key doesn't match the one bundled in the APEX";
-    }
   } else {
     return public_key.error();
   }
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index 8b82b41..cd4b175 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -80,6 +80,7 @@
 }
 
 TEST(ApexFileTest, VerifyApexVerity) {
+  ASSERT_RESULT_OK(collectPreinstalledData({"/system_ext/apex"}));
   const std::string filePath = testDataDir + "apex.apexd_test.apex";
   Result<ApexFile> apexFile = ApexFile::Open(filePath);
   ASSERT_RESULT_OK(apexFile);
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 0b81e42..bf673ce 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -63,6 +63,7 @@
 #include <algorithm>
 #include <array>
 #include <chrono>
+#include <cstdlib>
 #include <filesystem>
 #include <fstream>
 #include <iomanip>
@@ -1607,7 +1608,7 @@
 
   // Make sure that kActiveApexPackagesDataDir exists.
   auto create_dir_status =
-      createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0750);
+      createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0755);
   if (!create_dir_status.ok()) {
     return create_dir_status.error();
   }
@@ -1687,6 +1688,9 @@
   // temporary folder and restore state from it in case unstagePackages fails.
 
   for (const std::string& path : paths) {
+    if (isPathForBuiltinApexes(path)) {
+      return Error() << "Can't uninstall pre-installed apex " << path;
+    }
     if (access(path.c_str(), F_OK) != 0) {
       return ErrnoError() << "Can't access " << path;
     }
@@ -1820,63 +1824,10 @@
 }
 
 Result<void> remountApexFile(const std::string& path) {
-  auto ret = deactivatePackage(path);
-  if (!ret.ok()) return ret.error();
-
-  ret = activatePackage(path);
-  if (!ret.ok()) return ret.error();
-
-  return {};
-}
-
-Result<void> monitorBuiltinDirs() {
-  int fd = inotify_init1(IN_CLOEXEC);
-  if (fd == -1) {
-    return ErrnoErrorf("inotify_init failed");
+  if (auto ret = deactivatePackage(path); !ret.ok()) {
+    return ret;
   }
-  std::map<int, std::string> desc_to_dir;
-  for (const auto& dir : kApexPackageBuiltinDirs) {
-    int desc = inotify_add_watch(fd, dir.c_str(), IN_CREATE | IN_MODIFY);
-    if (desc == -1 && errno != ENOENT) {
-      // don't complain about missing directories like /product/apex
-      return ErrnoErrorf("failed to add watch on {}", dir);
-    }
-    desc_to_dir.emplace(desc, dir);
-  }
-  static std::thread th([fd, desc_to_dir]() -> void {
-    constexpr int num_events = 100;
-    constexpr size_t average_path_length = 50;
-    char buffer[num_events *
-                (sizeof(struct inotify_event) + average_path_length)];
-    while (true) {
-      ssize_t length = read(fd, buffer, sizeof(buffer));
-      if (length < 0) {
-        PLOG(ERROR) << "failed to read inotify event: " << strerror(errno);
-        continue;
-      }
-      int i = 0;
-      while (i < length) {
-        struct inotify_event* e = (struct inotify_event*)&buffer[i];
-        if (e->len > 0 && (e->mask & (IN_CREATE | IN_MODIFY)) != 0) {
-          if (desc_to_dir.find(e->wd) == desc_to_dir.end()) {
-            LOG(ERROR) << "unexpected watch descriptor " << e->wd
-                       << " for name: " << e->name;
-          } else {
-            std::string path = desc_to_dir.at(e->wd) + "/" + e->name;
-            auto ret = remountApexFile(path);
-            if (!ret.ok()) {
-              LOG(ERROR) << ret.error().message();
-            } else {
-              LOG(INFO) << path << " remounted because it was changed";
-            }
-          }
-        }
-        i += sizeof(struct inotify_event) + e->len;
-      }
-    }
-  });
-
-  return {};
+  return activatePackage(path);
 }
 
 void initialize(CheckpointInterface* checkpoint_service) {
@@ -1993,13 +1944,6 @@
 
   // Now that APEXes are mounted, snapshot or restore DE_sys data.
   snapshotOrRestoreDeSysData();
-
-  if (android::base::GetBoolProperty("ro.debuggable", false)) {
-    status = monitorBuiltinDirs();
-    if (!status.ok()) {
-      LOG(ERROR) << "cannot monitor built-in dirs: " << status.error();
-    }
-  }
 }
 
 void onAllPackagesActivated() {
@@ -2226,5 +2170,36 @@
   return status != kApexStatusReady && status != kApexStatusActivated;
 }
 
+Result<void> remountPackages() {
+  std::vector<std::string> apexes;
+  gMountedApexes.ForallMountedApexes([&apexes](const std::string& /*package*/,
+                                               const MountedApexData& data,
+                                               bool latest) {
+    if (latest) {
+      LOG(DEBUG) << "Found active APEX " << data.full_path;
+      apexes.push_back(data.full_path);
+    }
+  });
+  std::vector<std::string> failed;
+  for (const std::string& apex : apexes) {
+    // Since this is only used during development workflow, we are trying to
+    // remount as many apexes as possible instead of failing fast.
+    if (auto ret = remountApexFile(apex); !ret) {
+      LOG(WARNING) << "Failed to remount " << apex << " : " << ret.error();
+      failed.emplace_back(apex);
+    }
+  }
+  static constexpr const char* kErrorMessage =
+      "Failed to remount following APEX packages, hence previous versions of "
+      "them are still active. If APEX you are developing is in this list, it "
+      "means that there still are alive processes holding a reference to the "
+      "previous version of your APEX.\n";
+  if (!failed.empty()) {
+    return Error() << kErrorMessage << "Failed (" << failed.size() << ") "
+                   << "APEX packages: [" << Join(failed, ',') << "]";
+  }
+  return {};
+}
+
 }  // namespace apex
 }  // namespace android
diff --git a/apexd/apexd.h b/apexd/apexd.h
index e757ae6..cde6989 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -114,6 +114,10 @@
 
 int unmountAll();
 
+// Optimistically tries to remount as many APEX packages as possible.
+// For more documentation see corresponding binder call in IApexService.aidl.
+android::base::Result<void> remountPackages();
+
 }  // namespace apex
 }  // namespace android
 
diff --git a/apexd/apexd.rc b/apexd/apexd.rc
index 9d77ef3..e306025 100644
--- a/apexd/apexd.rc
+++ b/apexd/apexd.rc
@@ -1,8 +1,9 @@
 service apexd /system/bin/apexd
+    interface aidl apexservice
     class core
     user root
     group system
-    shutdown critical
+    oneshot
     disabled # does not start with the core class
 
 service apexd-bootstrap /system/bin/apexd --bootstrap
diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp
index 24a2b26..2a4dfd5 100644
--- a/apexd/apexd_main.cpp
+++ b/apexd/apexd_main.cpp
@@ -126,6 +126,8 @@
         android::apex::bootCompletedCleanup);
   }
 
+  android::apex::binder::AllowServiceShutdown();
+
   android::apex::binder::JoinThreadPool();
   return 1;
 }
diff --git a/apexd/apexd_verity_test.cpp b/apexd/apexd_verity_test.cpp
index ce86dab..cf2317c 100644
--- a/apexd/apexd_verity_test.cpp
+++ b/apexd/apexd_verity_test.cpp
@@ -25,6 +25,7 @@
 #include <gtest/gtest.h>
 
 #include "apex_file.h"
+#include "apex_preinstalled_data.h"
 #include "apexd_test_utils.h"
 #include "apexd_verity.h"
 
@@ -44,6 +45,7 @@
 }
 
 TEST(ApexdVerityTest, ReusesHashtree) {
+  ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
   TemporaryDir td;
 
   auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
@@ -76,6 +78,7 @@
 }
 
 TEST(ApexdVerityTest, RegenerateHashree) {
+  ASSERT_TRUE(IsOk(collectPreinstalledData({"/system_ext/apex"})));
   TemporaryDir td;
 
   auto apex = ApexFile::Open(GetTestFile("apex.apexd_test_no_hashtree.apex"));
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index 1dfff0a..0a8ef6a 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -29,8 +29,10 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IResultReceiver.h>
 #include <binder/IServiceManager.h>
+#include <binder/LazyServiceRegistrar.h>
 #include <binder/ProcessState.h>
 #include <binder/Status.h>
+#include <private/android_filesystem_config.h>
 #include <utils/String16.h>
 
 #include "apex_file.h"
@@ -50,6 +52,16 @@
 
 using BinderStatus = ::android::binder::Status;
 
+BinderStatus CheckCallerIsRoot(const std::string& name) {
+  uid_t uid = IPCThreadState::self()->getCallingUid();
+  if (uid != AID_ROOT) {
+    std::string msg = "Only root is allowed to call " + name;
+    return BinderStatus::fromExceptionCode(BinderStatus::EX_SECURITY,
+                                           String8(name.c_str()));
+  }
+  return BinderStatus::ok();
+}
+
 class ApexService : public BnApexService {
  public:
   using BinderStatus = ::android::binder::Status;
@@ -87,6 +99,7 @@
   BinderStatus destroyDeSnapshots(int rollback_id) override;
   BinderStatus destroyCeSnapshotsNotSpecified(
       int user_id, const std::vector<int>& retain_rollback_ids) override;
+  BinderStatus remountPackages() override;
 
   status_t dump(int fd, const Vector<String16>& args) override;
 
@@ -524,6 +537,22 @@
   return BinderStatus::ok();
 }
 
+BinderStatus ApexService::remountPackages() {
+  LOG(DEBUG) << "remountPackages() received by ApexService";
+  if (auto debug = CheckDebuggable("remountPackages"); !debug.isOk()) {
+    return debug;
+  }
+  if (auto root = CheckCallerIsRoot("remountPackages"); !root.isOk()) {
+    return root;
+  }
+  if (auto res = ::android::apex::remountPackages(); !res.ok()) {
+    return BinderStatus::fromExceptionCode(
+        BinderStatus::EX_SERVICE_SPECIFIC,
+        String8(res.error().message().c_str()));
+  }
+  return BinderStatus::ok();
+}
+
 status_t ApexService::onTransact(uint32_t _aidl_code, const Parcel& _aidl_data,
                                  Parcel* _aidl_reply, uint32_t _aidl_flags) {
   switch (_aidl_code) {
@@ -631,8 +660,20 @@
         << std::endl
         << "  getStagedSessionInfo [sessionId] - displays information about a "
            "given session previously submitted"
+        << std::endl
         << "  submitStagedSession [sessionId] - attempts to submit the "
            "installer session with given id"
+        << std::endl
+        << "  remountPackages - Force apexd to remount active packages. This "
+           "call can be used to speed up development workflow of an APEX "
+           "package. Example of usage:\n"
+           "    1. adb shell stop\n"
+           "    2. adb sync\n"
+           "    3. adb shell cmd -w apexservice remountPackages\n"
+           "    4. adb shell start\n"
+           "\n"
+           "Note: APEX package will be successfully remounted only if there "
+           "are no alive processes holding a reference to it"
         << std::endl;
     dprintf(fd, "%s", log.operator std::string().c_str());
   };
@@ -851,6 +892,17 @@
     return BAD_VALUE;
   }
 
+  if (cmd == String16("remountPackages")) {
+    BinderStatus status = remountPackages();
+    if (status.isOk()) {
+      return OK;
+    }
+    std::string msg = StringLog() << "remountPackages failed: "
+                                  << status.toString8().string() << std::endl;
+    dprintf(err, "%s", msg.c_str());
+    return BAD_VALUE;
+  }
+
   if (cmd == String16("help")) {
     if (args.size() != 1) {
       print_help(err, "Help has no options");
@@ -868,18 +920,24 @@
 
 static constexpr const char* kApexServiceName = "apexservice";
 
-using android::defaultServiceManager;
 using android::IPCThreadState;
 using android::ProcessState;
 using android::sp;
-using android::String16;
+using android::binder::LazyServiceRegistrar;
 
 void CreateAndRegisterService() {
   sp<ProcessState> ps(ProcessState::self());
 
-  // Create binder service and register with servicemanager
+  // Create binder service and register with LazyServiceRegistrar
   sp<ApexService> apexService = new ApexService();
-  defaultServiceManager()->addService(String16(kApexServiceName), apexService);
+  auto lazyRegistrar = LazyServiceRegistrar::getInstance();
+  lazyRegistrar.forcePersist(true);
+  lazyRegistrar.registerService(apexService, kApexServiceName);
+}
+
+void AllowServiceShutdown() {
+  auto lazyRegistrar = LazyServiceRegistrar::getInstance();
+  lazyRegistrar.forcePersist(false);
 }
 
 void StartThreadPool() {
diff --git a/apexd/apexservice.h b/apexd/apexservice.h
index b246a10..73c0f1f 100644
--- a/apexd/apexservice.h
+++ b/apexd/apexservice.h
@@ -22,6 +22,7 @@
 namespace binder {
 
 void CreateAndRegisterService();
+void AllowServiceShutdown();
 void StartThreadPool();
 void JoinThreadPool();
 
diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp
index b435626..9751397 100644
--- a/apexd/apexservice_test.cpp
+++ b/apexd/apexservice_test.cpp
@@ -40,6 +40,7 @@
 #include <android-base/strings.h>
 #include <android/os/IVold.h>
 #include <binder/IServiceManager.h>
+#include <fs_mgr_overlayfs.h>
 #include <fstab/fstab.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -99,7 +100,7 @@
     using android::IServiceManager;
 
     sp<IServiceManager> sm = android::defaultServiceManager();
-    sp<IBinder> binder = sm->getService(String16("apexservice"));
+    sp<IBinder> binder = sm->waitForService(String16("apexservice"));
     if (binder != nullptr) {
       service_ = android::interface_cast<IApexService>(binder);
     }
@@ -136,12 +137,29 @@
 
   static bool IsSelinuxEnforced() { return 0 != security_getenforce(); }
 
-  Result<bool> IsActive(const std::string& name, int64_t version) {
+  Result<bool> IsActive(const std::string& name) {
+    std::vector<ApexInfo> list;
+    android::binder::Status status = service_->getActivePackages(&list);
+    if (!status.isOk()) {
+      return Error() << "Failed to check if " << name
+                     << " is active : " << status.exceptionMessage().c_str();
+    }
+    for (const ApexInfo& apex : list) {
+      if (apex.moduleName == name) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Result<bool> IsActive(const std::string& name, int64_t version,
+                        const std::string& path) {
     std::vector<ApexInfo> list;
     android::binder::Status status = service_->getActivePackages(&list);
     if (status.isOk()) {
       for (const ApexInfo& p : list) {
-        if (p.moduleName == name && p.versionCode == version) {
+        if (p.moduleName == name && p.versionCode == version &&
+            p.modulePath == path) {
           return true;
         }
       }
@@ -550,6 +568,16 @@
   return slaves;
 }
 
+Result<void> CopyFile(const std::string& from, const std::string& to,
+                      const fs::copy_options& options) {
+  std::error_code ec;
+  if (!fs::copy_file(from, to, options)) {
+    return Error() << "Failed to copy file " << from << " to " << to << " : "
+                   << ec.message();
+  }
+  return {};
+}
+
 }  // namespace
 
 TEST_F(ApexServiceTest, HaveSelinux) {
@@ -957,7 +985,10 @@
 
     {
       // Check package is not active.
-      Result<bool> active = IsActive(installer_->package, installer_->version);
+      std::string path = stage_package ? installer_->test_installed_file
+                                       : installer_->test_file;
+      Result<bool> active =
+          IsActive(installer_->package, installer_->version, path);
       ASSERT_TRUE(IsOk(active));
       ASSERT_FALSE(*active);
     }
@@ -1034,7 +1065,8 @@
 
   {
     // Check package is active.
-    Result<bool> active = IsActive(installer_->package, installer_->version);
+    Result<bool> active = IsActive(installer_->package, installer_->version,
+                                   installer_->test_installed_file);
     ASSERT_TRUE(IsOk(active));
     ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ',');
   }
@@ -1164,7 +1196,8 @@
       << GetDebugStr(installer_.get());
   {
     // Check package is active.
-    Result<bool> active = IsActive(installer_->package, installer_->version);
+    Result<bool> active = IsActive(installer_->package, installer_->version,
+                                   installer_->test_installed_file);
     ASSERT_TRUE(IsOk(active));
     ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ',');
   }
@@ -1192,7 +1225,8 @@
       << GetDebugStr(installer_.get());
   {
     // Check package is active.
-    Result<bool> active = IsActive(installer_->package, installer_->version);
+    Result<bool> active = IsActive(installer_->package, installer_->version,
+                                   installer_->test_installed_file);
     ASSERT_TRUE(IsOk(active));
     ASSERT_TRUE(*active) << Join(GetActivePackagesStrings(), ',');
   }
@@ -1594,7 +1628,8 @@
 
     // Ensure that the package is neither active nor mounted.
     for (const InstallerUPtr& installer : installers) {
-      Result<bool> active = IsActive(installer->package, installer->version);
+      Result<bool> active = IsActive(installer->package, installer->version,
+                                     installer->test_file);
       ASSERT_TRUE(IsOk(active));
       EXPECT_FALSE(*active);
     }
@@ -2063,7 +2098,7 @@
 
   // Make sure that /data/apex/active exists and is empty
   ASSERT_TRUE(
-      IsOk(createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0750)));
+      IsOk(createDirIfNeeded(std::string(kActiveApexPackagesDataDir), 0755)));
   auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir);
   ASSERT_TRUE(IsOk(active_pkgs));
   ASSERT_EQ(0u, active_pkgs->size());
@@ -2147,6 +2182,18 @@
               UnorderedElementsAre(installer1.test_installed_file));
 }
 
+TEST_F(ApexServiceTest, UnstagePackagesFailPreInstalledApex) {
+  auto status = service_->unstagePackages(
+      {"/system/apex/com.android.apex.cts.shim.apex"});
+  ASSERT_FALSE(IsOk(status));
+  const std::string& error_message =
+      std::string(status.exceptionMessage().c_str());
+  ASSERT_THAT(error_message,
+              HasSubstr("Can't uninstall pre-installed apex "
+                        "/system/apex/com.android.apex.cts.shim.apex"));
+  ASSERT_TRUE(RegularFileExists("/system/apex/com.android.apex.cts.shim.apex"));
+}
+
 class ApexServiceRevertTest : public ApexServiceTest {
  protected:
   void SetUp() override { ApexServiceTest::SetUp(); }
@@ -2171,7 +2218,7 @@
     // First check that /data/apex/active exists and has correct permissions.
     struct stat sd;
     ASSERT_EQ(0, stat(kActiveApexPackagesDataDir, &sd));
-    ASSERT_EQ(0750u, sd.st_mode & ALLPERMS);
+    ASSERT_EQ(0755u, sd.st_mode & ALLPERMS);
 
     // Now read content and check it contains expected values.
     auto active_pkgs = ReadEntireDir(kActiveApexPackagesDataDir);
@@ -2668,6 +2715,80 @@
   ASSERT_FALSE(IsOk(service_->submitStagedSession(params, &list)));
 }
 
+TEST_F(ApexServiceTest, RemountPackagesPackageOnSystemChanged) {
+  static constexpr const char* kSystemPath =
+      "/system_ext/apex/apex.apexd_test.apex";
+  static constexpr const char* kPackageName = "com.android.apex.test_package";
+  if (!fs_mgr_overlayfs_is_setup()) {
+    GTEST_SKIP() << "/system_ext is not overlayed into read-write";
+  }
+  if (auto res = IsActive(kPackageName); !res.ok()) {
+    FAIL() << res.error();
+  } else {
+    ASSERT_FALSE(*res) << kPackageName << " is active";
+  }
+  ASSERT_EQ(0, access(kSystemPath, F_OK))
+      << "Failed to stat " << kSystemPath << " : " << strerror(errno);
+  ASSERT_TRUE(IsOk(service_->activatePackage(kSystemPath)));
+  std::string backup_path = GetTestFile("apex.apexd_test.apexd.bak");
+  // Copy original /system_ext apex file. We will need to restore it after test
+  // runs.
+  ASSERT_RESULT_OK(CopyFile(kSystemPath, backup_path, fs::copy_options::none));
+
+  // Make sure we cleanup after ourselves.
+  auto deleter = android::base::make_scope_guard([&]() {
+    if (auto ret = service_->deactivatePackage(kSystemPath); !ret.isOk()) {
+      LOG(ERROR) << ret.exceptionMessage();
+    }
+    auto ret = CopyFile(backup_path, kSystemPath,
+                        fs::copy_options::overwrite_existing);
+    if (!ret.ok()) {
+      LOG(ERROR) << ret.error();
+    }
+  });
+
+  // Copy v2 version to /system_ext/apex/ and then call remountPackages.
+  PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex"));
+  if (!installer.Prepare()) {
+    FAIL() << GetDebugStr(&installer);
+  }
+  ASSERT_RESULT_OK(CopyFile(installer.test_file, kSystemPath,
+                            fs::copy_options::overwrite_existing));
+  // Don't check that remountPackages succeeded. Most likely it will fail, but
+  // it should still remount our test apex.
+  service_->remountPackages();
+
+  // Check that v2 is now active.
+  auto active_apex = GetActivePackage("com.android.apex.test_package");
+  ASSERT_RESULT_OK(active_apex);
+  ASSERT_EQ(2u, active_apex->versionCode);
+  // Sanity check that module path didn't change.
+  ASSERT_EQ(kSystemPath, active_apex->modulePath);
+}
+
+TEST_F(ApexServiceActivationSuccessTest, RemountPackagesPackageOnDataChanged) {
+  ASSERT_TRUE(IsOk(service_->activatePackage(installer_->test_installed_file)))
+      << GetDebugStr(installer_.get());
+  // Copy v2 version to /data/apex/active and then call remountPackages.
+  PrepareTestApexForInstall installer2(GetTestFile("apex.apexd_test_v2.apex"));
+  if (!installer2.Prepare()) {
+    FAIL() << GetDebugStr(&installer2);
+  }
+  ASSERT_RESULT_OK(CopyFile(installer2.test_file,
+                            installer_->test_installed_file,
+                            fs::copy_options::overwrite_existing));
+  // Don't check that remountPackages succeeded. Most likely it will fail, but
+  // it should still remount our test apex.
+  service_->remountPackages();
+
+  // Check that v2 is now active.
+  auto active_apex = GetActivePackage("com.android.apex.test_package");
+  ASSERT_RESULT_OK(active_apex);
+  ASSERT_EQ(2u, active_apex->versionCode);
+  // Sanity check that module path didn't change.
+  ASSERT_EQ(installer_->test_installed_file, active_apex->modulePath);
+}
+
 class LogTestToLogcat : public ::testing::EmptyTestEventListener {
   void OnTestStart(const ::testing::TestInfo& test_info) override {
 #ifdef __ANDROID__
diff --git a/tests/Android.bp b/tests/Android.bp
index 3cb6d7c..95475c7 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -210,20 +210,6 @@
 }
 
 java_test_host {
-    name: "apex_remount_tests",
-    srcs:  ["src/**/ApexRemountTest.java"],
-    libs: ["tradefed"],
-    static_libs: [
-        "module_test_util",
-        "truth-prebuilt",
-        "apex_manifest_proto_java",
-    ],
-    test_config: "apex-remount-tests.xml",
-
-    data: [":com.android.apex.cts.shim.v2_prebuilt"],
-}
-
-java_test_host {
     name: "module_test_utils_tests",
     srcs:  ["src/**/ModuleTestUtilsTest.java"],
     libs: ["tradefed", "truth-prebuilt"],
@@ -246,6 +232,7 @@
     test_suites: ["general-tests"],
     data: [
         ":apex.apexd_test_v2",
+        ":com.android.apex.cts.shim.v2_prebuilt",
         ":com.android.apex.cts.shim.v2_no_pb",
     ],
 }
diff --git a/tests/apex-remount-tests.xml b/tests/apex-remount-tests.xml
deleted file mode 100644
index 50bf95f..0000000
--- a/tests/apex-remount-tests.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Runs the apex remount test cases">
-    <option name="test-suite-tag" value="apex_remount_tests" />
-    <option name="test-suite-tag" value="apct" />
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
-    <test class="com.android.tradefed.testtype.HostTest" >
-        <option name="jar" value="apex_remount_tests.jar" />
-    </test>
-</configuration>
diff --git a/tests/src/com/android/tests/apex/ApexRemountTest.java b/tests/src/com/android/tests/apex/ApexRemountTest.java
deleted file mode 100644
index bd73182..0000000
--- a/tests/src/com/android/tests/apex/ApexRemountTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.apex;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeTrue;
-
-import com.android.apex.Protos.ApexManifest;
-import com.android.tests.util.ModuleTestUtils;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.util.JsonFormat;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for automatic remount of APEXes when they are updated via `adb sync`
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class ApexRemountTest extends BaseHostJUnit4Test {
-    private File mSavedShimFile;
-    private final ModuleTestUtils mUtils = new ModuleTestUtils(this);
-
-    private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex";
-
-    @Before
-    public void setUp() throws Exception {
-        mUtils.abandonActiveStagedSession();
-        mUtils.uninstallShimApexIfNecessary();
-        mSavedShimFile = getDevice().pullFile(SHIM_APEX_PATH);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mSavedShimFile != null) {
-            getDevice().remountSystemWritable();
-            getDevice().pushFile(mSavedShimFile, SHIM_APEX_PATH);
-            getDevice().reboot();
-            assertThat(getShimApexManifest().getVersion()).isEqualTo(1L);
-        }
-    }
-
-    @Test
-    public void testApexIsRemountedUponUpdate() throws Exception {
-        assumeTrue("APEXes on the device are flattened", hasNonFlattenedApex());
-
-        ModuleTestUtils utils = new ModuleTestUtils(this);
-        File updatedFile = utils.getTestFile("com.android.apex.cts.shim.v2.apex");
-
-        getDevice().remountSystemWritable();
-        getDevice().pushFile(updatedFile, SHIM_APEX_PATH);
-
-        // Wait some time until the update is detected by apexd and remount is done
-        TimeUnit.SECONDS.sleep(5);
-
-        assertThat(getShimApexManifest().getVersion()).isEqualTo(2L);
-    }
-
-    private ApexManifest getShimApexManifest() throws DeviceNotAvailableException,
-            InvalidProtocolBufferException {
-        String json = getDevice().executeShellCommand(
-                "cat /apex/com.android.apex.cts.shim/apex_manifest.pb");
-        ApexManifest.Builder builder = ApexManifest.newBuilder();
-        JsonFormat.parser().merge(json, builder);
-        return builder.build();
-    }
-
-    private boolean hasNonFlattenedApex() throws Exception {
-        return "true".equals(getDevice().getProperty("ro.apex.updatable"));
-    }
-}
diff --git a/tests/src/com/android/tests/apex/ApexRollbackTests.java b/tests/src/com/android/tests/apex/ApexRollbackTests.java
index dfc05fc..c64182c 100644
--- a/tests/src/com/android/tests/apex/ApexRollbackTests.java
+++ b/tests/src/com/android/tests/apex/ApexRollbackTests.java
@@ -82,7 +82,13 @@
     @Test
     public void testAutomaticBootLoopRecovery() throws Exception {
         assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
-
+        ITestDevice device = getDevice();
+        // Skip this test if there is already crashing process on device
+        boolean hasCrashingProcess =
+                device.getBooleanProperty("sys.init.updatable_crashing", false);
+        String crashingProcess = device.getProperty("sys.init.updatable_crashing_process_name");
+        assumeFalse(
+                "Device already has a crashing process: " + crashingProcess, hasCrashingProcess);
         File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
 
         // To simulate an apex update that causes a boot loop, we install a
@@ -92,7 +98,6 @@
         // persist.debug.trigger_watchdog.apex is installed. If so,
         // trigger_watchdog.sh repeatedly kills the system server causing a
         // boot loop.
-        ITestDevice device = getDevice();
         assertThat(device.setProperty("persist.debug.trigger_watchdog.apex",
                 "com.android.apex.cts.shim@2")).isTrue();
         String error = device.installPackage(apexFile, false, "--wait");
@@ -118,7 +123,7 @@
         assertThat(activatedApexes).doesNotContain(ctsShimV2);
 
         // Assert that a session has failed with the expected reason
-        String sessionInfo = device.executeShellCommand("cmd apexservice getStagedSessionInfo "
+        String sessionInfo = device.executeShellCommand("cmd -w apexservice getStagedSessionInfo "
                     + sessionIdToCheck);
         assertThat(sessionInfo).contains("revertReason: zygote");
     }
diff --git a/tests/src/com/android/tests/apex/ApexdHostTest.java b/tests/src/com/android/tests/apex/ApexdHostTest.java
index b7b6a95..2e3a832 100644
--- a/tests/src/com/android/tests/apex/ApexdHostTest.java
+++ b/tests/src/com/android/tests/apex/ApexdHostTest.java
@@ -29,6 +29,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.time.Duration;
 import java.util.Set;
 
@@ -38,10 +39,13 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class ApexdHostTest extends BaseHostJUnit4Test  {
 
+    private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex";
+
     private final ModuleTestUtils mTestUtils = new ModuleTestUtils(this);
 
     @Test
     public void testOrphanedApexIsNotActivated() throws Exception {
+        assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
         assumeTrue("Device requires root", getDevice().isAdbRoot());
         try {
             assertThat(getDevice().pushFile(mTestUtils.getTestFile("apex.apexd_test_v2.apex"),
@@ -61,8 +65,9 @@
     }
     @Test
     public void testApexWithoutPbIsNotActivated() throws Exception {
-        final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex";
+        assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
         assumeTrue("Device requires root", getDevice().isAdbRoot());
+        final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex";
         try {
             assertThat(getDevice().pushFile(mTestUtils.getTestFile(testApexFile),
                     "/data/apex/active/" + testApexFile)).isTrue();
@@ -79,4 +84,34 @@
             getDevice().executeShellV2Command("rm /data/apex/active/" + testApexFile);
         }
     }
+
+    @Test
+    public void testRemountApex() throws Exception {
+        assumeTrue("Device does not support updating APEX", mTestUtils.isApexUpdateSupported());
+        assumeTrue("Device requires root", getDevice().isAdbRoot());
+        final File oldFile = getDevice().pullFile(SHIM_APEX_PATH);
+        try {
+            getDevice().remountSystemWritable();
+            // In case remount requires a reboot, wait for boot to complete.
+            getDevice().waitForBootComplete(Duration.ofMinutes(3).toMillis());
+            final File newFile = mTestUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+            // Stop framework
+            getDevice().executeShellV2Command("stop");
+            // Push new shim APEX. This simulates adb sync.
+            getDevice().pushFile(newFile, SHIM_APEX_PATH);
+            // Ask apexd to remount packages
+            getDevice().executeShellV2Command("cmd -w apexservice remountPackages");
+            // Start framework
+            getDevice().executeShellV2Command("start");
+            // Give enough time for system_server to boot.
+            Thread.sleep(Duration.ofSeconds(15).toMillis());
+            final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
+            ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
+                    "com.android.apex.cts.shim", 2L);
+            assertThat(activeApexes).contains(testApex);
+        } finally {
+            getDevice().pushFile(oldFile, SHIM_APEX_PATH);
+            getDevice().reboot();
+        }
+    }
 }
diff --git a/tests/util/com/android/tests/util/ModuleTestUtils.java b/tests/util/com/android/tests/util/ModuleTestUtils.java
index 8f62bc3..dcf2efd 100644
--- a/tests/util/com/android/tests/util/ModuleTestUtils.java
+++ b/tests/util/com/android/tests/util/ModuleTestUtils.java
@@ -196,14 +196,16 @@
             return;
         }
 
+        final ITestDevice.ApexInfo shim = getShimApex();
+        if (shim.sourceDir.startsWith("/system")) {
+            CLog.i("Skipping uninstall of " + shim.sourceDir + ". Reason: pre-installed version");
+            return;
+        }
+        CLog.i("Uninstalling shim apex");
         final String errorMessage = mTest.getDevice().uninstallPackage(SHIM);
         if (errorMessage == null) {
-            CLog.i("Uninstalling shim apex");
             mTest.getDevice().reboot();
         } else {
-            // Most likely we tried to uninstall system version and failed. It should be fine to
-            // continue tests.
-            // TODO(b/140813980): use ApexInfo.sourceDir to decide whenever to issue an uninstall.
             CLog.w("Failed to uninstall shim APEX: " + errorMessage);
         }
         assertThat(getShimApex().versionCode).isEqualTo(1L);