Add SDK 29 sources.

Test: N/A
Change-Id: Iedb7a31029e003928eb16f7e69ed147e72bb6235
diff --git a/android/app/LoadedApk.java b/android/app/LoadedApk.java
new file mode 100644
index 0000000..1ac619c
--- /dev/null
+++ b/android/app/LoadedApk.java
@@ -0,0 +1,2007 @@
+/*
+ * 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.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.FileUtils;
+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.text.TextUtils;
+import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+
+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.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;
+    private static final String PROPERTY_NAME_APPEND_NATIVE = "pi.append_native_lib_paths";
+
+    @UnsupportedAppUsage
+    private final ActivityThread mActivityThread;
+    @UnsupportedAppUsage
+    final String mPackageName;
+    @UnsupportedAppUsage
+    private ApplicationInfo mApplicationInfo;
+    @UnsupportedAppUsage
+    private String mAppDir;
+    @UnsupportedAppUsage
+    private String mResDir;
+    private String[] mOverlayDirs;
+    @UnsupportedAppUsage
+    private String mDataDir;
+    @UnsupportedAppUsage
+    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;
+
+    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;
+        mOverlayDirs = 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 (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
+    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) {
+        setApplicationInfo(aInfo);
+
+        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 (this) {
+            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, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
+                        Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                        getClassLoader());
+            }
+        }
+        mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
+    }
+
+    private void setApplicationInfo(ApplicationInfo aInfo) {
+        final int myUid = Process.myUid();
+        aInfo = adjustNativeLibraryPaths(aInfo);
+        mApplicationInfo = aInfo;
+        mAppDir = aInfo.sourceDir;
+        mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
+        mOverlayDirs = aInfo.resourceDirs;
+        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);
+        }
+    }
+
+    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) {
+            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);
+
+                // 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)) {
+                        outZipPaths.add(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) {
+                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> {
+        private final String[][] mCachedResourcePaths;
+        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) {
+            return mCachedClassLoaders[splitIdx] != null;
+        }
+
+        @Override
+        protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+                int parentSplitIdx) throws NameNotFoundException {
+            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 {
+            return mCachedClassLoaders[ensureSplitLoaded(splitName)];
+        }
+
+        String[] getSplitPathsForSplit(String splitName) throws NameNotFoundException {
+            return mCachedResourcePaths[ensureSplitLoaded(splitName)];
+        }
+    }
+
+    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();
+        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);
+    }
+
+    private List<ClassLoader> createSharedLibrariesLoaders(List<SharedLibraryInfo> sharedLibraries,
+            boolean isBundledApp, String librarySearchPath, String libraryPermittedPath) {
+        if (sharedLibraries == null) {
+            return null;
+        }
+        List<ClassLoader> loaders = new ArrayList<>();
+        for (SharedLibraryInfo info : sharedLibraries) {
+            loaders.add(createSharedLibraryLoader(
+                    info, isBundledApp, librarySearchPath, libraryPermittedPath));
+        }
+        return loaders;
+    }
+
+    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 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;
+        }
+
+        makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
+
+        String libraryPermittedPath = 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 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 needToSetupJitProfiles = false;
+        if (mDefaultClassLoader == null) {
+            // Temporarily disable logging of disk reads on the Looper thread
+            // as this is early and necessary.
+            StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
+
+            List<ClassLoader> sharedLibraries = createSharedLibrariesLoaders(
+                    mApplicationInfo.sharedLibraryInfos, isBundledApp, librarySearchPath,
+                    libraryPermittedPath);
+
+            mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
+                    zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
+                    libraryPermittedPath, mBaseClassLoader,
+                    mApplicationInfo.classLoaderName, sharedLibraries);
+            mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
+
+            setThreadPolicy(oldPolicy);
+            // Setup the class loader paths for profiling.
+            needToSetupJitProfiles = true;
+        }
+
+        if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) {
+            // 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);
+            }
+        }
+
+        // /aepx/com.android.runtime/lib, /vendor/lib, /odm/lib and /product/lib
+        // are added to the native lib search paths of the classloader.
+        // Note that this is done AFTER the classloader is
+        // created by ApplicationLoaders.getDefault().getClassLoader(...). The
+        // reason is because if we have added the paths when creating the classloader
+        // above, the paths are also added to the search path of the linker namespace
+        // 'classloader-namespace', which will allow ALL libs in the paths to apps.
+        // Since only the libs listed in <partition>/etc/public.libraries.txt can be
+        // available to apps, we shouldn't add the paths then.
+        //
+        // However, we need to add the paths to the classloader (Java) though. This
+        // is because when a native lib is requested via System.loadLibrary(), the
+        // classloader first tries to find the requested lib in its own native libs
+        // search paths. If a lib is not found in one of the paths, dlopen() is not
+        // called at all. This can cause a problem that a vendor public native lib
+        // is accessible when directly opened via dlopen(), but inaccesible via
+        // System.loadLibrary(). In order to prevent the problem, we explicitly
+        // add the paths only to the classloader, and not to the native loader
+        // (linker namespace).
+        List<String> extraLibPaths = new ArrayList<>(4);
+        String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
+        if (!defaultSearchPaths.contains("/apex/com.android.runtime/lib")) {
+            extraLibPaths.add("/apex/com.android.runtime/lib" + abiSuffix);
+        }
+        if (!defaultSearchPaths.contains("/vendor/lib")) {
+            extraLibPaths.add("/vendor/lib" + abiSuffix);
+        }
+        if (!defaultSearchPaths.contains("/odm/lib")) {
+            extraLibPaths.add("/odm/lib" + abiSuffix);
+        }
+        if (!defaultSearchPaths.contains("/product/lib")) {
+            extraLibPaths.add("/product/lib" + abiSuffix);
+        }
+        if (!extraLibPaths.isEmpty()) {
+            StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
+            try {
+                ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, extraLibPaths);
+            } 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.
+            needToSetupJitProfiles = 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 (needToSetupJitProfiles && !ActivityThread.isSystem() && mActivityThread != null) {
+            setupJitProfileSupport();
+        }
+
+        // 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));
+        }
+    }
+
+    @UnsupportedAppUsage
+    public ClassLoader getClassLoader() {
+        synchronized (this) {
+            if (mClassLoader == null) {
+                createOrUpdateClassLoaderLocked(null /*addedPaths*/);
+            }
+            return mClassLoader;
+        }
+    }
+
+    private void setupJitProfileSupport() {
+        if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
+            return;
+        }
+
+        // If we use profiles, 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 invoking app code for the first time so that we
+        // can capture the complete application startup.
+        BaseDexClassLoader.setReporter(DexLoadReporter.getInstance());
+
+        // 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 profileFile = ArtManager.getCurrentProfilePath(
+                    mPackageName, UserHandle.myUserId(), splitName);
+            VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)});
+        }
+
+        // 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;
+        try {
+            pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                    UserHandle.myUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        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;
+    }
+
+    @UnsupportedAppUsage
+    public String[] getOverlayDirs() {
+        return mOverlayDirs;
+    }
+
+    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");
+            }
+
+            mResources = ResourcesManager.getInstance().getResources(null, mResDir,
+                    splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
+                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                    getClassLoader());
+        }
+        return mResources;
+    }
+
+    @UnsupportedAppUsage
+    public Application makeApplication(boolean forceDefaultAppClass,
+            Instrumentation instrumentation) {
+        if (mApplication != null) {
+            return mApplication;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
+
+        Application app = null;
+
+        String appClass = mApplicationInfo.className;
+        if (forceDefaultAppClass || (appClass == null)) {
+            appClass = "android.app.Application";
+        }
+
+        try {
+            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);
+            }
+            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
+            app = mActivityThread.mInstrumentation.newApplication(
+                    cl, appClass, appContext);
+            appContext.setOuterContext(app);
+        } catch (Exception e) {
+            if (!mActivityThread.mInstrumentation.onException(app, e)) {
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                throw new RuntimeException(
+                    "Unable to instantiate application " + appClass
+                    + ": " + e.toString(), e);
+            }
+        }
+        mActivityThread.mAllApplications.add(app);
+        mApplication = app;
+
+        if (instrumentation != null) {
+            try {
+                instrumentation.callApplicationOnCreate(app);
+            } catch (Exception e) {
+                if (!instrumentation.onException(app, e)) {
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    throw new RuntimeException(
+                        "Unable to create application " + app.getClass().getName()
+                        + ": " + e.toString(), e);
+                }
+            }
+        }
+
+        // Rewrite the R 'constants' for all library apks.
+        SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
+        final int N = packageIdentifiers.size();
+        for (int i = 0; i < N; i++) {
+            final int id = packageIdentifiers.keyAt(i);
+            if (id == 0x01 || id == 0x7f) {
+                continue;
+            }
+
+            rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
+        }
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        return app;
+    }
+
+    @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(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 WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
+            final LoadedApk.ReceiverDispatcher mStrongRef;
+
+            InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
+                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) {
+                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, sendingUser);
+                } else {
+                    // 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(this, resultCode, data, extras, false, intent.getFlags());
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+            }
+        }
+
+        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 final boolean mOrdered;
+            private boolean mDispatched;
+            private boolean mRunCalled;
+
+            public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
+                    boolean ordered, boolean sticky, int sendingUser) {
+                super(resultCode, resultData, resultExtras,
+                        mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
+                        sticky, mIIntentReceiver.asBinder(), sendingUser, intent.getFlags());
+                mCurIntent = intent;
+                mOrdered = ordered;
+            }
+
+            public final Runnable getRunnable() {
+                return () -> {
+                    final BroadcastReceiver receiver = mReceiver;
+                    final boolean ordered = mOrdered;
+
+                    if (ActivityThread.DEBUG_BROADCAST) {
+                        int seq = mCurIntent.getIntExtra("seq", -1);
+                        Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
+                                + " seq=" + seq + " to " + mReceiver);
+                        Slog.i(ActivityThread.TAG, "  mRegistered=" + mRegistered
+                                + " mOrderedHint=" + ordered);
+                    }
+
+                    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 (mRegistered && ordered) {
+                            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+                                    "Finishing null broadcast to " + mReceiver);
+                            sendFinished(mgr);
+                        }
+                        return;
+                    }
+
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");
+                    try {
+                        ClassLoader cl = mReceiver.getClass().getClassLoader();
+                        intent.setExtrasClassLoader(cl);
+                        intent.prepareToEnterProcess();
+                        setExtrasClassLoader(cl);
+                        receiver.setPendingResult(this);
+                        receiver.onReceive(mContext, intent);
+                    } catch (Exception e) {
+                        if (mRegistered && ordered) {
+                            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(BroadcastReceiver receiver, Context context,
+                Handler activityThread, Instrumentation instrumentation,
+                boolean registered) {
+            if (activityThread == null) {
+                throw new NullPointerException("Handler must not be null");
+            }
+
+            mIIntentReceiver = new InnerReceiver(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, int sendingUser) {
+            final Args args = new Args(intent, resultCode, data, extras, ordered,
+                    sticky, sendingUser);
+            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())) {
+                if (mRegistered && ordered) {
+                    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, int flags) {
+        return getServiceDispatcherCommon(c, context, handler, null, flags);
+    }
+
+    public final IServiceConnection getServiceDispatcher(ServiceConnection c,
+            Context context, Executor executor, int flags) {
+        return getServiceDispatcherCommon(c, context, null, executor, flags);
+    }
+
+    private IServiceConnection getServiceDispatcherCommon(ServiceConnection c,
+            Context context, Handler handler, Executor executor, int 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
+    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
+        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 int mFlags;
+
+        private RuntimeException mUnbindLocation;
+
+        private boolean mForgotten;
+
+        private static class ConnectionInfo {
+            IBinder binder;
+            IBinder.DeathRecipient deathMonitor;
+        }
+
+        private static class InnerConnection extends IServiceConnection.Stub {
+            @UnsupportedAppUsage
+            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
+        ServiceDispatcher(ServiceConnection conn,
+                Context context, Handler activityThread, int 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, int 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
+        IServiceConnection getIServiceConnection() {
+            return mIServiceConnection;
+        }
+
+        int 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);
+            }
+            // 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;
+        }
+    }
+}