From 3d2af7f72a2e6be36f0f2cbf899ad4d0bde451b4 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Wed, 19 Apr 2017 19:56:21 -0700 Subject: [PATCH] SystemApi for dex module registration PackageManager#registerDexModule() allows apps which can call system apis to register a dex module with the Package Manager. The PM may optimize the modules on the spot if needed. This is particular useful for shared dex modules (e.g. chimera modules) which are loaded in multiple processes. Test: adb shell am instrument -e class 'android.content.pm.PackageManagerTests' -w 'com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner' Bug: 37290820 Change-Id: I9ea8f937a76d2549a29e90a6c84c53c2e44a1ee4 --- Android.mk | 1 + api/system-current.txt | 7 + .../app/ApplicationPackageManager.java | 78 +++++++++++ .../pm/IDexModuleRegisterCallback.aidl | 28 ++++ .../android/content/pm/IPackageManager.aidl | 33 +++++ .../android/content/pm/PackageManager.java | 44 +++++++ .../content/pm/PackageManagerTests.java | 121 +++++++++++++++++- .../android/server/pm/InstructionSets.java | 2 +- .../server/pm/PackageManagerService.java | 26 ++++ .../com/android/server/pm/dex/DexManager.java | 64 ++++++++- .../android/test/mock/MockPackageManager.java | 10 ++ .../bridge/android/BridgePackageManager.java | 6 + 12 files changed, 415 insertions(+), 5 deletions(-) create mode 100644 core/java/android/content/pm/IDexModuleRegisterCallback.aidl diff --git a/Android.mk b/Android.mk index 260da03b4f232..57f0881ad1cd2 100644 --- a/Android.mk +++ b/Android.mk @@ -157,6 +157,7 @@ LOCAL_SRC_FILES += \ 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 2eaa31dd0d3f3..61535a4c12b41 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -11339,6 +11339,7 @@ package android.content.pm { method public abstract java.util.List queryIntentContentProviders(android.content.Intent, int); method public abstract java.util.List queryIntentServices(android.content.Intent, int); method public abstract java.util.List 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); @@ -11559,6 +11560,11 @@ package android.content.pm { 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); @@ -44558,6 +44564,7 @@ package android.test.mock { method public java.util.List queryIntentContentProviders(android.content.Intent, int); method public java.util.List queryIntentServices(android.content.Intent, int); method public java.util.List 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 e50c307df8545..f6aea96bf7e28 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -78,6 +78,10 @@ import android.os.UserManager; 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 @@ public class ApplicationPackageManager extends PackageManager { 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 0000000000000..4af6999c09bac --- /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 c7dd1fad4edc6..77891fa84f78e 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.ApplicationInfo; 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; @@ -473,6 +474,38 @@ interface IPackageManager { */ void notifyDexLoad(String loadingPackageName, in List 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 diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 10b7965c58ea8..07125e035edce 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5725,4 +5725,48 @@ public abstract class PackageManager { * @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 33a04939387ae..698f2ec1c2f3f 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; 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 android.util.Log; 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 class PackageManagerTests extends AndroidTestCase { 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 @@ public class PackageManagerTests extends AndroidTestCase { 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 5092ebf9d4b93..f326f1d20c46f 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 @@ import dalvik.system.VMRuntime; */ 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 99b24e2c42e59..5eca40d143a46 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.ApplicationInfo; 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; @@ -8614,6 +8615,31 @@ public class PackageManagerService extends IPackageManager.Stub mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId); } + @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) { 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 3d7cedce522af..4a8232da2b35d 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.internal.annotations.GuardedBy; 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.HashSet; 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 class DexManager { } } + 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 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 @@ public class DexManager { 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 @@ public class DexManager { return mOwningPackageName + "-" + mOutcome; } } - - } diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 2d3d79a034464..9a03c53d95cc1 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 class MockPackageManager extends PackageManager { 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 764eeebaef6a8..47dad3404beba 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 class BridgePackageManager extends PackageManager { public String getInstantAppAndroidId(String packageName, UserHandle user) { return null; } + + @Override + public void registerDexModule(String dexModulePath, + @Nullable DexModuleRegisterCallback callback) { + callback.onDexModuleRegistered(dexModulePath, false, null); + } }