Merge "SystemApi for dex module registration"
diff --git a/Android.mk b/Android.mk
index 6e75bea..8bb366a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -157,6 +157,7 @@
core/java/android/content/ISyncServiceAdapter.aidl \
core/java/android/content/ISyncStatusObserver.aidl \
core/java/android/content/om/IOverlayManager.aidl \
+ core/java/android/content/pm/IDexModuleRegisterCallback.aidl \
core/java/android/content/pm/ILauncherApps.aidl \
core/java/android/content/pm/IOnAppsChangedListener.aidl \
core/java/android/content/pm/IOnPermissionsChangeListener.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index fcd0c2a..a095a32 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11340,6 +11340,7 @@
method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
method public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract deprecated void removePackageFromPreferred(java.lang.String);
method public abstract void removePermission(java.lang.String);
@@ -11560,6 +11561,11 @@
field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
}
+ public static abstract class PackageManager.DexModuleRegisterCallback {
+ ctor public PackageManager.DexModuleRegisterCallback();
+ method public abstract void onDexModuleRegistered(java.lang.String, boolean, java.lang.String);
+ }
+
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
ctor public PackageManager.NameNotFoundException();
ctor public PackageManager.NameNotFoundException(java.lang.String);
@@ -44527,6 +44533,7 @@
method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public void removePackageFromPreferred(java.lang.String);
method public void removePermission(java.lang.String);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index e50c307..f6aea96 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -78,6 +78,10 @@
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
import android.util.ArrayMap;
import android.util.IconDrawableFactory;
import android.util.LauncherIcons;
@@ -2662,4 +2666,78 @@
throw e.rethrowAsRuntimeException();
}
}
+
+ private static class DexModuleRegisterResult {
+ final String dexModulePath;
+ final boolean success;
+ final String message;
+
+ private DexModuleRegisterResult(String dexModulePath, boolean success, String message) {
+ this.dexModulePath = dexModulePath;
+ this.success = success;
+ this.message = message;
+ }
+ }
+
+ private static class DexModuleRegisterCallbackDelegate
+ extends android.content.pm.IDexModuleRegisterCallback.Stub
+ implements Handler.Callback {
+ private static final int MSG_DEX_MODULE_REGISTERED = 1;
+ private final DexModuleRegisterCallback callback;
+ private final Handler mHandler;
+
+ DexModuleRegisterCallbackDelegate(@NonNull DexModuleRegisterCallback callback) {
+ this.callback = callback;
+ mHandler = new Handler(Looper.getMainLooper(), this);
+ }
+
+ @Override
+ public void onDexModuleRegistered(@NonNull String dexModulePath, boolean success,
+ @Nullable String message)throws RemoteException {
+ mHandler.obtainMessage(MSG_DEX_MODULE_REGISTERED,
+ new DexModuleRegisterResult(dexModulePath, success, message)).sendToTarget();
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what != MSG_DEX_MODULE_REGISTERED) {
+ return false;
+ }
+ DexModuleRegisterResult result = (DexModuleRegisterResult)msg.obj;
+ callback.onDexModuleRegistered(result.dexModulePath, result.success, result.message);
+ return true;
+ }
+ }
+
+ @Override
+ public void registerDexModule(@NonNull String dexModule,
+ @Nullable DexModuleRegisterCallback callback) {
+ // Check if this is a shared module by looking if the others can read it.
+ boolean isSharedModule = false;
+ try {
+ StructStat stat = Os.stat(dexModule);
+ if ((OsConstants.S_IROTH & stat.st_mode) != 0) {
+ isSharedModule = true;
+ }
+ } catch (ErrnoException e) {
+ callback.onDexModuleRegistered(dexModule, false,
+ "Could not get stat the module file: " + e.getMessage());
+ return;
+ }
+
+ // Module path is ok.
+ // Create the callback delegate to be passed to package manager service.
+ DexModuleRegisterCallbackDelegate callbackDelegate = null;
+ if (callback != null) {
+ callbackDelegate = new DexModuleRegisterCallbackDelegate(callback);
+ }
+
+ // Invoke the package manager service.
+ try {
+ mPM.registerDexModule(mContext.getPackageName(), dexModule,
+ isSharedModule, callbackDelegate);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/content/pm/IDexModuleRegisterCallback.aidl b/core/java/android/content/pm/IDexModuleRegisterCallback.aidl
new file mode 100644
index 0000000..4af6999
--- /dev/null
+++ b/core/java/android/content/pm/IDexModuleRegisterCallback.aidl
@@ -0,0 +1,28 @@
+/*
+** Copyright 2017, 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 android.content.pm;
+
+import android.os.Bundle;
+
+/**
+ * Callback for registering a dex module with the Package Manager.
+ *
+ * @hide
+ */
+oneway interface IDexModuleRegisterCallback {
+ void onDexModuleRegistered(in String dexModulePath, in boolean success, in String message);
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c7dd1fa..77891fa 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -25,6 +25,7 @@
import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageDeleteObserver;
@@ -474,6 +475,38 @@
void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, String loaderIsa);
/**
+ * Register an application dex module with the package manager.
+ * The package manager will keep track of the given module for future optimizations.
+ *
+ * Dex module optimizations will disable the classpath checking at runtime. The client bares
+ * the responsibility to ensure that the static assumptions on classes in the optimized code
+ * hold at runtime (e.g. there's no duplicate classes in the classpath).
+ *
+ * Note that the package manager already keeps track of dex modules loaded with
+ * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}.
+ * This can be called for an eager registration.
+ *
+ * The call might take a while and the results will be posted on the main thread, using
+ * the given callback.
+ *
+ * If the module is intended to be shared with other apps, make sure that the file
+ * permissions allow for it.
+ * If at registration time the permissions allow for others to read it, the module would
+ * be marked as a shared module which might undergo a different optimization strategy.
+ * (usually shared modules will generated larger optimizations artifacts,
+ * taking more disk space).
+ *
+ * @param packageName the package name to which the dex module belongs
+ * @param dexModulePath the absolute path of the dex module.
+ * @param isSharedModule whether or not the module is intended to be used by other apps.
+ * @param callback if not null,
+ * {@link android.content.pm.IDexModuleRegisterCallback.IDexModuleRegisterCallback#onDexModuleRegistered}
+ * will be called once the registration finishes.
+ */
+ void registerDexModule(in String packageName, in String dexModulePath,
+ in boolean isSharedModule, IDexModuleRegisterCallback callback);
+
+ /**
* Ask the package manager to perform a dex-opt for the given reason. The package
* manager will map the reason to a compiler filter according to the current system
* configuration.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 10b7965..07125e0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5725,4 +5725,48 @@
* @hide
*/
public abstract String getInstantAppAndroidId(String packageName, @NonNull UserHandle user);
+
+ /**
+ * Callback use to notify the callers of module registration that the operation
+ * has finished.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static abstract class DexModuleRegisterCallback {
+ public abstract void onDexModuleRegistered(String dexModulePath, boolean success,
+ String message);
+ }
+
+ /**
+ * Register an application dex module with the package manager.
+ * The package manager will keep track of the given module for future optimizations.
+ *
+ * Dex module optimizations will disable the classpath checking at runtime. The client bares
+ * the responsibility to ensure that the static assumptions on classes in the optimized code
+ * hold at runtime (e.g. there's no duplicate classes in the classpath).
+ *
+ * Note that the package manager already keeps track of dex modules loaded with
+ * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}.
+ * This can be called for an eager registration.
+ *
+ * The call might take a while and the results will be posted on the main thread, using
+ * the given callback.
+ *
+ * If the module is intended to be shared with other apps, make sure that the file
+ * permissions allow for it.
+ * If at registration time the permissions allow for others to read it, the module would
+ * be marked as a shared module which might undergo a different optimization strategy.
+ * (usually shared modules will generated larger optimizations artifacts,
+ * taking more disk space).
+ *
+ * @param dexModulePath the absolute path of the dex module.
+ * @param callback if not null, {@link DexModuleRegisterCallback#onDexModuleRegistered} will
+ * be called once the registration finishes.
+ *
+ * @hide
+ */
+ @SystemApi
+ public abstract void registerDexModule(String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback);
}
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 33a0493..698f2ec 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -36,6 +36,7 @@
import android.content.res.Resources.NotFoundException;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
@@ -64,9 +65,14 @@
import com.android.frameworks.coretests.R;
import com.android.internal.content.PackageHelper;
+import dalvik.system.VMRuntime;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -78,9 +84,9 @@
public static final String TAG = "PackageManagerTests";
- public final long MAX_WAIT_TIME = 25 * 1000;
+ public static final long MAX_WAIT_TIME = 25 * 1000;
- public final long WAIT_TIME_INCR = 5 * 1000;
+ public static final long WAIT_TIME_INCR = 5 * 1000;
private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
@@ -3861,6 +3867,117 @@
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
+ private static class TestDexModuleRegisterCallback
+ extends PackageManager.DexModuleRegisterCallback {
+ private String mDexModulePath = null;
+ private boolean mSuccess = false;
+ private String mMessage = null;
+ CountDownLatch doneSignal = new CountDownLatch(1);
+
+ @Override
+ public void onDexModuleRegistered(String dexModulePath, boolean success, String message) {
+ mDexModulePath = dexModulePath;
+ mSuccess = success;
+ mMessage = message;
+ doneSignal.countDown();
+ }
+
+ boolean waitTillDone() {
+ long startTime = System.currentTimeMillis();
+ while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
+ try {
+ return doneSignal.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.i(TAG, "Interrupted during sleep", e);
+ }
+ }
+ return false;
+ }
+
+ }
+
+ // Verify that the base code path cannot be registered.
+ public void testRegisterDexModuleBaseCode() throws Exception {
+ PackageManager pm = getPm();
+ ApplicationInfo info = getContext().getApplicationInfo();
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ pm.registerDexModule(info.getBaseCodePath(), callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(info.getBaseCodePath(), callback.mDexModulePath);
+ assertFalse("BaseCodePath should not be registered", callback.mSuccess);
+ }
+
+ // Verify thatmodules which are not own by the calling package are not registered.
+ public void testRegisterDexModuleNotOwningModule() throws Exception {
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ String moduleBelongingToOtherPackage = "/data/user/0/com.google.android.gms/module.apk";
+ getPm().registerDexModule(moduleBelongingToOtherPackage, callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(moduleBelongingToOtherPackage, callback.mDexModulePath);
+ assertTrue(callback.waitTillDone());
+ assertFalse("Only modules belonging to the calling package can be registered",
+ callback.mSuccess);
+ }
+
+ // Verify that modules owned by the package are successfully registered.
+ public void testRegisterDexModuleSuccessfully() throws Exception {
+ ApplicationInfo info = getContext().getApplicationInfo();
+ // Copy the main apk into the data folder and use it as a "module".
+ File dexModuleDir = new File(info.dataDir, "module-dir");
+ File dexModule = new File(dexModuleDir, "module.apk");
+ try {
+ assertNotNull(FileUtils.createDir(
+ dexModuleDir.getParentFile(), dexModuleDir.getName()));
+ Files.copy(Paths.get(info.getBaseCodePath()), dexModule.toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ getPm().registerDexModule(dexModule.toString(), callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(dexModule.toString(), callback.mDexModulePath);
+ assertTrue(callback.waitTillDone());
+ assertTrue(callback.mMessage, callback.mSuccess);
+
+ // NOTE:
+ // This actually verifies internal behaviour which might change. It's not
+ // ideal but it's the best we can do since there's no other place we can currently
+ // write a better test.
+ for(String isa : getAppDexInstructionSets(info)) {
+ Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.odex"));
+ Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.vdex"));
+ }
+ } finally {
+ FileUtils.deleteContentsAndDir(dexModuleDir);
+ }
+ }
+
+ // If the module does not exist on disk we should get a failure.
+ public void testRegisterDexModuleNotExists() throws Exception {
+ ApplicationInfo info = getContext().getApplicationInfo();
+ String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
+ TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
+ getPm().registerDexModule(nonExistentApk, callback);
+ assertTrue(callback.waitTillDone());
+ assertEquals(nonExistentApk, callback.mDexModulePath);
+ assertTrue(callback.waitTillDone());
+ assertFalse("DexModule registration should fail", callback.mSuccess);
+ }
+
+ // Copied from com.android.server.pm.InstructionSets because we don't have access to it here.
+ private static String[] getAppDexInstructionSets(ApplicationInfo info) {
+ if (info.primaryCpuAbi != null) {
+ if (info.secondaryCpuAbi != null) {
+ return new String[] {
+ VMRuntime.getInstructionSet(info.primaryCpuAbi),
+ VMRuntime.getInstructionSet(info.secondaryCpuAbi) };
+ } else {
+ return new String[] {
+ VMRuntime.getInstructionSet(info.primaryCpuAbi) };
+ }
+ }
+
+ return new String[] { VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]) };
+ }
+
/*---------- Recommended install location tests ----*/
/*
* TODO's
diff --git a/services/core/java/com/android/server/pm/InstructionSets.java b/services/core/java/com/android/server/pm/InstructionSets.java
index 5092ebf..f326f1d 100644
--- a/services/core/java/com/android/server/pm/InstructionSets.java
+++ b/services/core/java/com/android/server/pm/InstructionSets.java
@@ -34,7 +34,7 @@
*/
public class InstructionSets {
private static final String PREFERRED_INSTRUCTION_SET =
- VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);;
+ VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
public static String[] getAppDexInstructionSets(ApplicationInfo info) {
if (info.primaryCpuAbi != null) {
if (info.secondaryCpuAbi != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e21a2b9..f173dbc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -127,6 +127,7 @@
import android.content.pm.AppsQueryHelper;
import android.content.pm.ChangedPackages;
import android.content.pm.ComponentInfo;
+import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.InstantAppRequest;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.FallbackCategoryProvider;
@@ -8729,6 +8730,31 @@
}
@Override
+ public void registerDexModule(String packageName, String dexModulePath, boolean isSharedModule,
+ IDexModuleRegisterCallback callback) {
+ int userId = UserHandle.getCallingUserId();
+ ApplicationInfo ai = getApplicationInfo(packageName, /*flags*/ 0, userId);
+ DexManager.RegisterDexModuleResult result;
+ if (ai == null) {
+ Slog.w(TAG, "Registering a dex module for a package that does not exist for the" +
+ " calling user. package=" + packageName + ", user=" + userId);
+ result = new DexManager.RegisterDexModuleResult(false, "Package not installed");
+ } else {
+ result = mDexManager.registerDexModule(ai, dexModulePath, isSharedModule, userId);
+ }
+
+ if (callback != null) {
+ mHandler.post(() -> {
+ try {
+ callback.onDexModuleRegistered(dexModulePath, result.success, result.message);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to callback after module registration " + dexModulePath, e);
+ }
+ });
+ }
+ }
+
+ @Override
public boolean performDexOpt(String packageName,
boolean checkProfiles, int compileReason, boolean force) {
int dexOptStatus = performDexOptTraced(packageName, checkProfiles,
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 3d7cedc..4a8232d 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -30,6 +30,7 @@
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.PackageDexOptimizer;
+import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.PackageManagerServiceCompilerMapping;
@@ -41,6 +42,7 @@
import java.util.Map;
import java.util.Set;
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
@@ -431,6 +433,52 @@
}
}
+ public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
+ boolean isUsedByOtherApps, int userId) {
+ // Find the owning package record.
+ DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
+
+ if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
+ return new RegisterDexModuleResult(false, "Package not found");
+ }
+ if (!info.packageName.equals(searchResult.mOwningPackageName)) {
+ return new RegisterDexModuleResult(false, "Dex path does not belong to package");
+ }
+ if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
+ searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
+ return new RegisterDexModuleResult(false, "Main apks cannot be registered");
+ }
+
+ // We found the package. Now record the usage for all declared ISAs.
+ boolean update = false;
+ Set<String> isas = new HashSet<>();
+ for (String isa : getAppDexInstructionSets(info)) {
+ isas.add(isa);
+ boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
+ dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false);
+ update |= newUpdate;
+ }
+ if (update) {
+ mPackageDexUsage.maybeWriteAsync();
+ }
+
+ // Try to optimize the package according to the install reason.
+ String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+ PackageManagerService.REASON_INSTALL);
+ int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas,
+ compilerFilter, isUsedByOtherApps);
+
+ // If we fail to optimize the package log an error but don't propagate the error
+ // back to the app. The app cannot do much about it and the background job
+ // will rety again when it executes.
+ // TODO(calin): there might be some value to return the error here but it may
+ // cause red herrings since that doesn't mean the app cannot use the module.
+ if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
+ Slog.e(TAG, "Failed to optimize dex module " + dexPath);
+ }
+ return new RegisterDexModuleResult(true, "Dex module registered successfully");
+ }
+
/**
* Return all packages that contain records of secondary dex files.
*/
@@ -510,6 +558,20 @@
return existingValue == null ? newValue : existingValue;
}
+ public static class RegisterDexModuleResult {
+ public RegisterDexModuleResult() {
+ this(false, null);
+ }
+
+ public RegisterDexModuleResult(boolean success, String message) {
+ this.success = success;
+ this.message = message;
+ }
+
+ public final boolean success;
+ public final String message;
+ }
+
/**
* Convenience class to store the different locations where a package might
* own code.
@@ -589,6 +651,4 @@
return mOwningPackageName + "-" + mOutcome;
}
}
-
-
}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 2d3d79a..9a03c53 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -17,6 +17,7 @@
package android.test.mock;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -1159,4 +1160,13 @@
public String getInstantAppAndroidId(String packageName, UserHandle user) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public void registerDexModule(String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 764eeeb..47dad34 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -949,4 +949,10 @@
public String getInstantAppAndroidId(String packageName, UserHandle user) {
return null;
}
+
+ @Override
+ public void registerDexModule(String dexModulePath,
+ @Nullable DexModuleRegisterCallback callback) {
+ callback.onDexModuleRegistered(dexModulePath, false, null);
+ }
}