2305 lines
96 KiB
Java
2305 lines
96 KiB
Java
/*
|
|
* Copyright (C) 2010 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.app;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.IIntentReceiver;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.content.pm.IPackageManager;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
import android.content.pm.SharedLibraryInfo;
|
|
import android.content.pm.dex.ArtManager;
|
|
import android.content.pm.split.SplitDependencyLoader;
|
|
import android.content.res.AssetManager;
|
|
import android.content.res.CompatibilityInfo;
|
|
import android.content.res.Resources;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Environment;
|
|
import android.os.FileUtils;
|
|
import android.os.GraphicsEnvironment;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.Process;
|
|
import android.os.RemoteException;
|
|
import android.os.StrictMode;
|
|
import android.os.SystemProperties;
|
|
import android.os.Trace;
|
|
import android.os.UserHandle;
|
|
import android.provider.Settings;
|
|
import android.security.net.config.NetworkSecurityConfigProvider;
|
|
import android.text.TextUtils;
|
|
import android.util.AndroidRuntimeException;
|
|
import android.util.ArrayMap;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
import android.util.Slog;
|
|
import android.util.SparseArray;
|
|
import android.view.DisplayAdjustments;
|
|
|
|
import com.android.internal.R;
|
|
import com.android.internal.annotations.GuardedBy;
|
|
import com.android.internal.util.ArrayUtils;
|
|
|
|
import dalvik.system.BaseDexClassLoader;
|
|
import dalvik.system.VMRuntime;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.lang.ref.WeakReference;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.net.URL;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.concurrent.Executor;
|
|
|
|
final class IntentReceiverLeaked extends AndroidRuntimeException {
|
|
@UnsupportedAppUsage
|
|
public IntentReceiverLeaked(String msg) {
|
|
super(msg);
|
|
}
|
|
}
|
|
|
|
final class ServiceConnectionLeaked extends AndroidRuntimeException {
|
|
@UnsupportedAppUsage
|
|
public ServiceConnectionLeaked(String msg) {
|
|
super(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Local state maintained about a currently loaded .apk.
|
|
* @hide
|
|
*/
|
|
public final class LoadedApk {
|
|
static final String TAG = "LoadedApk";
|
|
static final boolean DEBUG = false;
|
|
|
|
@UnsupportedAppUsage
|
|
private final ActivityThread mActivityThread;
|
|
@UnsupportedAppUsage
|
|
final String mPackageName;
|
|
@UnsupportedAppUsage
|
|
private ApplicationInfo mApplicationInfo;
|
|
@UnsupportedAppUsage
|
|
private String mAppDir;
|
|
@UnsupportedAppUsage
|
|
private String mResDir;
|
|
private String[] mLegacyOverlayDirs;
|
|
private String[] mOverlayPaths;
|
|
@UnsupportedAppUsage
|
|
private String mDataDir;
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
private String mLibDir;
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
private File mDataDirFile;
|
|
private File mDeviceProtectedDataDirFile;
|
|
private File mCredentialProtectedDataDirFile;
|
|
@UnsupportedAppUsage
|
|
private final ClassLoader mBaseClassLoader;
|
|
private ClassLoader mDefaultClassLoader;
|
|
private final boolean mSecurityViolation;
|
|
private final boolean mIncludeCode;
|
|
private final boolean mRegisterPackage;
|
|
@UnsupportedAppUsage
|
|
private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
|
|
/** WARNING: This may change. Don't hold external references to it. */
|
|
@UnsupportedAppUsage
|
|
Resources mResources;
|
|
@UnsupportedAppUsage
|
|
private ClassLoader mClassLoader;
|
|
@UnsupportedAppUsage
|
|
private Application mApplication;
|
|
|
|
private String[] mSplitNames;
|
|
private String[] mSplitAppDirs;
|
|
@UnsupportedAppUsage
|
|
private String[] mSplitResDirs;
|
|
private String[] mSplitClassLoaderNames;
|
|
|
|
@UnsupportedAppUsage
|
|
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
|
|
= new ArrayMap<>();
|
|
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
|
|
= new ArrayMap<>();
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
|
|
= new ArrayMap<>();
|
|
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
|
|
= new ArrayMap<>();
|
|
private AppComponentFactory mAppComponentFactory;
|
|
|
|
/**
|
|
* We cache the instantiated application object for each package on this process here.
|
|
*/
|
|
@GuardedBy("sApplications")
|
|
private static final ArrayMap<String, Application> sApplications = new ArrayMap<>(4);
|
|
|
|
private final Object mLock = new Object();
|
|
|
|
Application getApplication() {
|
|
return mApplication;
|
|
}
|
|
|
|
/**
|
|
* Create information about a new .apk
|
|
*
|
|
* NOTE: This constructor is called with ActivityThread's lock held,
|
|
* so MUST NOT call back out to the activity manager.
|
|
*/
|
|
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
|
|
CompatibilityInfo compatInfo, ClassLoader baseLoader,
|
|
boolean securityViolation, boolean includeCode, boolean registerPackage) {
|
|
|
|
mActivityThread = activityThread;
|
|
setApplicationInfo(aInfo);
|
|
mPackageName = aInfo.packageName;
|
|
mBaseClassLoader = baseLoader;
|
|
mSecurityViolation = securityViolation;
|
|
mIncludeCode = includeCode;
|
|
mRegisterPackage = registerPackage;
|
|
mDisplayAdjustments.setCompatibilityInfo(compatInfo);
|
|
mAppComponentFactory = createAppFactory(mApplicationInfo, mBaseClassLoader);
|
|
}
|
|
|
|
private static ApplicationInfo adjustNativeLibraryPaths(ApplicationInfo info) {
|
|
// If we're dealing with a multi-arch application that has both
|
|
// 32 and 64 bit shared libraries, we might need to choose the secondary
|
|
// depending on what the current runtime's instruction set is.
|
|
if (info.primaryCpuAbi != null && info.secondaryCpuAbi != null) {
|
|
final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();
|
|
|
|
// Get the instruction set that the libraries of secondary Abi is supported.
|
|
// In presence of a native bridge this might be different than the one secondary Abi used.
|
|
String secondaryIsa = VMRuntime.getInstructionSet(info.secondaryCpuAbi);
|
|
final String secondaryDexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + secondaryIsa);
|
|
secondaryIsa = secondaryDexCodeIsa.isEmpty() ? secondaryIsa : secondaryDexCodeIsa;
|
|
|
|
// If the runtimeIsa is the same as the primary isa, then we do nothing.
|
|
// Everything will be set up correctly because info.nativeLibraryDir will
|
|
// correspond to the right ISA.
|
|
if (runtimeIsa.equals(secondaryIsa)) {
|
|
final ApplicationInfo modified = new ApplicationInfo(info);
|
|
modified.nativeLibraryDir = modified.secondaryNativeLibraryDir;
|
|
modified.primaryCpuAbi = modified.secondaryCpuAbi;
|
|
return modified;
|
|
}
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Create information about the system package.
|
|
* Must call {@link #installSystemApplicationInfo} later.
|
|
*/
|
|
LoadedApk(ActivityThread activityThread) {
|
|
mActivityThread = activityThread;
|
|
mApplicationInfo = new ApplicationInfo();
|
|
mApplicationInfo.packageName = "android";
|
|
mPackageName = "android";
|
|
mAppDir = null;
|
|
mResDir = null;
|
|
mSplitAppDirs = null;
|
|
mSplitResDirs = null;
|
|
mSplitClassLoaderNames = null;
|
|
mLegacyOverlayDirs = null;
|
|
mOverlayPaths = null;
|
|
mDataDir = null;
|
|
mDataDirFile = null;
|
|
mDeviceProtectedDataDirFile = null;
|
|
mCredentialProtectedDataDirFile = null;
|
|
mLibDir = null;
|
|
mBaseClassLoader = null;
|
|
mSecurityViolation = false;
|
|
mIncludeCode = true;
|
|
mRegisterPackage = false;
|
|
mResources = Resources.getSystem();
|
|
mDefaultClassLoader = ClassLoader.getSystemClassLoader();
|
|
mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
|
|
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
|
|
new ApplicationInfo(mApplicationInfo));
|
|
}
|
|
|
|
/**
|
|
* Sets application info about the system package.
|
|
*/
|
|
void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
|
|
assert info.packageName.equals("android");
|
|
mApplicationInfo = info;
|
|
mDefaultClassLoader = classLoader;
|
|
mAppComponentFactory = createAppFactory(info, mDefaultClassLoader);
|
|
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
|
|
new ApplicationInfo(mApplicationInfo));
|
|
}
|
|
|
|
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
|
|
if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) {
|
|
try {
|
|
return (AppComponentFactory)
|
|
cl.loadClass(appInfo.appComponentFactory).newInstance();
|
|
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
|
|
Slog.e(TAG, "Unable to instantiate appComponentFactory", e);
|
|
}
|
|
}
|
|
return AppComponentFactory.DEFAULT;
|
|
}
|
|
|
|
public AppComponentFactory getAppFactory() {
|
|
return mAppComponentFactory;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public String getPackageName() {
|
|
return mPackageName;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public ApplicationInfo getApplicationInfo() {
|
|
return mApplicationInfo;
|
|
}
|
|
|
|
public int getTargetSdkVersion() {
|
|
return mApplicationInfo.targetSdkVersion;
|
|
}
|
|
|
|
public boolean isSecurityViolation() {
|
|
return mSecurityViolation;
|
|
}
|
|
|
|
@UnsupportedAppUsage(trackingBug = 172409979)
|
|
public CompatibilityInfo getCompatibilityInfo() {
|
|
return mDisplayAdjustments.getCompatibilityInfo();
|
|
}
|
|
|
|
public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
|
|
mDisplayAdjustments.setCompatibilityInfo(compatInfo);
|
|
}
|
|
|
|
/**
|
|
* Gets the array of shared libraries that are listed as
|
|
* used by the given package.
|
|
*
|
|
* @param packageName the name of the package (note: not its
|
|
* file name)
|
|
* @return null-ok; the array of shared libraries, each one
|
|
* a fully-qualified path
|
|
*/
|
|
private static String[] getLibrariesFor(String packageName) {
|
|
ApplicationInfo ai = null;
|
|
try {
|
|
ai = ActivityThread.getPackageManager().getApplicationInfo(packageName,
|
|
PackageManager.GET_SHARED_LIBRARY_FILES, UserHandle.myUserId());
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
|
|
if (ai == null) {
|
|
return null;
|
|
}
|
|
|
|
return ai.sharedLibraryFiles;
|
|
}
|
|
|
|
/**
|
|
* Update the ApplicationInfo for an app. If oldPaths is null, all the paths are considered
|
|
* new.
|
|
* @param aInfo The new ApplicationInfo to use for this LoadedApk
|
|
* @param oldPaths The code paths for the old ApplicationInfo object. null means no paths can
|
|
* be reused.
|
|
*/
|
|
public void updateApplicationInfo(@NonNull ApplicationInfo aInfo,
|
|
@Nullable List<String> oldPaths) {
|
|
if (!setApplicationInfo(aInfo)) {
|
|
return;
|
|
}
|
|
|
|
final List<String> newPaths = new ArrayList<>();
|
|
makePaths(mActivityThread, aInfo, newPaths);
|
|
final List<String> addedPaths = new ArrayList<>(newPaths.size());
|
|
|
|
if (oldPaths != null) {
|
|
for (String path : newPaths) {
|
|
final String apkName = path.substring(path.lastIndexOf(File.separator));
|
|
boolean match = false;
|
|
for (String oldPath : oldPaths) {
|
|
final String oldApkName = oldPath.substring(oldPath.lastIndexOf(File.separator));
|
|
if (apkName.equals(oldApkName)) {
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!match) {
|
|
addedPaths.add(path);
|
|
}
|
|
}
|
|
} else {
|
|
addedPaths.addAll(newPaths);
|
|
}
|
|
synchronized (mLock) {
|
|
createOrUpdateClassLoaderLocked(addedPaths);
|
|
if (mResources != null) {
|
|
final String[] splitPaths;
|
|
try {
|
|
splitPaths = getSplitPaths(null);
|
|
} catch (NameNotFoundException e) {
|
|
// This should NEVER fail.
|
|
throw new AssertionError("null split not found");
|
|
}
|
|
|
|
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
|
|
splitPaths, mLegacyOverlayDirs, mOverlayPaths,
|
|
mApplicationInfo.sharedLibraryFiles, null, null, getCompatibilityInfo(),
|
|
getClassLoader(), mApplication == null ? null
|
|
: mApplication.getResources().getLoaders());
|
|
}
|
|
}
|
|
mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
|
|
}
|
|
|
|
private boolean setApplicationInfo(ApplicationInfo aInfo) {
|
|
if (mApplicationInfo != null && mApplicationInfo.createTimestamp > aInfo.createTimestamp) {
|
|
Slog.w(TAG, "New application info for package " + aInfo.packageName
|
|
+ " is out of date with TS " + aInfo.createTimestamp + " < the current TS "
|
|
+ mApplicationInfo.createTimestamp);
|
|
return false;
|
|
}
|
|
final int myUid = Process.myUid();
|
|
aInfo = adjustNativeLibraryPaths(aInfo);
|
|
mApplicationInfo = aInfo;
|
|
mAppDir = aInfo.sourceDir;
|
|
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
|
|
mLegacyOverlayDirs = aInfo.resourceDirs;
|
|
mOverlayPaths = aInfo.overlayPaths;
|
|
mDataDir = aInfo.dataDir;
|
|
mLibDir = aInfo.nativeLibraryDir;
|
|
mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
|
|
mDeviceProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.deviceProtectedDataDir);
|
|
mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(
|
|
aInfo.credentialProtectedDataDir);
|
|
|
|
mSplitNames = aInfo.splitNames;
|
|
mSplitAppDirs = aInfo.splitSourceDirs;
|
|
mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
|
|
mSplitClassLoaderNames = aInfo.splitClassLoaderNames;
|
|
|
|
if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
|
|
mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void setSdkSandboxStorage(@Nullable String sdkSandboxClientAppVolumeUuid,
|
|
String sdkSandboxClientAppPackage) {
|
|
int userId = UserHandle.myUserId();
|
|
mDeviceProtectedDataDirFile = Environment
|
|
.getDataMiscDeSharedSdkSandboxDirectory(sdkSandboxClientAppVolumeUuid, userId,
|
|
sdkSandboxClientAppPackage)
|
|
.getAbsoluteFile();
|
|
mCredentialProtectedDataDirFile = Environment
|
|
.getDataMiscCeSharedSdkSandboxDirectory(sdkSandboxClientAppVolumeUuid, userId,
|
|
sdkSandboxClientAppPackage)
|
|
.getAbsoluteFile();
|
|
|
|
if ((mApplicationInfo.privateFlags
|
|
& ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0
|
|
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
|
|
mDataDirFile = mDeviceProtectedDataDirFile;
|
|
} else {
|
|
mDataDirFile = mCredentialProtectedDataDirFile;
|
|
}
|
|
mDataDir = mDataDirFile.getAbsolutePath();
|
|
}
|
|
|
|
public static void makePaths(ActivityThread activityThread,
|
|
ApplicationInfo aInfo,
|
|
List<String> outZipPaths) {
|
|
makePaths(activityThread, false, aInfo, outZipPaths, null);
|
|
}
|
|
|
|
private static void appendSharedLibrariesLibPathsIfNeeded(
|
|
List<SharedLibraryInfo> sharedLibraries, ApplicationInfo aInfo,
|
|
Set<String> outSeenPaths,
|
|
List<String> outLibPaths) {
|
|
if (sharedLibraries == null) {
|
|
return;
|
|
}
|
|
for (SharedLibraryInfo lib : sharedLibraries) {
|
|
if (lib.isNative()) {
|
|
// Native shared lib doesn't contribute to the native lib search path. Its name is
|
|
// sent to libnativeloader and then the native shared lib is exported from the
|
|
// default linker namespace.
|
|
continue;
|
|
}
|
|
List<String> paths = lib.getAllCodePaths();
|
|
outSeenPaths.addAll(paths);
|
|
for (String path : paths) {
|
|
appendApkLibPathIfNeeded(path, aInfo, outLibPaths);
|
|
}
|
|
appendSharedLibrariesLibPathsIfNeeded(
|
|
lib.getDependencies(), aInfo, outSeenPaths, outLibPaths);
|
|
}
|
|
}
|
|
|
|
public static void makePaths(ActivityThread activityThread,
|
|
boolean isBundledApp,
|
|
ApplicationInfo aInfo,
|
|
List<String> outZipPaths,
|
|
List<String> outLibPaths) {
|
|
final String appDir = aInfo.sourceDir;
|
|
final String libDir = aInfo.nativeLibraryDir;
|
|
|
|
outZipPaths.clear();
|
|
outZipPaths.add(appDir);
|
|
|
|
// Do not load all available splits if the app requested isolated split loading.
|
|
if (aInfo.splitSourceDirs != null && !aInfo.requestsIsolatedSplitLoading()) {
|
|
Collections.addAll(outZipPaths, aInfo.splitSourceDirs);
|
|
}
|
|
|
|
if (outLibPaths != null) {
|
|
outLibPaths.clear();
|
|
}
|
|
|
|
/*
|
|
* The following is a bit of a hack to inject
|
|
* instrumentation into the system: If the app
|
|
* being started matches one of the instrumentation names,
|
|
* then we combine both the "instrumentation" and
|
|
* "instrumented" app into the path, along with the
|
|
* concatenation of both apps' shared library lists.
|
|
*/
|
|
|
|
String[] instrumentationLibs = null;
|
|
// activityThread will be null when called from the WebView zygote; just assume
|
|
// no instrumentation applies in this case.
|
|
if (activityThread != null) {
|
|
String instrumentationPackageName = activityThread.mInstrumentationPackageName;
|
|
String instrumentationAppDir = activityThread.mInstrumentationAppDir;
|
|
String[] instrumentationSplitAppDirs = activityThread.mInstrumentationSplitAppDirs;
|
|
String instrumentationLibDir = activityThread.mInstrumentationLibDir;
|
|
|
|
String instrumentedAppDir = activityThread.mInstrumentedAppDir;
|
|
String[] instrumentedSplitAppDirs = activityThread.mInstrumentedSplitAppDirs;
|
|
String instrumentedLibDir = activityThread.mInstrumentedLibDir;
|
|
|
|
if (appDir.equals(instrumentationAppDir)
|
|
|| appDir.equals(instrumentedAppDir)) {
|
|
outZipPaths.clear();
|
|
outZipPaths.add(instrumentationAppDir);
|
|
if (!instrumentationAppDir.equals(instrumentedAppDir)) {
|
|
outZipPaths.add(instrumentedAppDir);
|
|
}
|
|
|
|
// Only add splits if the app did not request isolated split loading.
|
|
if (!aInfo.requestsIsolatedSplitLoading()) {
|
|
if (instrumentationSplitAppDirs != null) {
|
|
Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
|
|
}
|
|
|
|
if (!instrumentationAppDir.equals(instrumentedAppDir)) {
|
|
if (instrumentedSplitAppDirs != null) {
|
|
Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (outLibPaths != null) {
|
|
outLibPaths.add(instrumentationLibDir);
|
|
if (!instrumentationLibDir.equals(instrumentedLibDir)) {
|
|
outLibPaths.add(instrumentedLibDir);
|
|
}
|
|
}
|
|
|
|
if (!instrumentedAppDir.equals(instrumentationAppDir)) {
|
|
instrumentationLibs = getLibrariesFor(instrumentationPackageName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (outLibPaths != null) {
|
|
if (outLibPaths.isEmpty()) {
|
|
outLibPaths.add(libDir);
|
|
}
|
|
|
|
// Add path to libraries in apk for current abi. Do this now because more entries
|
|
// will be added to zipPaths that shouldn't be part of the library path.
|
|
if (aInfo.primaryCpuAbi != null) {
|
|
// Add fake libs into the library search path if we target prior to N.
|
|
if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) {
|
|
outLibPaths.add("/system/fake-libs" +
|
|
(VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : ""));
|
|
}
|
|
for (String apk : outZipPaths) {
|
|
outLibPaths.add(apk + "!/lib/" + aInfo.primaryCpuAbi);
|
|
}
|
|
}
|
|
|
|
if (isBundledApp) {
|
|
// Add path to system libraries to libPaths;
|
|
// Access to system libs should be limited
|
|
// to bundled applications; this is why updated
|
|
// system apps are not included.
|
|
outLibPaths.add(System.getProperty("java.library.path"));
|
|
}
|
|
}
|
|
|
|
// Add the shared libraries native paths. The dex files in shared libraries will
|
|
// be resolved through shared library loaders, which are setup later.
|
|
Set<String> outSeenPaths = new LinkedHashSet<>();
|
|
appendSharedLibrariesLibPathsIfNeeded(
|
|
aInfo.sharedLibraryInfos, aInfo, outSeenPaths, outLibPaths);
|
|
|
|
// ApplicationInfo.sharedLibraryFiles is a public API, so anyone can change it.
|
|
// We prepend shared libraries that the package manager hasn't seen, maintaining their
|
|
// original order where possible.
|
|
if (aInfo.sharedLibraryFiles != null) {
|
|
int index = 0;
|
|
for (String lib : aInfo.sharedLibraryFiles) {
|
|
// sharedLibraryFiles might contain native shared libraries that are not APK paths.
|
|
if (!lib.endsWith(".apk")) {
|
|
continue;
|
|
}
|
|
if (!outSeenPaths.contains(lib) && !outZipPaths.contains(lib)) {
|
|
outZipPaths.add(index, lib);
|
|
index++;
|
|
appendApkLibPathIfNeeded(lib, aInfo, outLibPaths);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (instrumentationLibs != null) {
|
|
for (String lib : instrumentationLibs) {
|
|
if (!outZipPaths.contains(lib)) {
|
|
outZipPaths.add(0, lib);
|
|
appendApkLibPathIfNeeded(lib, aInfo, outLibPaths);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method appends a path to the appropriate native library folder of a
|
|
* library if this library is hosted in an APK. This allows support for native
|
|
* shared libraries. The library API is determined based on the application
|
|
* ABI.
|
|
*
|
|
* @param path Path to the library.
|
|
* @param applicationInfo The application depending on the library.
|
|
* @param outLibPaths List to which to add the native lib path if needed.
|
|
*/
|
|
private static void appendApkLibPathIfNeeded(@NonNull String path,
|
|
@NonNull ApplicationInfo applicationInfo, @Nullable List<String> outLibPaths) {
|
|
// Looking at the suffix is a little hacky but a safe and simple solution.
|
|
// We will be revisiting code in the next release and clean this up.
|
|
if (outLibPaths != null && applicationInfo.primaryCpuAbi != null && path.endsWith(".apk")) {
|
|
if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
|
|
outLibPaths.add(path + "!/lib/" + applicationInfo.primaryCpuAbi);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* All indices received by the super class should be shifted by 1 when accessing mSplitNames,
|
|
* etc. The super class assumes the base APK is index 0, while the PackageManager APIs don't
|
|
* include the base APK in the list of splits.
|
|
*/
|
|
private class SplitDependencyLoaderImpl extends SplitDependencyLoader<NameNotFoundException> {
|
|
@GuardedBy("mLock")
|
|
private final String[][] mCachedResourcePaths;
|
|
@GuardedBy("mLock")
|
|
private final ClassLoader[] mCachedClassLoaders;
|
|
|
|
SplitDependencyLoaderImpl(@NonNull SparseArray<int[]> dependencies) {
|
|
super(dependencies);
|
|
mCachedResourcePaths = new String[mSplitNames.length + 1][];
|
|
mCachedClassLoaders = new ClassLoader[mSplitNames.length + 1];
|
|
}
|
|
|
|
@Override
|
|
protected boolean isSplitCached(int splitIdx) {
|
|
synchronized (mLock) {
|
|
return mCachedClassLoaders[splitIdx] != null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
|
|
int parentSplitIdx) throws NameNotFoundException {
|
|
synchronized (mLock) {
|
|
final ArrayList<String> splitPaths = new ArrayList<>();
|
|
if (splitIdx == 0) {
|
|
createOrUpdateClassLoaderLocked(null);
|
|
mCachedClassLoaders[0] = mClassLoader;
|
|
|
|
// Never add the base resources here, they always get added no matter what.
|
|
for (int configSplitIdx : configSplitIndices) {
|
|
splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
|
|
}
|
|
mCachedResourcePaths[0] = splitPaths.toArray(new String[splitPaths.size()]);
|
|
return;
|
|
}
|
|
|
|
// Since we handled the special base case above, parentSplitIdx is always valid.
|
|
final ClassLoader parent = mCachedClassLoaders[parentSplitIdx];
|
|
mCachedClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
|
|
mSplitAppDirs[splitIdx - 1], getTargetSdkVersion(), false, null,
|
|
null, parent, mSplitClassLoaderNames[splitIdx - 1]);
|
|
|
|
Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
|
|
splitPaths.add(mSplitResDirs[splitIdx - 1]);
|
|
for (int configSplitIdx : configSplitIndices) {
|
|
splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
|
|
}
|
|
mCachedResourcePaths[splitIdx] = splitPaths.toArray(new String[splitPaths.size()]);
|
|
}
|
|
}
|
|
|
|
private int ensureSplitLoaded(String splitName) throws NameNotFoundException {
|
|
int idx = 0;
|
|
if (splitName != null) {
|
|
idx = Arrays.binarySearch(mSplitNames, splitName);
|
|
if (idx < 0) {
|
|
throw new PackageManager.NameNotFoundException(
|
|
"Split name '" + splitName + "' is not installed");
|
|
}
|
|
idx += 1;
|
|
}
|
|
loadDependenciesForSplit(idx);
|
|
return idx;
|
|
}
|
|
|
|
ClassLoader getClassLoaderForSplit(String splitName) throws NameNotFoundException {
|
|
final int idx = ensureSplitLoaded(splitName);
|
|
synchronized (mLock) {
|
|
return mCachedClassLoaders[idx];
|
|
}
|
|
}
|
|
|
|
String[] getSplitPathsForSplit(String splitName) throws NameNotFoundException {
|
|
final int idx = ensureSplitLoaded(splitName);
|
|
synchronized (mLock) {
|
|
return mCachedResourcePaths[idx];
|
|
}
|
|
}
|
|
}
|
|
|
|
private SplitDependencyLoaderImpl mSplitLoader;
|
|
|
|
ClassLoader getSplitClassLoader(String splitName) throws NameNotFoundException {
|
|
if (mSplitLoader == null) {
|
|
return mClassLoader;
|
|
}
|
|
return mSplitLoader.getClassLoaderForSplit(splitName);
|
|
}
|
|
|
|
String[] getSplitPaths(String splitName) throws NameNotFoundException {
|
|
if (mSplitLoader == null) {
|
|
return mSplitResDirs;
|
|
}
|
|
return mSplitLoader.getSplitPathsForSplit(splitName);
|
|
}
|
|
|
|
/**
|
|
* Create a class loader for the {@code sharedLibrary}. Shared libraries are canonicalized,
|
|
* so if we already created a class loader with that shared library, we return it.
|
|
*
|
|
* Implementation notes: the canonicalization of shared libraries is something dex2oat
|
|
* also does.
|
|
*/
|
|
ClassLoader createSharedLibraryLoader(SharedLibraryInfo sharedLibrary,
|
|
boolean isBundledApp, String librarySearchPath, String libraryPermittedPath) {
|
|
List<String> paths = sharedLibrary.getAllCodePaths();
|
|
Pair<List<ClassLoader>, List<ClassLoader>> sharedLibraries = createSharedLibrariesLoaders(
|
|
sharedLibrary.getDependencies(), isBundledApp, librarySearchPath,
|
|
libraryPermittedPath);
|
|
final String jars = (paths.size() == 1) ? paths.get(0) :
|
|
TextUtils.join(File.pathSeparator, paths);
|
|
|
|
// Shared libraries get a null parent: this has the side effect of having canonicalized
|
|
// shared libraries using ApplicationLoaders cache, which is the behavior we want.
|
|
return ApplicationLoaders.getDefault().getSharedLibraryClassLoaderWithSharedLibraries(jars,
|
|
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
|
|
libraryPermittedPath, /* parent */ null,
|
|
/* classLoaderName */ null, sharedLibraries.first, sharedLibraries.second);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return a {@link Pair} of List<ClassLoader> where the first is for standard shared libraries
|
|
* and the second is list for shared libraries that code should be loaded after the dex
|
|
*/
|
|
private Pair<List<ClassLoader>, List<ClassLoader>> createSharedLibrariesLoaders(
|
|
List<SharedLibraryInfo> sharedLibraries,
|
|
boolean isBundledApp, String librarySearchPath, String libraryPermittedPath) {
|
|
if (sharedLibraries == null || sharedLibraries.isEmpty()) {
|
|
return new Pair<>(null, null);
|
|
}
|
|
|
|
// if configured to do so, shared libs are split into 2 collections: those that are
|
|
// on the class path before the applications code, which is standard, and those
|
|
// specified to be loaded after the applications code.
|
|
HashSet<String> libsToLoadAfter = new HashSet<>();
|
|
Resources systemR = Resources.getSystem();
|
|
Collections.addAll(libsToLoadAfter, systemR.getStringArray(
|
|
R.array.config_sharedLibrariesLoadedAfterApp));
|
|
|
|
List<ClassLoader> loaders = new ArrayList<>();
|
|
List<ClassLoader> after = new ArrayList<>();
|
|
for (SharedLibraryInfo info : sharedLibraries) {
|
|
if (info.isNative()) {
|
|
// Native shared lib doesn't contribute to the native lib search path. Its name is
|
|
// sent to libnativeloader and then the native shared lib is exported from the
|
|
// default linker namespace.
|
|
continue;
|
|
}
|
|
if (info.isSdk()) {
|
|
// SDKs are not loaded automatically.
|
|
continue;
|
|
}
|
|
if (libsToLoadAfter.contains(info.getName())) {
|
|
if (DEBUG) {
|
|
Slog.v(ActivityThread.TAG,
|
|
info.getName() + " will be loaded after application code");
|
|
}
|
|
after.add(createSharedLibraryLoader(
|
|
info, isBundledApp, librarySearchPath, libraryPermittedPath));
|
|
} else {
|
|
loaders.add(createSharedLibraryLoader(
|
|
info, isBundledApp, librarySearchPath, libraryPermittedPath));
|
|
}
|
|
}
|
|
return new Pair<>(loaders, after);
|
|
}
|
|
|
|
private StrictMode.ThreadPolicy allowThreadDiskReads() {
|
|
if (mActivityThread == null) {
|
|
// When LoadedApk is used without an ActivityThread (usually in a
|
|
// zygote context), don't call into StrictMode, as it initializes
|
|
// the binder subsystem, which we don't want.
|
|
return null;
|
|
}
|
|
|
|
return StrictMode.allowThreadDiskReads();
|
|
}
|
|
|
|
private void setThreadPolicy(StrictMode.ThreadPolicy policy) {
|
|
if (mActivityThread != null && policy != null) {
|
|
StrictMode.setThreadPolicy(policy);
|
|
}
|
|
}
|
|
|
|
private StrictMode.VmPolicy allowVmViolations() {
|
|
if (mActivityThread == null) {
|
|
// When LoadedApk is used without an ActivityThread (usually in a
|
|
// zygote context), don't call into StrictMode, as it initializes
|
|
// the binder subsystem, which we don't want.
|
|
return null;
|
|
}
|
|
|
|
return StrictMode.allowVmViolations();
|
|
}
|
|
|
|
private void setVmPolicy(StrictMode.VmPolicy policy) {
|
|
if (mActivityThread != null && policy != null) {
|
|
StrictMode.setVmPolicy(policy);
|
|
}
|
|
}
|
|
|
|
@GuardedBy("mLock")
|
|
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
|
|
if (mPackageName.equals("android")) {
|
|
// Note: This branch is taken for system server and we don't need to setup
|
|
// jit profiling support.
|
|
if (mClassLoader != null) {
|
|
// nothing to update
|
|
return;
|
|
}
|
|
|
|
if (mBaseClassLoader != null) {
|
|
mDefaultClassLoader = mBaseClassLoader;
|
|
} else {
|
|
mDefaultClassLoader = ClassLoader.getSystemClassLoader();
|
|
}
|
|
mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
|
|
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
|
|
new ApplicationInfo(mApplicationInfo));
|
|
return;
|
|
}
|
|
|
|
// Avoid the binder call when the package is the current application package.
|
|
// The activity manager will perform ensure that dexopt is performed before
|
|
// spinning up the process. Similarly, don't call into binder when we don't
|
|
// have an ActivityThread object.
|
|
if (mActivityThread != null
|
|
&& !Objects.equals(mPackageName, ActivityThread.currentPackageName())
|
|
&& mIncludeCode) {
|
|
try {
|
|
ActivityThread.getPackageManager().notifyPackageUse(mPackageName,
|
|
PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE);
|
|
} catch (RemoteException re) {
|
|
throw re.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
if (mRegisterPackage) {
|
|
try {
|
|
ActivityManager.getService().addPackageDependency(mPackageName);
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
// Lists for the elements of zip/code and native libraries.
|
|
//
|
|
// Both lists are usually not empty. We expect on average one APK for the zip component,
|
|
// but shared libraries and splits are not uncommon. We expect at least three elements
|
|
// for native libraries (app-based, system, vendor). As such, give both some breathing
|
|
// space and initialize to a small value (instead of incurring growth code).
|
|
final List<String> zipPaths = new ArrayList<>(10);
|
|
final List<String> libPaths = new ArrayList<>(10);
|
|
|
|
boolean isBundledApp = mApplicationInfo.isSystemApp()
|
|
&& !mApplicationInfo.isUpdatedSystemApp();
|
|
|
|
// Vendor apks are treated as bundled only when /vendor/lib is in the default search
|
|
// paths. If not, they are treated as unbundled; access to system libs is limited.
|
|
// Having /vendor/lib in the default search paths means that all system processes
|
|
// are allowed to use any vendor library, which in turn means that system is dependent
|
|
// on vendor partition. In the contrary, not having /vendor/lib in the default search
|
|
// paths mean that the two partitions are separated and thus we can treat vendor apks
|
|
// as unbundled.
|
|
final String defaultSearchPaths = System.getProperty("java.library.path");
|
|
final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib");
|
|
if (mApplicationInfo.getCodePath() != null
|
|
&& mApplicationInfo.isVendor() && treatVendorApkAsUnbundled) {
|
|
isBundledApp = false;
|
|
}
|
|
|
|
// Similar to vendor apks, we should add /product/lib for apks from product partition
|
|
// when product apps are marked as unbundled. Product is separated as long as the
|
|
// partition exists, so it can be handled with same approach from the vendor partition.
|
|
if (mApplicationInfo.getCodePath() != null
|
|
&& mApplicationInfo.isProduct()) {
|
|
isBundledApp = false;
|
|
}
|
|
|
|
makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
|
|
|
|
// Including an inaccessible dir in libraryPermittedPath would cause SELinux denials
|
|
// when the loader attempts to canonicalise the path. so we don't.
|
|
String libraryPermittedPath = canAccessDataDir() ? mDataDir : "";
|
|
|
|
if (isBundledApp) {
|
|
// For bundled apps, add the base directory of the app (e.g.,
|
|
// /system/app/Foo/) to the permitted paths so that it can load libraries
|
|
// embedded in module apks under the directory. For now, GmsCore is relying
|
|
// on this, but this isn't specific to the app. Also note that, we don't
|
|
// need to do this for unbundled apps as entire /data is already set to
|
|
// the permitted paths for them.
|
|
libraryPermittedPath += File.pathSeparator
|
|
+ Paths.get(getAppDir()).getParent().toString();
|
|
|
|
// This is necessary to grant bundled apps access to
|
|
// libraries located in subdirectories of /system/lib
|
|
libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
|
|
}
|
|
|
|
final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
|
|
|
|
if (mActivityThread != null) {
|
|
final String gpuDebugApp = mActivityThread.getStringCoreSetting(
|
|
Settings.Global.GPU_DEBUG_APP, "");
|
|
if (!gpuDebugApp.isEmpty() && mPackageName.equals(gpuDebugApp)) {
|
|
|
|
// The current application is used to debug, attempt to get the debug layers.
|
|
try {
|
|
// Get the ApplicationInfo from PackageManager so that metadata fields present.
|
|
final ApplicationInfo ai = ActivityThread.getPackageManager()
|
|
.getApplicationInfo(mPackageName, PackageManager.GET_META_DATA,
|
|
UserHandle.myUserId());
|
|
final String debugLayerPath = GraphicsEnvironment.getInstance()
|
|
.getDebugLayerPathsFromSettings(mActivityThread.getCoreSettings(),
|
|
ActivityThread.getPackageManager(), mPackageName, ai);
|
|
if (debugLayerPath != null) {
|
|
libraryPermittedPath += File.pathSeparator + debugLayerPath;
|
|
}
|
|
} catch (RemoteException e) {
|
|
// Unlikely to fail for applications, but in case of failure, something is wrong
|
|
// inside the system server, hence just skip.
|
|
Slog.e(ActivityThread.TAG,
|
|
"RemoteException when fetching debug layer paths for: " + mPackageName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're not asked to include code, we construct a classloader that has
|
|
// no code path included. We still need to set up the library search paths
|
|
// and permitted path because NativeActivity relies on it (it attempts to
|
|
// call System.loadLibrary() on a classloader from a LoadedApk with
|
|
// mIncludeCode == false).
|
|
if (!mIncludeCode) {
|
|
if (mDefaultClassLoader == null) {
|
|
StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
|
|
mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(
|
|
"" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
|
|
librarySearchPath, libraryPermittedPath, mBaseClassLoader,
|
|
null /* classLoaderName */);
|
|
setThreadPolicy(oldPolicy);
|
|
mAppComponentFactory = AppComponentFactory.DEFAULT;
|
|
}
|
|
|
|
if (mClassLoader == null) {
|
|
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
|
|
new ApplicationInfo(mApplicationInfo));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* With all the combination done (if necessary, actually create the java class
|
|
* loader and set up JIT profiling support if necessary.
|
|
*
|
|
* In many cases this is a single APK, so try to avoid the StringBuilder in TextUtils.
|
|
*/
|
|
final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
|
|
TextUtils.join(File.pathSeparator, zipPaths);
|
|
|
|
if (DEBUG) Slog.v(ActivityThread.TAG, "Class path: " + zip +
|
|
", JNI path: " + librarySearchPath);
|
|
|
|
boolean registerAppInfoToArt = false;
|
|
if (mDefaultClassLoader == null) {
|
|
// Setup the dex reporter to notify package manager
|
|
// of any relevant dex loads. The idle maintenance job will use the information
|
|
// reported to optimize the loaded dex files.
|
|
// Note that we only need one global reporter per app.
|
|
// Make sure we do this before creating the main app classloader for the first time
|
|
// so that we can capture the complete application startup.
|
|
//
|
|
// We should not do this in a zygote context (where mActivityThread will be null),
|
|
// thus we'll guard against it.
|
|
// Also, the system server reporter (SystemServerDexLoadReporter) is already registered
|
|
// when system server starts, so we don't need to do it here again.
|
|
if (mActivityThread != null && !ActivityThread.isSystem()) {
|
|
BaseDexClassLoader.setReporter(DexLoadReporter.getInstance());
|
|
}
|
|
|
|
// Temporarily disable logging of disk reads on the Looper thread
|
|
// as this is early and necessary.
|
|
StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
|
|
|
|
Pair<List<ClassLoader>, List<ClassLoader>> sharedLibraries =
|
|
createSharedLibrariesLoaders(mApplicationInfo.sharedLibraryInfos, isBundledApp,
|
|
librarySearchPath, libraryPermittedPath);
|
|
|
|
List<String> nativeSharedLibraries = new ArrayList<>();
|
|
if (mApplicationInfo.sharedLibraryInfos != null) {
|
|
for (SharedLibraryInfo info : mApplicationInfo.sharedLibraryInfos) {
|
|
if (info.isNative()) {
|
|
nativeSharedLibraries.add(info.getName());
|
|
}
|
|
}
|
|
}
|
|
|
|
mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
|
|
zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
|
|
libraryPermittedPath, mBaseClassLoader,
|
|
mApplicationInfo.classLoaderName, sharedLibraries.first, nativeSharedLibraries,
|
|
sharedLibraries.second);
|
|
mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
|
|
|
|
setThreadPolicy(oldPolicy);
|
|
// Setup the class loader paths for profiling.
|
|
registerAppInfoToArt = true;
|
|
}
|
|
|
|
if (!libPaths.isEmpty()) {
|
|
// Temporarily disable logging of disk reads on the Looper thread as this is necessary
|
|
StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
|
|
try {
|
|
ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, libPaths);
|
|
} finally {
|
|
setThreadPolicy(oldPolicy);
|
|
}
|
|
}
|
|
|
|
if (addedPaths != null && addedPaths.size() > 0) {
|
|
final String add = TextUtils.join(File.pathSeparator, addedPaths);
|
|
ApplicationLoaders.getDefault().addPath(mDefaultClassLoader, add);
|
|
// Setup the new code paths for profiling.
|
|
registerAppInfoToArt = true;
|
|
}
|
|
|
|
// Setup jit profile support.
|
|
//
|
|
// It is ok to call this multiple times if the application gets updated with new splits.
|
|
// The runtime only keeps track of unique code paths and can handle re-registration of
|
|
// the same code path. There's no need to pass `addedPaths` since any new code paths
|
|
// are already in `mApplicationInfo`.
|
|
//
|
|
// It is NOT ok to call this function from the system_server (for any of the packages it
|
|
// loads code from) so we explicitly disallow it there.
|
|
//
|
|
// It is not ok to call this in a zygote context where mActivityThread is null.
|
|
if (registerAppInfoToArt && !ActivityThread.isSystem() && mActivityThread != null) {
|
|
registerAppInfoToArt();
|
|
}
|
|
|
|
// Call AppComponentFactory to select/create the main class loader of this app.
|
|
// Since this may call code in the app, mDefaultClassLoader must be fully set up
|
|
// before invoking the factory.
|
|
// Invoke with a copy of ApplicationInfo to protect against the app changing it.
|
|
if (mClassLoader == null) {
|
|
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
|
|
new ApplicationInfo(mApplicationInfo));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return whether we can access the package's private data directory in order to be able to
|
|
* load code from it.
|
|
*/
|
|
private boolean canAccessDataDir() {
|
|
// In a zygote context where mActivityThread is null we can't access the app data dir.
|
|
if (mActivityThread == null) {
|
|
return false;
|
|
}
|
|
|
|
// A package can access its own data directory (the common case, so short-circuit it).
|
|
if (Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
|
|
return true;
|
|
}
|
|
|
|
if (mDataDir == null) {
|
|
return false;
|
|
}
|
|
|
|
// Temporarily disable logging of disk reads on the Looper thread as this is necessary -
|
|
// and the loader will access the directory anyway if we don't check it.
|
|
StrictMode.ThreadPolicy oldThreadPolicy = allowThreadDiskReads();
|
|
|
|
// Also disable logging of access to /data/user before CE storage is unlocked. The check
|
|
// below will return false (because the directory name we pass will not match the
|
|
// encrypted one), but that's correct.
|
|
StrictMode.VmPolicy oldVmPolicy = allowVmViolations();
|
|
|
|
try {
|
|
// We are constructing a classloader for a different package. It is likely,
|
|
// but not certain, that we can't acccess its app data dir - so check.
|
|
return new File(mDataDir).canExecute();
|
|
} finally {
|
|
setThreadPolicy(oldThreadPolicy);
|
|
setVmPolicy(oldVmPolicy);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public ClassLoader getClassLoader() {
|
|
synchronized (mLock) {
|
|
if (mClassLoader == null) {
|
|
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
|
|
}
|
|
return mClassLoader;
|
|
}
|
|
}
|
|
|
|
private void registerAppInfoToArt() {
|
|
// Only set up profile support if the loaded apk has the same uid as the
|
|
// current process.
|
|
// Currently, we do not support profiling across different apps.
|
|
// (e.g. application's uid might be different when the code is
|
|
// loaded by another app via createApplicationContext)
|
|
if (mApplicationInfo.uid != Process.myUid()) {
|
|
return;
|
|
}
|
|
|
|
final List<String> codePaths = new ArrayList<>();
|
|
if ((mApplicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
|
|
codePaths.add(mApplicationInfo.sourceDir);
|
|
}
|
|
if (mApplicationInfo.splitSourceDirs != null) {
|
|
Collections.addAll(codePaths, mApplicationInfo.splitSourceDirs);
|
|
}
|
|
|
|
if (codePaths.isEmpty()) {
|
|
// If there are no code paths there's no need to setup a profile file and register with
|
|
// the runtime,
|
|
return;
|
|
}
|
|
|
|
for (int i = codePaths.size() - 1; i >= 0; i--) {
|
|
String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1];
|
|
String curProfileFile = ArtManager.getCurrentProfilePath(
|
|
mPackageName, UserHandle.myUserId(), splitName);
|
|
String refProfileFile = ArtManager.getReferenceProfilePath(
|
|
mPackageName, UserHandle.myUserId(), splitName);
|
|
int codePathType = codePaths.get(i).equals(mApplicationInfo.sourceDir)
|
|
? VMRuntime.CODE_PATH_TYPE_PRIMARY_APK
|
|
: VMRuntime.CODE_PATH_TYPE_SPLIT_APK;
|
|
VMRuntime.registerAppInfo(
|
|
mPackageName,
|
|
curProfileFile,
|
|
refProfileFile,
|
|
new String[] {codePaths.get(i)},
|
|
codePathType);
|
|
}
|
|
|
|
// Register the app data directory with the reporter. It will
|
|
// help deciding whether or not a dex file is the primary apk or a
|
|
// secondary dex.
|
|
DexLoadReporter.getInstance().registerAppDataDir(mPackageName, mDataDir);
|
|
}
|
|
|
|
/**
|
|
* Setup value for Thread.getContextClassLoader(). If the
|
|
* package will not run in in a VM with other packages, we set
|
|
* the Java context ClassLoader to the
|
|
* PackageInfo.getClassLoader value. However, if this VM can
|
|
* contain multiple packages, we intead set the Java context
|
|
* ClassLoader to a proxy that will warn about the use of Java
|
|
* context ClassLoaders and then fall through to use the
|
|
* system ClassLoader.
|
|
*
|
|
* <p> Note that this is similar to but not the same as the
|
|
* android.content.Context.getClassLoader(). While both
|
|
* context class loaders are typically set to the
|
|
* PathClassLoader used to load the package archive in the
|
|
* single application per VM case, a single Android process
|
|
* may contain several Contexts executing on one thread with
|
|
* their own logical ClassLoaders while the Java context
|
|
* ClassLoader is a thread local. This is why in the case when
|
|
* we have multiple packages per VM we do not set the Java
|
|
* context ClassLoader to an arbitrary but instead warn the
|
|
* user to set their own if we detect that they are using a
|
|
* Java library that expects it to be set.
|
|
*/
|
|
private void initializeJavaContextClassLoader() {
|
|
IPackageManager pm = ActivityThread.getPackageManager();
|
|
android.content.pm.PackageInfo pi =
|
|
PackageManager.getPackageInfoAsUserCached(
|
|
mPackageName,
|
|
PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
|
|
UserHandle.myUserId());
|
|
if (pi == null) {
|
|
throw new IllegalStateException("Unable to get package info for "
|
|
+ mPackageName + "; is package not installed?");
|
|
}
|
|
/*
|
|
* Two possible indications that this package could be
|
|
* sharing its virtual machine with other packages:
|
|
*
|
|
* 1.) the sharedUserId attribute is set in the manifest,
|
|
* indicating a request to share a VM with other
|
|
* packages with the same sharedUserId.
|
|
*
|
|
* 2.) the application element of the manifest has an
|
|
* attribute specifying a non-default process name,
|
|
* indicating the desire to run in another packages VM.
|
|
*/
|
|
boolean sharedUserIdSet = (pi.sharedUserId != null);
|
|
boolean processNameNotDefault =
|
|
(pi.applicationInfo != null &&
|
|
!mPackageName.equals(pi.applicationInfo.processName));
|
|
boolean sharable = (sharedUserIdSet || processNameNotDefault);
|
|
ClassLoader contextClassLoader =
|
|
(sharable)
|
|
? new WarningContextClassLoader()
|
|
: mClassLoader;
|
|
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
|
}
|
|
|
|
private static class WarningContextClassLoader extends ClassLoader {
|
|
|
|
private static boolean warned = false;
|
|
|
|
private void warn(String methodName) {
|
|
if (warned) {
|
|
return;
|
|
}
|
|
warned = true;
|
|
Thread.currentThread().setContextClassLoader(getParent());
|
|
Slog.w(ActivityThread.TAG, "ClassLoader." + methodName + ": " +
|
|
"The class loader returned by " +
|
|
"Thread.getContextClassLoader() may fail for processes " +
|
|
"that host multiple applications. You should explicitly " +
|
|
"specify a context class loader. For example: " +
|
|
"Thread.setContextClassLoader(getClass().getClassLoader());");
|
|
}
|
|
|
|
@Override public URL getResource(String resName) {
|
|
warn("getResource");
|
|
return getParent().getResource(resName);
|
|
}
|
|
|
|
@Override public Enumeration<URL> getResources(String resName) throws IOException {
|
|
warn("getResources");
|
|
return getParent().getResources(resName);
|
|
}
|
|
|
|
@Override public InputStream getResourceAsStream(String resName) {
|
|
warn("getResourceAsStream");
|
|
return getParent().getResourceAsStream(resName);
|
|
}
|
|
|
|
@Override public Class<?> loadClass(String className) throws ClassNotFoundException {
|
|
warn("loadClass");
|
|
return getParent().loadClass(className);
|
|
}
|
|
|
|
@Override public void setClassAssertionStatus(String cname, boolean enable) {
|
|
warn("setClassAssertionStatus");
|
|
getParent().setClassAssertionStatus(cname, enable);
|
|
}
|
|
|
|
@Override public void setPackageAssertionStatus(String pname, boolean enable) {
|
|
warn("setPackageAssertionStatus");
|
|
getParent().setPackageAssertionStatus(pname, enable);
|
|
}
|
|
|
|
@Override public void setDefaultAssertionStatus(boolean enable) {
|
|
warn("setDefaultAssertionStatus");
|
|
getParent().setDefaultAssertionStatus(enable);
|
|
}
|
|
|
|
@Override public void clearAssertionStatus() {
|
|
warn("clearAssertionStatus");
|
|
getParent().clearAssertionStatus();
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public String getAppDir() {
|
|
return mAppDir;
|
|
}
|
|
|
|
public String getLibDir() {
|
|
return mLibDir;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public String getResDir() {
|
|
return mResDir;
|
|
}
|
|
|
|
public String[] getSplitAppDirs() {
|
|
return mSplitAppDirs;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public String[] getSplitResDirs() {
|
|
return mSplitResDirs;
|
|
}
|
|
|
|
/**
|
|
* Corresponds to {@link ApplicationInfo#resourceDirs}.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public String[] getOverlayDirs() {
|
|
return mLegacyOverlayDirs;
|
|
}
|
|
|
|
/**
|
|
* Corresponds to {@link ApplicationInfo#overlayPaths}.
|
|
*/
|
|
public String[] getOverlayPaths() {
|
|
return mOverlayPaths;
|
|
}
|
|
|
|
public String getDataDir() {
|
|
return mDataDir;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public File getDataDirFile() {
|
|
return mDataDirFile;
|
|
}
|
|
|
|
public File getDeviceProtectedDataDirFile() {
|
|
return mDeviceProtectedDataDirFile;
|
|
}
|
|
|
|
public File getCredentialProtectedDataDirFile() {
|
|
return mCredentialProtectedDataDirFile;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public AssetManager getAssets() {
|
|
return getResources().getAssets();
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public Resources getResources() {
|
|
if (mResources == null) {
|
|
final String[] splitPaths;
|
|
try {
|
|
splitPaths = getSplitPaths(null);
|
|
} catch (NameNotFoundException e) {
|
|
// This should never fail.
|
|
throw new AssertionError("null split not found");
|
|
}
|
|
|
|
if (Process.myUid() == mApplicationInfo.uid) {
|
|
ResourcesManager.getInstance().initializeApplicationPaths(mResDir, splitPaths);
|
|
}
|
|
|
|
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
|
|
splitPaths, mLegacyOverlayDirs, mOverlayPaths,
|
|
mApplicationInfo.sharedLibraryFiles, null, null, getCompatibilityInfo(),
|
|
getClassLoader(), null);
|
|
}
|
|
return mResources;
|
|
}
|
|
|
|
/**
|
|
* This is for 3p apps accessing this hidden API directly... in which case, we don't return
|
|
* the cached Application instance.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public Application makeApplication(boolean forceDefaultAppClass,
|
|
Instrumentation instrumentation) {
|
|
return makeApplicationInner(forceDefaultAppClass, instrumentation,
|
|
/* allowDuplicateInstances= */ true);
|
|
}
|
|
|
|
/**
|
|
* This is for all the (internal) callers, for which we do return the cached instance.
|
|
*/
|
|
public Application makeApplicationInner(boolean forceDefaultAppClass,
|
|
Instrumentation instrumentation) {
|
|
return makeApplicationInner(forceDefaultAppClass, instrumentation,
|
|
/* allowDuplicateInstances= */ false);
|
|
}
|
|
|
|
private Application makeApplicationInner(boolean forceDefaultAppClass,
|
|
Instrumentation instrumentation, boolean allowDuplicateInstances) {
|
|
if (mApplication != null) {
|
|
return mApplication;
|
|
}
|
|
|
|
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
|
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
|
|
}
|
|
|
|
try {
|
|
synchronized (sApplications) {
|
|
final Application cached = sApplications.get(mPackageName);
|
|
if (cached != null) {
|
|
// Looks like this is always happening for the system server, because
|
|
// the LoadedApk created in systemMain() -> attach() isn't cached properly?
|
|
if (!"android".equals(mPackageName)) {
|
|
Slog.wtfStack(TAG, "App instance already created for package="
|
|
+ mPackageName + " instance=" + cached);
|
|
}
|
|
if (!allowDuplicateInstances) {
|
|
mApplication = cached;
|
|
return cached;
|
|
}
|
|
// Some apps intentionally call makeApplication() to create a new Application
|
|
// instance... Sigh...
|
|
}
|
|
}
|
|
|
|
Application app = null;
|
|
|
|
final String myProcessName = Process.myProcessName();
|
|
String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
|
|
myProcessName);
|
|
if (forceDefaultAppClass || (appClass == null)) {
|
|
appClass = "android.app.Application";
|
|
}
|
|
|
|
try {
|
|
final java.lang.ClassLoader cl = getClassLoader();
|
|
if (!mPackageName.equals("android")) {
|
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
|
|
"initializeJavaContextClassLoader");
|
|
initializeJavaContextClassLoader();
|
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
|
}
|
|
|
|
// Rewrite the R 'constants' for all library apks.
|
|
SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(
|
|
false, false);
|
|
for (int i = 0, n = packageIdentifiers.size(); i < n; i++) {
|
|
final int id = packageIdentifiers.keyAt(i);
|
|
if (id == 0x01 || id == 0x7f) {
|
|
continue;
|
|
}
|
|
|
|
rewriteRValues(cl, packageIdentifiers.valueAt(i), id);
|
|
}
|
|
|
|
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
|
|
// The network security config needs to be aware of multiple
|
|
// applications in the same process to handle discrepancies
|
|
NetworkSecurityConfigProvider.handleNewApplication(appContext);
|
|
app = mActivityThread.mInstrumentation.newApplication(
|
|
cl, appClass, appContext);
|
|
appContext.setOuterContext(app);
|
|
} catch (Exception e) {
|
|
if (!mActivityThread.mInstrumentation.onException(app, e)) {
|
|
throw new RuntimeException(
|
|
"Unable to instantiate application " + appClass
|
|
+ " package " + mPackageName + ": " + e.toString(), e);
|
|
}
|
|
}
|
|
mActivityThread.mAllApplications.add(app);
|
|
mApplication = app;
|
|
if (!allowDuplicateInstances) {
|
|
synchronized (sApplications) {
|
|
sApplications.put(mPackageName, app);
|
|
}
|
|
}
|
|
|
|
if (instrumentation != null) {
|
|
try {
|
|
instrumentation.callApplicationOnCreate(app);
|
|
} catch (Exception e) {
|
|
if (!instrumentation.onException(app, e)) {
|
|
throw new RuntimeException(
|
|
"Unable to create application " + app.getClass().getName()
|
|
+ ": " + e.toString(), e);
|
|
}
|
|
}
|
|
}
|
|
|
|
return app;
|
|
} finally {
|
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
private void rewriteRValues(ClassLoader cl, String packageName, int id) {
|
|
final Class<?> rClazz;
|
|
try {
|
|
rClazz = cl.loadClass(packageName + ".R");
|
|
} catch (ClassNotFoundException e) {
|
|
// This is not necessarily an error, as some packages do not ship with resources
|
|
// (or they do not need rewriting).
|
|
Log.i(TAG, "No resource references to update in package " + packageName);
|
|
return;
|
|
}
|
|
|
|
final Method callback;
|
|
try {
|
|
callback = rClazz.getMethod("onResourcesLoaded", int.class);
|
|
} catch (NoSuchMethodException e) {
|
|
// No rewriting to be done.
|
|
return;
|
|
}
|
|
|
|
Throwable cause;
|
|
try {
|
|
callback.invoke(null, id);
|
|
return;
|
|
} catch (IllegalAccessException e) {
|
|
cause = e;
|
|
} catch (InvocationTargetException e) {
|
|
cause = e.getCause();
|
|
}
|
|
|
|
throw new RuntimeException("Failed to rewrite resource references for " + packageName,
|
|
cause);
|
|
}
|
|
|
|
public void removeContextRegistrations(Context context,
|
|
String who, String what) {
|
|
final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled();
|
|
synchronized (mReceivers) {
|
|
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap =
|
|
mReceivers.remove(context);
|
|
if (rmap != null) {
|
|
for (int i = 0; i < rmap.size(); i++) {
|
|
LoadedApk.ReceiverDispatcher rd = rmap.valueAt(i);
|
|
IntentReceiverLeaked leak = new IntentReceiverLeaked(
|
|
what + " " + who + " has leaked IntentReceiver "
|
|
+ rd.getIntentReceiver() + " that was " +
|
|
"originally registered here. Are you missing a " +
|
|
"call to unregisterReceiver()?");
|
|
leak.setStackTrace(rd.getLocation().getStackTrace());
|
|
Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
|
|
if (reportRegistrationLeaks) {
|
|
StrictMode.onIntentReceiverLeaked(leak);
|
|
}
|
|
try {
|
|
ActivityManager.getService().unregisterReceiver(
|
|
rd.getIIntentReceiver());
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
}
|
|
mUnregisteredReceivers.remove(context);
|
|
}
|
|
|
|
synchronized (mServices) {
|
|
//Slog.i(TAG, "Receiver registrations: " + mReceivers);
|
|
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap =
|
|
mServices.remove(context);
|
|
if (smap != null) {
|
|
for (int i = 0; i < smap.size(); i++) {
|
|
LoadedApk.ServiceDispatcher sd = smap.valueAt(i);
|
|
ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
|
|
what + " " + who + " has leaked ServiceConnection "
|
|
+ sd.getServiceConnection() + " that was originally bound here");
|
|
leak.setStackTrace(sd.getLocation().getStackTrace());
|
|
Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
|
|
if (reportRegistrationLeaks) {
|
|
StrictMode.onServiceConnectionLeaked(leak);
|
|
}
|
|
try {
|
|
ActivityManager.getService().unbindService(
|
|
sd.getIServiceConnection());
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
sd.doForget();
|
|
}
|
|
}
|
|
mUnboundServices.remove(context);
|
|
//Slog.i(TAG, "Service registrations: " + mServices);
|
|
}
|
|
}
|
|
|
|
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
|
|
Context context, Handler handler,
|
|
Instrumentation instrumentation, boolean registered) {
|
|
synchronized (mReceivers) {
|
|
LoadedApk.ReceiverDispatcher rd = null;
|
|
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
|
|
if (registered) {
|
|
map = mReceivers.get(context);
|
|
if (map != null) {
|
|
rd = map.get(r);
|
|
}
|
|
}
|
|
if (rd == null) {
|
|
rd = new ReceiverDispatcher(mActivityThread.getApplicationThread(), r, context,
|
|
handler, instrumentation, registered);
|
|
if (registered) {
|
|
if (map == null) {
|
|
map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
|
|
mReceivers.put(context, map);
|
|
}
|
|
map.put(r, rd);
|
|
}
|
|
} else {
|
|
rd.validate(context, handler);
|
|
}
|
|
rd.mForgotten = false;
|
|
return rd.getIIntentReceiver();
|
|
}
|
|
}
|
|
|
|
public IIntentReceiver forgetReceiverDispatcher(Context context,
|
|
BroadcastReceiver r) {
|
|
synchronized (mReceivers) {
|
|
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = mReceivers.get(context);
|
|
LoadedApk.ReceiverDispatcher rd = null;
|
|
if (map != null) {
|
|
rd = map.get(r);
|
|
if (rd != null) {
|
|
map.remove(r);
|
|
if (map.size() == 0) {
|
|
mReceivers.remove(context);
|
|
}
|
|
if (r.getDebugUnregister()) {
|
|
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
|
|
= mUnregisteredReceivers.get(context);
|
|
if (holder == null) {
|
|
holder = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
|
|
mUnregisteredReceivers.put(context, holder);
|
|
}
|
|
RuntimeException ex = new IllegalArgumentException(
|
|
"Originally unregistered here:");
|
|
ex.fillInStackTrace();
|
|
rd.setUnregisterLocation(ex);
|
|
holder.put(r, rd);
|
|
}
|
|
rd.mForgotten = true;
|
|
return rd.getIIntentReceiver();
|
|
}
|
|
}
|
|
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
|
|
= mUnregisteredReceivers.get(context);
|
|
if (holder != null) {
|
|
rd = holder.get(r);
|
|
if (rd != null) {
|
|
RuntimeException ex = rd.getUnregisterLocation();
|
|
throw new IllegalArgumentException(
|
|
"Unregistering Receiver " + r
|
|
+ " that was already unregistered", ex);
|
|
}
|
|
}
|
|
if (context == null) {
|
|
throw new IllegalStateException("Unbinding Receiver " + r
|
|
+ " from Context that is no longer in use: " + context);
|
|
} else {
|
|
throw new IllegalArgumentException("Receiver not registered: " + r);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static final class ReceiverDispatcher {
|
|
|
|
final static class InnerReceiver extends IIntentReceiver.Stub {
|
|
final IApplicationThread mApplicationThread;
|
|
final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
|
|
final LoadedApk.ReceiverDispatcher mStrongRef;
|
|
|
|
InnerReceiver(IApplicationThread thread, LoadedApk.ReceiverDispatcher rd,
|
|
boolean strong) {
|
|
mApplicationThread = thread;
|
|
mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
|
|
mStrongRef = strong ? rd : null;
|
|
}
|
|
|
|
@Override
|
|
public void performReceive(Intent intent, int resultCode, String data,
|
|
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
|
|
Log.wtf(TAG, "performReceive() called targeting raw IIntentReceiver for " + intent);
|
|
performReceive(intent, resultCode, data, extras, ordered, sticky,
|
|
BroadcastReceiver.PendingResult.guessAssumeDelivered(
|
|
BroadcastReceiver.PendingResult.TYPE_REGISTERED, ordered),
|
|
sendingUser, /*sendingUid=*/ Process.INVALID_UID,
|
|
/*sendingPackage=*/ null);
|
|
}
|
|
|
|
public void performReceive(Intent intent, int resultCode, String data,
|
|
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
|
|
int sendingUser, int sendingUid, String sendingPackage) {
|
|
final LoadedApk.ReceiverDispatcher rd;
|
|
if (intent == null) {
|
|
Log.wtf(TAG, "Null intent received");
|
|
rd = null;
|
|
} else {
|
|
rd = mDispatcher.get();
|
|
}
|
|
if (ActivityThread.DEBUG_BROADCAST) {
|
|
int seq = intent.getIntExtra("seq", -1);
|
|
Slog.i(ActivityThread.TAG, "Receiving broadcast " + intent.getAction()
|
|
+ " seq=" + seq + " to " + (rd != null ? rd.mReceiver : null));
|
|
}
|
|
if (rd != null) {
|
|
rd.performReceive(intent, resultCode, data, extras,
|
|
ordered, sticky, assumeDelivered, sendingUser,
|
|
sendingUid, sendingPackage);
|
|
} else if (!assumeDelivered) {
|
|
// The activity manager dispatched a broadcast to a registered
|
|
// receiver in this process, but before it could be delivered the
|
|
// receiver was unregistered. Acknowledge the broadcast on its
|
|
// behalf so that the system's broadcast sequence can continue.
|
|
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
|
|
"Finishing broadcast to unregistered receiver");
|
|
IActivityManager mgr = ActivityManager.getService();
|
|
try {
|
|
if (extras != null) {
|
|
extras.setAllowFds(false);
|
|
}
|
|
mgr.finishReceiver(mApplicationThread.asBinder(), resultCode, data,
|
|
extras, false, intent.getFlags());
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final IApplicationThread mAppThread;
|
|
final IIntentReceiver.Stub mIIntentReceiver;
|
|
@UnsupportedAppUsage
|
|
final BroadcastReceiver mReceiver;
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
final Context mContext;
|
|
final Handler mActivityThread;
|
|
final Instrumentation mInstrumentation;
|
|
final boolean mRegistered;
|
|
final IntentReceiverLeaked mLocation;
|
|
RuntimeException mUnregisterLocation;
|
|
boolean mForgotten;
|
|
|
|
final class Args extends BroadcastReceiver.PendingResult {
|
|
private Intent mCurIntent;
|
|
private boolean mDispatched;
|
|
private boolean mRunCalled;
|
|
|
|
public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
|
|
boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser,
|
|
int sendingUid, String sendingPackage) {
|
|
super(resultCode, resultData, resultExtras,
|
|
mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
|
|
sticky, assumeDelivered, mAppThread.asBinder(), sendingUser,
|
|
intent.getFlags(), sendingUid, sendingPackage);
|
|
mCurIntent = intent;
|
|
}
|
|
|
|
public final Runnable getRunnable() {
|
|
return () -> {
|
|
final BroadcastReceiver receiver = mReceiver;
|
|
|
|
if (ActivityThread.DEBUG_BROADCAST) {
|
|
int seq = mCurIntent.getIntExtra("seq", -1);
|
|
Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
|
|
+ " seq=" + seq + " to " + mReceiver);
|
|
}
|
|
|
|
final IActivityManager mgr = ActivityManager.getService();
|
|
final Intent intent = mCurIntent;
|
|
if (intent == null) {
|
|
Log.wtf(TAG, "Null intent being dispatched, mDispatched=" + mDispatched
|
|
+ (mRunCalled ? ", run() has already been called" : ""));
|
|
}
|
|
|
|
mCurIntent = null;
|
|
mDispatched = true;
|
|
mRunCalled = true;
|
|
if (receiver == null || intent == null || mForgotten) {
|
|
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
|
|
"Finishing null broadcast to " + mReceiver);
|
|
sendFinished(mgr);
|
|
return;
|
|
}
|
|
|
|
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
|
|
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
|
|
"broadcastReceiveReg: " + intent.getAction());
|
|
}
|
|
|
|
try {
|
|
ClassLoader cl = mReceiver.getClass().getClassLoader();
|
|
intent.setExtrasClassLoader(cl);
|
|
// TODO: determine at registration time if caller is
|
|
// protecting themselves with signature permission
|
|
intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent),
|
|
mContext.getAttributionSource());
|
|
setExtrasClassLoader(cl);
|
|
receiver.setPendingResult(this);
|
|
receiver.onReceive(mContext, intent);
|
|
} catch (Exception e) {
|
|
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
|
|
"Finishing failed broadcast to " + mReceiver);
|
|
sendFinished(mgr);
|
|
if (mInstrumentation == null ||
|
|
!mInstrumentation.onException(mReceiver, e)) {
|
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
|
throw new RuntimeException(
|
|
"Error receiving broadcast " + intent
|
|
+ " in " + mReceiver, e);
|
|
}
|
|
}
|
|
|
|
if (receiver.getPendingResult() != null) {
|
|
finish();
|
|
}
|
|
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
|
|
};
|
|
}
|
|
}
|
|
|
|
ReceiverDispatcher(IApplicationThread appThread, BroadcastReceiver receiver,
|
|
Context context, Handler activityThread, Instrumentation instrumentation,
|
|
boolean registered) {
|
|
if (activityThread == null) {
|
|
throw new NullPointerException("Handler must not be null");
|
|
}
|
|
|
|
mAppThread = appThread;
|
|
mIIntentReceiver = new InnerReceiver(mAppThread, this, !registered);
|
|
mReceiver = receiver;
|
|
mContext = context;
|
|
mActivityThread = activityThread;
|
|
mInstrumentation = instrumentation;
|
|
mRegistered = registered;
|
|
mLocation = new IntentReceiverLeaked(null);
|
|
mLocation.fillInStackTrace();
|
|
}
|
|
|
|
void validate(Context context, Handler activityThread) {
|
|
if (mContext != context) {
|
|
throw new IllegalStateException(
|
|
"Receiver " + mReceiver +
|
|
" registered with differing Context (was " +
|
|
mContext + " now " + context + ")");
|
|
}
|
|
if (mActivityThread != activityThread) {
|
|
throw new IllegalStateException(
|
|
"Receiver " + mReceiver +
|
|
" registered with differing handler (was " +
|
|
mActivityThread + " now " + activityThread + ")");
|
|
}
|
|
}
|
|
|
|
IntentReceiverLeaked getLocation() {
|
|
return mLocation;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
BroadcastReceiver getIntentReceiver() {
|
|
return mReceiver;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
IIntentReceiver getIIntentReceiver() {
|
|
return mIIntentReceiver;
|
|
}
|
|
|
|
void setUnregisterLocation(RuntimeException ex) {
|
|
mUnregisterLocation = ex;
|
|
}
|
|
|
|
RuntimeException getUnregisterLocation() {
|
|
return mUnregisterLocation;
|
|
}
|
|
|
|
public void performReceive(Intent intent, int resultCode, String data,
|
|
Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
|
|
int sendingUser, int sendingUid, String sendingPackage) {
|
|
final Args args = new Args(intent, resultCode, data, extras, ordered,
|
|
sticky, assumeDelivered, sendingUser, sendingUid, sendingPackage);
|
|
if (intent == null) {
|
|
Log.wtf(TAG, "Null intent received");
|
|
} else {
|
|
if (ActivityThread.DEBUG_BROADCAST) {
|
|
int seq = intent.getIntExtra("seq", -1);
|
|
Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction()
|
|
+ " seq=" + seq + " to " + mReceiver);
|
|
}
|
|
}
|
|
if (intent == null || !mActivityThread.post(args.getRunnable())) {
|
|
IActivityManager mgr = ActivityManager.getService();
|
|
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
|
|
"Finishing sync broadcast to " + mReceiver);
|
|
args.sendFinished(mgr);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
public final IServiceConnection getServiceDispatcher(ServiceConnection c,
|
|
Context context, Handler handler, long flags) {
|
|
return getServiceDispatcherCommon(c, context, handler, null, flags);
|
|
}
|
|
|
|
public final IServiceConnection getServiceDispatcher(ServiceConnection c,
|
|
Context context, Executor executor, long flags) {
|
|
return getServiceDispatcherCommon(c, context, null, executor, flags);
|
|
}
|
|
|
|
private IServiceConnection getServiceDispatcherCommon(ServiceConnection c,
|
|
Context context, Handler handler, Executor executor, long flags) {
|
|
synchronized (mServices) {
|
|
LoadedApk.ServiceDispatcher sd = null;
|
|
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
|
|
if (map != null) {
|
|
if (DEBUG) Slog.d(TAG, "Returning existing dispatcher " + sd + " for conn " + c);
|
|
sd = map.get(c);
|
|
}
|
|
if (sd == null) {
|
|
if (executor != null) {
|
|
sd = new ServiceDispatcher(c, context, executor, flags);
|
|
} else {
|
|
sd = new ServiceDispatcher(c, context, handler, flags);
|
|
}
|
|
if (DEBUG) Slog.d(TAG, "Creating new dispatcher " + sd + " for conn " + c);
|
|
if (map == null) {
|
|
map = new ArrayMap<>();
|
|
mServices.put(context, map);
|
|
}
|
|
map.put(c, sd);
|
|
} else {
|
|
sd.validate(context, handler, executor);
|
|
}
|
|
return sd.getIServiceConnection();
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public IServiceConnection lookupServiceDispatcher(ServiceConnection c,
|
|
Context context) {
|
|
synchronized (mServices) {
|
|
LoadedApk.ServiceDispatcher sd = null;
|
|
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
|
|
if (map != null) {
|
|
sd = map.get(c);
|
|
}
|
|
return sd != null ? sd.getIServiceConnection() : null;
|
|
}
|
|
}
|
|
|
|
public final IServiceConnection forgetServiceDispatcher(Context context,
|
|
ServiceConnection c) {
|
|
synchronized (mServices) {
|
|
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map
|
|
= mServices.get(context);
|
|
LoadedApk.ServiceDispatcher sd = null;
|
|
if (map != null) {
|
|
sd = map.get(c);
|
|
if (sd != null) {
|
|
if (DEBUG) Slog.d(TAG, "Removing dispatcher " + sd + " for conn " + c);
|
|
map.remove(c);
|
|
sd.doForget();
|
|
if (map.size() == 0) {
|
|
mServices.remove(context);
|
|
}
|
|
if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) {
|
|
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
|
|
= mUnboundServices.get(context);
|
|
if (holder == null) {
|
|
holder = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
|
|
mUnboundServices.put(context, holder);
|
|
}
|
|
RuntimeException ex = new IllegalArgumentException(
|
|
"Originally unbound here:");
|
|
ex.fillInStackTrace();
|
|
sd.setUnbindLocation(ex);
|
|
holder.put(c, sd);
|
|
}
|
|
return sd.getIServiceConnection();
|
|
}
|
|
}
|
|
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
|
|
= mUnboundServices.get(context);
|
|
if (holder != null) {
|
|
sd = holder.get(c);
|
|
if (sd != null) {
|
|
RuntimeException ex = sd.getUnbindLocation();
|
|
throw new IllegalArgumentException(
|
|
"Unbinding Service " + c
|
|
+ " that was already unbound", ex);
|
|
}
|
|
}
|
|
if (context == null) {
|
|
throw new IllegalStateException("Unbinding Service " + c
|
|
+ " from Context that is no longer in use: " + context);
|
|
} else {
|
|
throw new IllegalArgumentException("Service not registered: " + c);
|
|
}
|
|
}
|
|
}
|
|
|
|
static final class ServiceDispatcher {
|
|
private final ServiceDispatcher.InnerConnection mIServiceConnection;
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
private final ServiceConnection mConnection;
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
|
private final Context mContext;
|
|
private final Handler mActivityThread;
|
|
private final Executor mActivityExecutor;
|
|
private final ServiceConnectionLeaked mLocation;
|
|
private final long mFlags;
|
|
|
|
private RuntimeException mUnbindLocation;
|
|
|
|
private boolean mForgotten;
|
|
|
|
private static class ConnectionInfo {
|
|
IBinder binder;
|
|
IBinder.DeathRecipient deathMonitor;
|
|
}
|
|
|
|
private static class InnerConnection extends IServiceConnection.Stub {
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;
|
|
|
|
InnerConnection(LoadedApk.ServiceDispatcher sd) {
|
|
mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
|
|
}
|
|
|
|
public void connected(ComponentName name, IBinder service, boolean dead)
|
|
throws RemoteException {
|
|
LoadedApk.ServiceDispatcher sd = mDispatcher.get();
|
|
if (sd != null) {
|
|
sd.connected(name, service, dead);
|
|
}
|
|
}
|
|
}
|
|
|
|
private final ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo> mActiveConnections
|
|
= new ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo>();
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
ServiceDispatcher(ServiceConnection conn,
|
|
Context context, Handler activityThread, long flags) {
|
|
mIServiceConnection = new InnerConnection(this);
|
|
mConnection = conn;
|
|
mContext = context;
|
|
mActivityThread = activityThread;
|
|
mActivityExecutor = null;
|
|
mLocation = new ServiceConnectionLeaked(null);
|
|
mLocation.fillInStackTrace();
|
|
mFlags = flags;
|
|
}
|
|
|
|
ServiceDispatcher(ServiceConnection conn,
|
|
Context context, Executor activityExecutor, long flags) {
|
|
mIServiceConnection = new InnerConnection(this);
|
|
mConnection = conn;
|
|
mContext = context;
|
|
mActivityThread = null;
|
|
mActivityExecutor = activityExecutor;
|
|
mLocation = new ServiceConnectionLeaked(null);
|
|
mLocation.fillInStackTrace();
|
|
mFlags = flags;
|
|
}
|
|
|
|
void validate(Context context, Handler activityThread, Executor activityExecutor) {
|
|
if (mContext != context) {
|
|
throw new RuntimeException(
|
|
"ServiceConnection " + mConnection +
|
|
" registered with differing Context (was " +
|
|
mContext + " now " + context + ")");
|
|
}
|
|
if (mActivityThread != activityThread) {
|
|
throw new RuntimeException(
|
|
"ServiceConnection " + mConnection +
|
|
" registered with differing handler (was " +
|
|
mActivityThread + " now " + activityThread + ")");
|
|
}
|
|
if (mActivityExecutor != activityExecutor) {
|
|
throw new RuntimeException(
|
|
"ServiceConnection " + mConnection +
|
|
" registered with differing executor (was " +
|
|
mActivityExecutor + " now " + activityExecutor + ")");
|
|
}
|
|
}
|
|
|
|
void doForget() {
|
|
synchronized(this) {
|
|
for (int i=0; i<mActiveConnections.size(); i++) {
|
|
ServiceDispatcher.ConnectionInfo ci = mActiveConnections.valueAt(i);
|
|
ci.binder.unlinkToDeath(ci.deathMonitor, 0);
|
|
}
|
|
mActiveConnections.clear();
|
|
mForgotten = true;
|
|
}
|
|
}
|
|
|
|
ServiceConnectionLeaked getLocation() {
|
|
return mLocation;
|
|
}
|
|
|
|
ServiceConnection getServiceConnection() {
|
|
return mConnection;
|
|
}
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
IServiceConnection getIServiceConnection() {
|
|
return mIServiceConnection;
|
|
}
|
|
|
|
long getFlags() {
|
|
return mFlags;
|
|
}
|
|
|
|
void setUnbindLocation(RuntimeException ex) {
|
|
mUnbindLocation = ex;
|
|
}
|
|
|
|
RuntimeException getUnbindLocation() {
|
|
return mUnbindLocation;
|
|
}
|
|
|
|
public void connected(ComponentName name, IBinder service, boolean dead) {
|
|
if (mActivityExecutor != null) {
|
|
mActivityExecutor.execute(new RunConnection(name, service, 0, dead));
|
|
} else if (mActivityThread != null) {
|
|
mActivityThread.post(new RunConnection(name, service, 0, dead));
|
|
} else {
|
|
doConnected(name, service, dead);
|
|
}
|
|
}
|
|
|
|
public void death(ComponentName name, IBinder service) {
|
|
if (mActivityExecutor != null) {
|
|
mActivityExecutor.execute(new RunConnection(name, service, 1, false));
|
|
} else if (mActivityThread != null) {
|
|
mActivityThread.post(new RunConnection(name, service, 1, false));
|
|
} else {
|
|
doDeath(name, service);
|
|
}
|
|
}
|
|
|
|
public void doConnected(ComponentName name, IBinder service, boolean dead) {
|
|
ServiceDispatcher.ConnectionInfo old;
|
|
ServiceDispatcher.ConnectionInfo info;
|
|
|
|
synchronized (this) {
|
|
if (mForgotten) {
|
|
// We unbound before receiving the connection; ignore
|
|
// any connection received.
|
|
return;
|
|
}
|
|
old = mActiveConnections.get(name);
|
|
if (old != null && old.binder == service) {
|
|
// Huh, already have this one. Oh well!
|
|
return;
|
|
}
|
|
|
|
if (service != null) {
|
|
// A new service is being connected... set it all up.
|
|
info = new ConnectionInfo();
|
|
info.binder = service;
|
|
info.deathMonitor = new DeathMonitor(name, service);
|
|
try {
|
|
service.linkToDeath(info.deathMonitor, 0);
|
|
mActiveConnections.put(name, info);
|
|
} catch (RemoteException e) {
|
|
// This service was dead before we got it... just
|
|
// don't do anything with it.
|
|
mActiveConnections.remove(name);
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
// The named service is being disconnected... clean up.
|
|
mActiveConnections.remove(name);
|
|
}
|
|
|
|
if (old != null) {
|
|
old.binder.unlinkToDeath(old.deathMonitor, 0);
|
|
}
|
|
}
|
|
|
|
// If there was an old service, it is now disconnected.
|
|
if (old != null) {
|
|
mConnection.onServiceDisconnected(name);
|
|
}
|
|
if (dead) {
|
|
mConnection.onBindingDied(name);
|
|
} else {
|
|
// If there is a new viable service, it is now connected.
|
|
if (service != null) {
|
|
mConnection.onServiceConnected(name, service);
|
|
} else {
|
|
// The binding machinery worked, but the remote returned null from onBind().
|
|
mConnection.onNullBinding(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void doDeath(ComponentName name, IBinder service) {
|
|
synchronized (this) {
|
|
ConnectionInfo old = mActiveConnections.get(name);
|
|
if (old == null || old.binder != service) {
|
|
// Death for someone different than who we last
|
|
// reported... just ignore it.
|
|
return;
|
|
}
|
|
mActiveConnections.remove(name);
|
|
old.binder.unlinkToDeath(old.deathMonitor, 0);
|
|
}
|
|
|
|
mConnection.onServiceDisconnected(name);
|
|
}
|
|
|
|
private final class RunConnection implements Runnable {
|
|
RunConnection(ComponentName name, IBinder service, int command, boolean dead) {
|
|
mName = name;
|
|
mService = service;
|
|
mCommand = command;
|
|
mDead = dead;
|
|
}
|
|
|
|
public void run() {
|
|
if (mCommand == 0) {
|
|
doConnected(mName, mService, mDead);
|
|
} else if (mCommand == 1) {
|
|
doDeath(mName, mService);
|
|
}
|
|
}
|
|
|
|
final ComponentName mName;
|
|
final IBinder mService;
|
|
final int mCommand;
|
|
final boolean mDead;
|
|
}
|
|
|
|
private final class DeathMonitor implements IBinder.DeathRecipient
|
|
{
|
|
DeathMonitor(ComponentName name, IBinder service) {
|
|
mName = name;
|
|
mService = service;
|
|
}
|
|
|
|
public void binderDied() {
|
|
death(mName, mService);
|
|
}
|
|
|
|
final ComponentName mName;
|
|
final IBinder mService;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the Apk paths in the cache are correct, and update them if they are not.
|
|
* @hide
|
|
*/
|
|
public static void checkAndUpdateApkPaths(ApplicationInfo expectedAppInfo) {
|
|
// Get the LoadedApk from the cache
|
|
ActivityThread activityThread = ActivityThread.currentActivityThread();
|
|
if (activityThread == null) {
|
|
Log.e(TAG, "Cannot find activity thread");
|
|
return;
|
|
}
|
|
checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ true);
|
|
checkAndUpdateApkPaths(activityThread, expectedAppInfo, /* cacheWithCode */ false);
|
|
}
|
|
|
|
private static void checkAndUpdateApkPaths(ActivityThread activityThread,
|
|
ApplicationInfo expectedAppInfo, boolean cacheWithCode) {
|
|
String expectedCodePath = expectedAppInfo.getCodePath();
|
|
LoadedApk loadedApk = activityThread.peekPackageInfo(
|
|
expectedAppInfo.packageName, /* includeCode= */ cacheWithCode);
|
|
// If there is load apk cached, or if the cache is valid, don't do anything.
|
|
if (loadedApk == null || loadedApk.getApplicationInfo() == null
|
|
|| loadedApk.getApplicationInfo().getCodePath().equals(expectedCodePath)) {
|
|
return;
|
|
}
|
|
// Duplicate framework logic
|
|
List<String> oldPaths = new ArrayList<>();
|
|
LoadedApk.makePaths(activityThread, expectedAppInfo, oldPaths);
|
|
|
|
// Force update the LoadedApk instance, which should update the reference in the cache
|
|
loadedApk.updateApplicationInfo(expectedAppInfo, oldPaths);
|
|
}
|
|
|
|
}
|