/*
 * Copyright (C) 2006 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 static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull;
import static android.app.Flags.customBackupagentCreation;
import static android.app.Flags.skipBgMemTrimOnFgApp;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.privatecompute.flags.Flags.enablePccFrameworkSupport;
import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.pm.ActivityInfo.CONFIG_RESOURCES_UNUSED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_VIRTUAL_GAMEPAD;
import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_VIRTUAL_GAMEPAD_OVERRIDE;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
import static android.window.ConfigurationHelper.isDifferentDisplay;
import static android.window.ConfigurationHelper.shouldUpdateResources;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.RemoteServiceException.ExcessiveEnqueuedBroadcastsException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupAnnotations.OperationType;
import android.app.compat.CompatChanges;
import android.app.sdksandbox.sandboxactivity.ActivityContextInfo;
import android.app.sdksandbox.sandboxactivity.SdkSandboxActivityAuthority;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.PendingTransactionActions.StopInfo;
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.TransactionExecutor;
import android.app.servertransaction.TransactionExecutorHelper;
import android.bluetooth.BluetoothFrameworkInitializer;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ProviderInfoList;
import android.content.pm.ServiceInfo;
import android.content.pm.SystemFeaturesCache;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.ResourceTimer;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.loader.ResourcesLoader;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.HardwareRenderer;
import android.graphics.Typeface;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.media.MediaFrameworkInitializer;
import android.media.MediaFrameworkPlatformInitializer;
import android.media.MediaServiceManager;
import android.net.ConnectivityManager;
import android.net.Proxy;
import android.net.TrafficStats;
import android.net.Uri;
import android.nfc.NfcFrameworkInitializer;
import android.nfc.NfcServiceManager;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.BluetoothServiceManager;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.DdmSyncStageUpdater;
import android.os.DdmSyncState.Stage;
import android.os.Debug;
import android.os.Environment;
import android.os.GraphicsEnvironment;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IBinderCallback;
import android.os.ICancellationSignal;
import android.os.LocaleList;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.ProfilingFrameworkInitializer;
import android.os.ProfilingServiceManager;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
import android.os.StatsFrameworkInitializer;
import android.os.StatsServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.TelephonyServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.instrumentation.ExecutableMethodFileOffsets;
import android.os.instrumentation.IOffsetCallback;
import android.os.instrumentation.MethodDescriptor;
import android.os.instrumentation.MethodDescriptorParser;
import android.permission.IPermissionManager;
import android.provider.BlockedNumberContract;
import android.provider.CalendarContract;
import android.provider.CallLog;
import android.provider.ContactsContract;
import android.provider.DeviceConfigInitializer;
import android.provider.DeviceConfigServiceManager;
import android.provider.Downloads;
import android.provider.FontsContract;
import android.provider.Settings;
import android.ravenwood.annotation.RavenwoodIgnore;
import android.ravenwood.annotation.RavenwoodKeep;
import android.ravenwood.annotation.RavenwoodKeepPartialClass;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.renderscript.RenderScriptCacheDir;
import android.se.omapi.SeFrameworkInitializer;
import android.se.omapi.SeServiceManager;
import android.security.NetworkSecurityPolicy;
import android.security.net.config.NetworkSecurityConfigProvider;
import android.system.ErrnoException;
import android.telephony.TelephonyFrameworkInitializer;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SuperNotCalledException;
import android.util.UtilConfig;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewManager;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.contentcapture.IContentCaptureManager;
import android.view.contentcapture.IContentCaptureOptionsCallback;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationSpec;
import android.webkit.WebView;
import android.window.ActivityWindowInfo;
import android.window.ITaskFragmentOrganizer;
import android.window.SizeConfigurationBuckets;
import android.window.SplashScreen;
import android.window.SplashScreenView;
import android.window.TaskFragmentTransaction;
import android.window.TaskSnapshotManager;
import android.window.WindowContextInfo;
import android.window.WindowExtensionsHelper;
import android.window.WindowProviderService;
import android.window.WindowTokenClientController;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.ApplicationSharedMemory;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.DebugStore;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SafeZipPathValidatorCallback;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.internal.policy.DecorView;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.android.server.am.BitmapDumpProto;
import com.android.server.am.MemInfoDumpProto;

import dalvik.annotation.optimization.NeverCompile;
import dalvik.annotation.optimization.NeverInline;
import dalvik.system.AppSpecializationHooks;
import dalvik.system.CloseGuard;
import dalvik.system.VMDebug;
import dalvik.system.VMRuntime;
import dalvik.system.ZipPathValidator;

import libcore.io.IoUtils;
import libcore.net.event.NetworkEventDispatcher;
import libcore.util.NativeAllocationRegistry;

import org.apache.harmony.dalvik.ddmc.DdmVmInternal;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
 * This manages the execution of the main thread in an
 * application process, scheduling and executing activities,
 * broadcasts, and other operations on it as the activity
 * manager requests.
 *
 * @hide
 */
@android.ravenwood.annotation.RavenwoodKeepPartialClass(comment =
        "Initialization logic is in ActivityThread_ravenwood and RavenwoodAppDriver."
        + " Only very basic initialization is done."
        + " Most of its logic is not ported to Ravenwood yet."
)
@android.ravenwood.annotation.RavenwoodRedirectionClass("ActivityThread_ravenwood")
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {

    private final DdmSyncStageUpdater mDdmSyncStageUpdater = newDdmSyncStageUpdater();

    @RavenwoodIgnore
    private static DdmSyncStageUpdater newDdmSyncStageUpdater() {
        return new DdmSyncStageUpdater();
    }

    /** @hide */
    public static final String TAG = "ActivityThread";

    // TODO(b/303199244): This is a temporary allowlist for early field data collection.
    // It will be replaced by a sharding config in the future.
    private static final Set<String> PERFETTO_TRACING_ALLOWLIST = new ArraySet<>(Arrays.asList(
            "com.google.android.youtube",
            "com.google.android.googlequicksearchbox"
    ));

    static final boolean localLOGV = false;
    static final boolean DEBUG_MESSAGES = false;
    /** @hide */
    public static final boolean DEBUG_BROADCAST = false;
    private static final boolean DEBUG_RESULTS = false;
    private static final boolean DEBUG_BACKUP = false;
    public static final boolean DEBUG_CONFIGURATION = false;
    private static final boolean DEBUG_SERVICE = false;
    public static final boolean DEBUG_MEMORY_TRIM = false;
    private static final boolean DEBUG_PROVIDER = false;
    public static final boolean DEBUG_ORDER = false;
    private static final boolean DEBUG_APP_INFO = false;
    private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
    /**
     * The delay to release the provider when it has no more references. It reduces the number of
     * transactions for acquiring and releasing provider if the client accesses the provider
     * frequently in a short time.
     */
    private static final long CONTENT_PROVIDER_RETAIN_TIME = 1000;
    private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;

    /** Type for IActivityManager.serviceDoneExecuting: anonymous operation */
    public static final int SERVICE_DONE_EXECUTING_ANON = 0;
    /** Type for IActivityManager.serviceDoneExecuting: done with an onStart call */
    public static final int SERVICE_DONE_EXECUTING_START = 1;
    /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */
    public static final int SERVICE_DONE_EXECUTING_STOP = 2;
    /** Type for IActivityManager.serviceDoneExecuting: done with an onRebind call */
    public static final int SERVICE_DONE_EXECUTING_REBIND = 3;
    /** Type for IActivityManager.serviceDoneExecuting: done with an onUnbind call */
    public static final int SERVICE_DONE_EXECUTING_UNBIND = 4;

    /** Use foreground GC policy (less pause time) and higher JIT weight. */
    private static final int VM_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
    /** Use background GC policy and default JIT threshold. */
    private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;

    /** The delay time for retrying to request DirectActions. */
    private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
    /** The max count for retrying to request DirectActions. */
    private static final int REQUEST_DIRECT_ACTIONS_RETRY_MAX_COUNT = 7;

    /**
     * Denotes an invalid sequence number corresponding to a process state change.
     */
    public static final long INVALID_PROC_STATE_SEQ = -1;

    /**
     * Identifier for the sequence no. associated with this process start. It will be provided
     * as one of the arguments when the process starts.
     */
    public static final String PROC_START_SEQ_IDENT = "seq=";

    private final Object mNetworkPolicyLock = new Object();

    private static final String DEFAULT_FULL_BACKUP_AGENT = "android.app.backup.FullBackupAgent";

    private static final long BINDER_CALLBACK_THROTTLE = 10_100L;
    private long mBinderCallbackLast = -1;

    private static final boolean DEBUG_STORE_ENABLED =
            com.android.internal.os.Flags.debugStoreEnabled();

    /**
    * Threshold for identifying long-running looper messages (in milliseconds).
    * Calculated as 2 seconds multiplied by the hardware timeout multiplier.
    */
    private static final long LONG_MESSAGE_THRESHOLD_MS = 2000 * Build.HW_TIMEOUT_MULTIPLIER;

    /**
     * Denotes the sequence number of the process state change for which the main thread needs
     * to block until the network rules are updated for it.
     *
     * Value of {@link #INVALID_PROC_STATE_SEQ} indicates there is no need for blocking.
     */
    @GuardedBy("mNetworkPolicyLock")
    private long mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;

    @UnsupportedAppUsage
    private ContextImpl mSystemContext;
    @GuardedBy("this")
    private ArrayList<WeakReference<Context>> mDisplaySystemUiContexts;

    @UnsupportedAppUsage
    static volatile IPackageManager sPackageManager;
    private static volatile IPermissionManager sPermissionManager;

    @UnsupportedAppUsage
    final ApplicationThread mAppThread = new ApplicationThread();
    @UnsupportedAppUsage
    final Looper mLooper = Looper.myLooper();
    @UnsupportedAppUsage
    final H mH = new H();
    final Executor mExecutor = new HandlerExecutor(mH);
    /**
     * Maps from activity token to local record of running activities in this process.
     *
     * This variable is readable if the code is running in activity thread or holding {@link
     * #mResourcesManager}. It's only writable if the code is running in activity thread and holding
     * {@link #mResourcesManager}.
     */
    @UnsupportedAppUsage
    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
    /** Maps from activity token to the pending override configuration. */
    @GuardedBy("mPendingOverrideConfigs")
    private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>();
    /** Maps from activity token to the pending configuration changes. */
    @GuardedBy("mPendingConfigChanges")
    private final ArrayMap<IBinder, ConfigChange> mPendingConfigChanges = new ArrayMap<>();

    private static class ConfigChange {
        @NonNull
        private final Configuration mConfiguration;
        @NonNull
        private final ActivityWindowInfo mActivityWindowInfo;
        private final int mDisplayId;

        private ConfigChange(Configuration configuration, ActivityWindowInfo activityWindowInfo,
                int displayId) {
            mConfiguration = configuration;
            mActivityWindowInfo = activityWindowInfo;
            mDisplayId = displayId;
        }
    }

    /**
     * A queue of pending ApplicationInfo updates. In case when we get a concurrent update
     * this queue allows us to only apply the latest object, and it can be applied on demand
     * instead of waiting for the handler thread to reach the scheduled callback.
     */
    @GuardedBy("mResourcesManager")
    private final ArrayMap<String, ApplicationInfo> mPendingAppInfoUpdates = new ArrayMap<>();

    /** The activities to be truly destroyed (not include relaunch). */
    final Map<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed =
            Collections.synchronizedMap(new ArrayMap<>());
    // List of new activities that should be reported when next we idle.
    final ArrayList<ActivityClientRecord> mNewActivities = new ArrayList<>();
    // Number of activities that are currently visible on-screen.
    @UnsupportedAppUsage
    int mNumVisibleActivities = 0;
    private final AtomicInteger mNumLaunchingActivities = new AtomicInteger();
    @GuardedBy("mAppThread")
    private int mLastProcessState = PROCESS_STATE_UNKNOWN;
    final ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();

    @NonNull
    private final ConfigurationChangedListenerController mConfigurationChangedListenerController =
            new ConfigurationChangedListenerController();

    private int mLastSessionId;
    // Holds the value of the last reported device ID value from the server for the top activity.
    int mLastReportedDeviceId = Context.DEVICE_ID_DEFAULT;
    final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
    @UnsupportedAppUsage
    final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
    @UnsupportedAppUsage
    AppBindData mBoundApplication;
    Profiler mProfiler;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553,
            publicAlternatives = "Use {@code Context#getResources()#getConfiguration()#densityDpi} "
                    + "instead.")
    int mCurDefaultDisplayDpi;
    @UnsupportedAppUsage
    boolean mDensityCompatMode;
    private CompatibilityInfo mCompatibilityInfo;
    @UnsupportedAppUsage(trackingBug = 176961850, maxTargetSdk = Build.VERSION_CODES.R,
            publicAlternatives = "Use {@code Context#getResources()#getConfiguration()} instead.")
    Configuration mConfiguration;
    @GuardedBy("this")
    private boolean mUpdateHttpProxyOnBind = false;
    @UnsupportedAppUsage
    Application mInitialApplication;
    @UnsupportedAppUsage
    final ArrayList<Application> mAllApplications = new ArrayList<>();
    /**
     * Bookkeeping of instantiated backup agents indexed first by user id, then by package name.
     * Indexing by user id supports parallel backups across users on system packages as they run in
     * the same process with the same package name. Indexing by package name supports multiple
     * distinct applications running in the same process.
     */
    private final SparseArray<ArrayMap<String, BackupAgent>> mBackupAgentsByUser =
            new SparseArray<>();
    /** Reference to singleton {@link ActivityThread} */
    @UnsupportedAppUsage
    private static volatile ActivityThread sCurrentActivityThread;
    @UnsupportedAppUsage
    Instrumentation mInstrumentation;
    String mInstrumentationPackageName = null;
    @UnsupportedAppUsage
    String mInstrumentationAppDir = null;
    String[] mInstrumentationSplitAppDirs = null;
    String mInstrumentationLibDir = null;
    @UnsupportedAppUsage
    String mInstrumentedAppDir = null;
    String[] mInstrumentedSplitAppDirs = null;
    String mInstrumentedLibDir = null;
    boolean mInstrumentingWithoutRestart;
    boolean mSystemThread = false;
    boolean mSomeActivitiesChanged = false;

    // These can be accessed by multiple threads; mResourcesManager is the lock.
    // XXX For now we keep around information about all packages we have
    // seen, not removing entries from this map.
    // NOTE: The activity and window managers need to call in to
    // ActivityThread to do things like update resource configurations,
    // which means this lock gets held while the activity and window managers
    // holds their own lock.  Thus you MUST NEVER call back into the activity manager
    // or window manager or anything that depends on them while holding this lock.
    // These LoadedApk are only valid for the userId that we're running as.
    @GuardedBy("mResourcesManager")
    @UnsupportedAppUsage
    final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
    @GuardedBy("mResourcesManager")
    @UnsupportedAppUsage
    final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<>();
    @GuardedBy("mResourcesManager")
    final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<>();
    @GuardedBy("mResourcesManager")
    @UnsupportedAppUsage(trackingBug = 176961850, maxTargetSdk = Build.VERSION_CODES.R,
            publicAlternatives = "Use {@code Context#getResources()#getConfiguration()} instead.")
    Configuration mPendingConfiguration = null;
    // An executor that performs multi-step transactions.
    private final TransactionExecutor mTransactionExecutor = newTransactionExecutor(this);

    @RavenwoodIgnore
    private static TransactionExecutor newTransactionExecutor(ActivityThread at) {
        return new TransactionExecutor(at);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private final ResourcesManager mResourcesManager;

    // Registry of remote cancellation transports pending a reply with reply handles.
    @GuardedBy("this")
    private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;

    private static final class ProviderKey {
        final String authority;
        final int userId;

        @GuardedBy("mLock")
        ContentProviderHolder mHolder; // Temp holder to be used between notifier and waiter
        final Object mLock; // The lock to be used to get notified when the provider is ready

        public ProviderKey(String authority, int userId) {
            this.authority = authority;
            this.userId = userId;
            this.mLock = new Object();
        }

        @Override
        public boolean equals(@Nullable Object o) {
            if (o instanceof ProviderKey) {
                final ProviderKey other = (ProviderKey) o;
                return Objects.equals(authority, other.authority) && userId == other.userId;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return ((authority != null) ? authority.hashCode() : 0) ^ userId;
        }
    }

    // The lock of mProviderMap protects the following variables.
    @UnsupportedAppUsage
    final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<>();
    @UnsupportedAppUsage
    final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap = new ArrayMap<>();
    @VisibleForTesting
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders = new ArrayMap<>();
    @UnsupportedAppUsage
    final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName = new ArrayMap<>();

    // Mitigation for b/74523247: Used to serialize calls to AM.getContentProvider().
    // Note we never removes items from this map but that's okay because there are only so many
    // users and so many authorities.
    @GuardedBy("mGetProviderKeys")
    final ArrayMap<ProviderKey, ProviderKey> mGetProviderKeys = new ArrayMap<>();

    final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners =
            new ArrayMap<>();

    private SplashScreen.SplashScreenManagerGlobal mSplashScreenGlobal;

    final GcIdler mGcIdler = new GcIdler();
    final PurgeIdler mPurgeIdler = new PurgeIdler();

    boolean mPurgeIdlerScheduled = false;
    boolean mGcIdlerScheduled = false;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    static volatile Handler sMainThreadHandler;  // set once in main()
    private long mStartSeq; // Only accesssed from the main thread

    private Bundle mCoreSettings = null;

    /**
     * The lock word for the {@link #mCoreSettings}.
     */
    private final Object mCoreSettingsLock = new Object();

    private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null;

    /** A client side controller to handle process level configuration changes. */
    private ConfigurationController mConfigurationController;

    /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */
    public static final class ActivityClientRecord {
        @UnsupportedAppUsage
        public IBinder token;
        public IBinder assistToken;
        // A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
        // used without security checks
        public IBinder shareableActivityToken;
        // The token of the TaskFragment that embedded this activity.
        @Nullable public IBinder mTaskFragmentToken;
        public IBinder initialCallerInfoAccessToken;
        int ident;
        @UnsupportedAppUsage
        Intent intent;
        String referrer;
        IVoiceInteractor voiceInteractor;
        Bundle state;
        PersistableBundle persistentState;
        @UnsupportedAppUsage
        Activity activity;
        Window window;
        Activity parent;
        String embeddedID;
        Activity.NonConfigurationInstances lastNonConfigurationInstances;
        // TODO(lifecycler): Use mLifecycleState instead.
        @UnsupportedAppUsage
        boolean paused;
        @UnsupportedAppUsage
        boolean stopped;
        boolean hideForNow;
        Configuration createdConfig;
        Configuration overrideConfig;
        HandoffActivityData handoffActivityData;
        @NonNull
        private final ActivityWindowInfo mActivityWindowInfo = new ActivityWindowInfo();
        @NonNull
        private final ActivityWindowInfo mLastReportedActivityWindowInfo = new ActivityWindowInfo();

        // Used for consolidating configs before sending on to Activity.
        private final Configuration tmpConfig = new Configuration();
        // Callback used for updating activity override config and camera compat control state.
        ViewRootImpl.ActivityConfigCallback activityConfigCallback;

        // Indicates whether this activity is currently the topmost resumed one in the system.
        // This holds the last reported value from server.
        boolean isTopResumedActivity;
        // This holds the value last sent to the activity. This is needed, because an update from
        // server may come at random time, but we always need to report changes between ON_RESUME
        // and ON_PAUSE to the app.
        boolean lastReportedTopResumedState;

        ProfilerInfo profilerInfo;

        @UnsupportedAppUsage
        ActivityInfo activityInfo;
        @UnsupportedAppUsage
        CompatibilityInfo compatInfo;
        @UnsupportedAppUsage
        public LoadedApk packageInfo;

        List<ResultInfo> pendingResults;
        List<ReferrerIntent> pendingIntents;

        boolean startsNotResumed;
        public final boolean isForward;
        int pendingConfigChanges;
        int mDisplayId;

        Window mPendingRemoveWindow;
        WindowManager mPendingRemoveWindowManager;
        @UnsupportedAppUsage
        boolean mPreserveWindow;

        /** The scene transition info. */
        SceneTransitionInfo mSceneTransitionInfo;

        /** Whether this activiy was launched from a bubble. */
        boolean mLaunchedFromBubble;

        /**
         * This can be different from the current configuration because a new configuration may not
         * always update to activity, e.g. windowing mode change without size change.
         */
        int mLastReportedWindowingMode = WINDOWING_MODE_UNDEFINED;

        @LifecycleState
        private int mLifecycleState = PRE_ON_CREATE;

        private SizeConfigurationBuckets mSizeConfigurations;

        @VisibleForTesting
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        public ActivityClientRecord() {
            this.isForward = false;
            init();
        }

        public ActivityClientRecord(IBinder token, Intent intent, int ident,
                ActivityInfo info, Configuration overrideConfig,
                String referrer, IVoiceInteractor voiceInteractor, Bundle state,
                PersistableBundle persistentState, List<ResultInfo> pendingResults,
                List<ReferrerIntent> pendingNewIntents, SceneTransitionInfo sceneTransitionInfo,
                boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
                IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
                IBinder taskFragmentToken, IBinder initialCallerInfoAccessToken,
                ActivityWindowInfo activityWindowInfo, int displayId) {
            this.token = token;
            this.assistToken = assistToken;
            this.shareableActivityToken = shareableActivityToken;
            this.ident = ident;
            this.intent = intent;
            this.referrer = referrer;
            this.voiceInteractor = voiceInteractor;
            this.activityInfo = info;
            this.state = state;
            this.persistentState = persistentState;
            this.pendingResults = pendingResults;
            this.pendingIntents = pendingNewIntents;
            this.isForward = isForward;
            this.profilerInfo = profilerInfo;
            this.overrideConfig = overrideConfig;
            this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
            this.initialCallerInfoAccessToken = initialCallerInfoAccessToken;
            mSceneTransitionInfo = sceneTransitionInfo;
            mLaunchedFromBubble = launchedFromBubble;
            mTaskFragmentToken = taskFragmentToken;
            mActivityWindowInfo.set(activityWindowInfo);
            mDisplayId = displayId;
            init();
        }

        /** Common initializer for all constructors. */
        private void init() {
            parent = null;
            embeddedID = null;
            paused = false;
            stopped = false;
            hideForNow = false;
            activityConfigCallback = new ViewRootImpl.ActivityConfigCallback() {

                @Override
                public void onConfigurationChanged(@NonNull Configuration overrideConfig,
                        int newDisplayId, @Nullable ActivityWindowInfo activityWindowInfo) {
                    if (activity == null) {
                        throw new IllegalStateException(
                                "Received config update for non-existing activity");
                    }
                    if (activityWindowInfo == null) {
                        Log.w(TAG, "Received empty ActivityWindowInfo update for r=" + activity);
                        activityWindowInfo = mActivityWindowInfo;
                    }
                    activity.mMainThread.handleActivityConfigurationChanged(
                            ActivityClientRecord.this, overrideConfig, newDisplayId,
                            activityWindowInfo,
                            false /* alwaysReportChange */);
                }
            };
        }

        /** Get the current lifecycle state. */
        public int getLifecycleState() {
            return mLifecycleState;
        }

        /** Update the current lifecycle state for internal bookkeeping. */
        public void setState(@LifecycleState int newLifecycleState) {
            mLifecycleState = newLifecycleState;
            switch (mLifecycleState) {
                case ON_CREATE:
                    paused = true;
                    stopped = true;
                    break;
                case ON_START:
                    paused = true;
                    stopped = false;
                    break;
                case ON_RESUME:
                    paused = false;
                    stopped = false;
                    break;
                case ON_PAUSE:
                    paused = true;
                    stopped = false;
                    break;
                case ON_STOP:
                    paused = true;
                    stopped = true;
                    break;
            }
        }

        private boolean isPreHoneycomb() {
            return activity != null && activity.getApplicationInfo().targetSdkVersion
                    < android.os.Build.VERSION_CODES.HONEYCOMB;
        }

        private boolean isPreP() {
            return activity != null && activity.getApplicationInfo().targetSdkVersion
                    < android.os.Build.VERSION_CODES.P;
        }

        public boolean isPersistable() {
            return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS;
        }

        public boolean isVisibleFromServer() {
            return activity != null && activity.mVisibleFromServer;
        }

        @NonNull
        public ActivityWindowInfo getActivityWindowInfo() {
            return mActivityWindowInfo;
        }

        public String toString() {
            ComponentName componentName = intent != null ? intent.getComponent() : null;
            return "ActivityRecord{"
                + Integer.toHexString(System.identityHashCode(this))
                + " token=" + token + " " + (componentName == null
                        ? "no component name" : componentName.toShortString())
                + "}";
        }

        public String getStateString() {
            StringBuilder sb = new StringBuilder();
            sb.append("ActivityClientRecord{");
            sb.append("paused=").append(paused);
            sb.append(", stopped=").append(stopped);
            sb.append(", hideForNow=").append(hideForNow);
            sb.append(", startsNotResumed=").append(startsNotResumed);
            sb.append(", isForward=").append(isForward);
            sb.append(", pendingConfigChanges=").append(pendingConfigChanges);
            sb.append(", preserveWindow=").append(mPreserveWindow);
            if (activity != null) {
                sb.append(", Activity{");
                sb.append("resumed=").append(activity.mResumed);
                sb.append(", stopped=").append(activity.mStopped);
                sb.append(", finished=").append(activity.isFinishing());
                sb.append(", destroyed=").append(activity.isDestroyed());
                sb.append(", startedActivity=").append(activity.mStartedActivity);
                sb.append(", changingConfigurations=").append(activity.mChangingConfigurations);
                sb.append("}");
            }
            sb.append("}");
            return sb.toString();
        }
    }

    @VisibleForTesting
    public static final class ProviderClientRecord {
        final String[] mNames;
        @UnsupportedAppUsage
        final IContentProvider mProvider;
        @UnsupportedAppUsage
        final ContentProvider mLocalProvider;
        @UnsupportedAppUsage
        final ContentProviderHolder mHolder;

        @VisibleForTesting
        public ProviderClientRecord(String[] names, IContentProvider provider,
                ContentProvider localProvider, ContentProviderHolder holder) {
            mNames = names;
            mProvider = provider;
            mLocalProvider = localProvider;
            mHolder = holder;
        }
    }

    @VisibleForTesting(visibility = PACKAGE)
    public static final class ReceiverData extends BroadcastReceiver.PendingResult {
        public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
                boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
                int sendingUser, int sendingUid, String sendingPackage) {
            super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
                    assumeDelivered, token, sendingUser, intent.getFlags(), sendingUid,
                    sendingPackage);
            this.intent = intent;
            mOptions = ActivityOptions.fromBundle(resultExtras);
        }

        @UnsupportedAppUsage
        final Intent intent;
        @UnsupportedAppUsage
        ActivityInfo info;
        @UnsupportedAppUsage
        CompatibilityInfo compatInfo;
        @Nullable
        final ActivityOptions mOptions;

        public String toString() {
            return "ReceiverData{intent=" + intent + " packageName=" +
                    info.packageName + " resultCode=" + getResultCode()
                    + " resultData=" + getResultData() + " resultExtras="
                    + getResultExtras(false) + " sentFromUid="
                    + getSentFromUid() + " sentFromPackage=" + getSentFromPackage()
                    + " mOptions=" + mOptions + "}";
        }
    }

    static final class CreateBackupAgentData {
        ApplicationInfo appInfo;
        int backupMode;
        int userId;
        @BackupDestination int backupDestination;
        public String toString() {
            return "CreateBackupAgentData{appInfo=" + appInfo
                    + " backupAgent=" + appInfo.backupAgentName
                    + " mode=" + backupMode + " userId=" + userId + "}";
        }
    }

    static final class CreateServiceData {
        @UnsupportedAppUsage
        CreateServiceData() {
        }
        @UnsupportedAppUsage
        IBinder token;
        @UnsupportedAppUsage
        ServiceInfo info;
        @UnsupportedAppUsage
        CompatibilityInfo compatInfo;
        @UnsupportedAppUsage
        Intent intent;
        public String toString() {
            return "CreateServiceData{token=" + token + " className="
            + info.name + " packageName=" + info.packageName
            + " intent=" + intent + "}";
        }
    }

    static final class BindServiceData {
        @UnsupportedAppUsage
        IBinder token;
        @UnsupportedAppUsage
        IBinder bindToken;
        @UnsupportedAppUsage
        Intent intent;
        boolean rebind;
        long bindSeq;
        public String toString() {
            return "BindServiceData{token=" + token + " intent=" + intent
                    + " bindToken = " + bindToken
                    + " bindSeq=" + bindSeq + "}";
        }
    }

    static final class ServiceArgsData {
        @UnsupportedAppUsage
        IBinder token;
        boolean taskRemoved;
        int startId;
        int flags;
        @UnsupportedAppUsage
        Intent args;
        public String toString() {
            return "ServiceArgsData{token=" + token + " startId=" + startId
            + " args=" + args + "}";
        }
    }

    @RavenwoodKeepWholeClass
    static final class AppBindData {
        @UnsupportedAppUsage
        AppBindData() {
        }
        @UnsupportedAppUsage
        LoadedApk info;
        @UnsupportedAppUsage
        String processName;
        @UnsupportedAppUsage
        ApplicationInfo appInfo;
        String sdkSandboxClientAppVolumeUuid;
        String sdkSandboxClientAppPackage;
        boolean isSdkInSandbox;
        @UnsupportedAppUsage
        List<ProviderInfo> providers;
        ComponentName instrumentationName;
        @UnsupportedAppUsage
        Bundle instrumentationArgs;
        IInstrumentationWatcher instrumentationWatcher;
        IUiAutomationConnection instrumentationUiAutomationConnection;
        int debugMode;
        boolean enableBinderTracking;
        boolean trackAllocation;
        @UnsupportedAppUsage
        boolean restrictedBackupMode;
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        boolean persistent;
        Configuration config;
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
        CompatibilityInfo compatInfo;
        String buildSerial;

        /** Initial values for {@link Profiler}. */
        ProfilerInfo initProfilerInfo;

        AutofillOptions autofillOptions;

        /**
         * Content capture options for the application - when null, it means ContentCapture is not
         * enabled for the package.
         */
        @Nullable
        ContentCaptureOptions contentCaptureOptions;

        long[] disabledCompatChanges;
        long[] enabledCompatChanges;
        long[] mLoggableCompatChanges;
        boolean mLogChangeChecksToStatsD;

        SharedMemory mSerializedSystemFontMap;

        long startRequestedElapsedTime;
        long startRequestedUptime;

        @Override
        public String toString() {
            return "AppBindData{appInfo=" + appInfo + "}";
        }
    }

    static final class Profiler {
        String profileFile;
        ParcelFileDescriptor profileFd;
        int samplingInterval;
        boolean autoStopProfiler;
        boolean streamingOutput;
        int mProfilerFlags;
        boolean profiling;
        boolean handlingProfiling;
        public void setProfiler(ProfilerInfo profilerInfo) {
            ParcelFileDescriptor fd = profilerInfo.profileFd;
            if (profiling) {
                if (fd != null) {
                    try {
                        fd.close();
                    } catch (IOException e) {
                        // Ignore
                    }
                }
                return;
            }
            if (profileFd != null) {
                try {
                    profileFd.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
            profileFile = profilerInfo.profileFile;
            profileFd = fd;
            samplingInterval = profilerInfo.samplingInterval;
            autoStopProfiler = profilerInfo.autoStopProfiler;
            streamingOutput = profilerInfo.streamingOutput;
            mProfilerFlags = profilerInfo.profilerFlags;
        }
        public void startProfiling() {
            if (profileFd == null || profiling) {
                return;
            }
            try {
                int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
                VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
                        bufferSize * 1024 * 1024, mProfilerFlags, samplingInterval != 0,
                        samplingInterval, streamingOutput);
                profiling = true;
            } catch (RuntimeException e) {
                Slog.w(TAG, "Profiling failed on path " + profileFile, e);
                try {
                    profileFd.close();
                    profileFd = null;
                } catch (IOException e2) {
                    Slog.w(TAG, "Failure closing profile fd", e2);
                }
            }
        }
        public void stopProfiling() {
            if (profiling) {
                profiling = false;
                Debug.stopMethodTracing();
                if (profileFd != null) {
                    try {
                        profileFd.close();
                    } catch (IOException e) {
                    }
                }
                profileFd = null;
                profileFile = null;
            }
        }
    }

    static final class DumpComponentInfo {
        ParcelFileDescriptor fd;
        IBinder token;
        String prefix;
        String[] args;
    }

    static final class ContextCleanupInfo {
        ContextImpl context;
        String what;
        String who;
    }

    static final class DumpHeapData {
        // Whether to dump the native or managed heap.
        public boolean managed;
        public boolean mallocInfo;
        public boolean runGc;
        // compression format to dump bitmaps, null if no bitmaps to be dumped
        public String dumpBitmaps;
        String path;
        ParcelFileDescriptor fd;
        RemoteCallback finishCallback;
    }

    static final class DumpResourcesData {
        public ParcelFileDescriptor fd;
        public RemoteCallback finishCallback;
    }

    static final class UpdateCompatibilityData {
        String pkg;
        CompatibilityInfo info;
    }

    static final class RequestAssistContextExtras {
        IBinder activityToken;
        IBinder requestToken;
        int requestType;
        int sessionId;
        int flags;
    }

    static final class RequestHandoffActivityData {
        List<IBinder> activityTokens;
        IBinder requestToken;
    }

    // A list of receivers and an index into the receiver to be processed next.
    static final class ReceiverList {
        List<ReceiverInfo> receivers;
        int index;
    }

    @RavenwoodKeepPartialClass
    private class ApplicationThread extends IApplicationThread.Stub {
        @RavenwoodKeep
        ApplicationThread() {
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            int callingUid = Binder.getCallingUid();
            if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID) {
                String packageName;
                if (callingUid == Process.SHELL_UID) {
                    // save lookup, we know and expect this
                    packageName = "com.android.shell";
                } else {
                    String[] packagesForUid = getPackageManager().getPackagesForUid(callingUid);
                    if (packagesForUid == null || packagesForUid.length == 0) {
                        packageName = "unknown";
                    } else if (packagesForUid.length == 1) {
                        packageName = packagesForUid[0];
                    } else {
                        // shared UID - just use first package name and annotate it
                        packageName = packagesForUid[0] + "*";
                    }
                }
                boolean fail = callingUid != Process.SHELL_UID;
                if (Build.IS_DEBUGGABLE || fail) {
                    Slog.wtf(TAG, "ApplicationThread called by non-system process"
                            + " (callingUid: " + callingUid
                            + "; packageName: " + packageName
                            + "; code: " + code
                            + "; flags: " + flags
                            + "; behavior: " + (fail ? "fail" : "log")
                            + ")");
                }
                if (fail) {
                    throw new SecurityException(
                            "ApplicationThread called by non-system process"
                                    + " (callingUid: " + callingUid
                                    + "; packageName: " + packageName + ")");
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static final String DB_CONNECTION_INFO_HEADER = "  %8s %8s %14s %5s %5s %5s  %s";
        private static final String DB_CONNECTION_INFO_FORMAT = "  %8s %8s %14s %5d %5d %5d  %s";
        private static final String DB_POOL_INFO_HEADER = "  %13s %13s %13s  %s";
        private static final String DB_POOL_INFO_FORMAT = "  %13d %13d %13d  %s";

        @Override
        public final void scheduleReceiver(Intent intent, ActivityInfo info,
                CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
                int sendingUid, String sendingPackage) {
            updateProcessState(processState, false);
            ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                    ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
                    sendingUid, sendingPackage);
            r.info = info;
            if (DEBUG_STORE_ENABLED) {
                DebugStore.recordScheduleBroadcastReceive(System.identityHashCode(r), intent);
            }
            sendMessage(H.RECEIVER, r);
        }

        @Override
        public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
            for (int i = 0; i < info.size(); i++) {
                ReceiverInfo r = info.get(i);
                if (r.registered) {
                    scheduleRegisteredReceiver(r.receiver, r.intent,
                            r.resultCode, r.data, r.extras, r.ordered, r.sticky,
                            r.assumeDelivered, r.sendingUser, r.processState,
                            r.sendingUid, r.sendingPackage);
                } else {
                    scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
                            r.resultCode, r.data, r.extras, r.sync,
                            r.assumeDelivered, r.sendingUser, r.processState,
                            r.sendingUid, r.sendingPackage);
                }
            }
        }

        @Override
        public final void scheduleCreateBackupAgent(ApplicationInfo app,
                int backupMode, int userId, @BackupDestination int backupDestination) {
            CreateBackupAgentData d = new CreateBackupAgentData();
            d.appInfo = app;
            d.backupMode = backupMode;
            d.userId = userId;
            d.backupDestination = backupDestination;

            sendMessage(H.CREATE_BACKUP_AGENT, d);
        }

        @Override
        public final void scheduleDestroyBackupAgent(ApplicationInfo app, int userId) {
            CreateBackupAgentData d = new CreateBackupAgentData();
            d.appInfo = app;
            d.userId = userId;

            sendMessage(H.DESTROY_BACKUP_AGENT, d);
        }

        @Override
        public final void scheduleCreateService(IBinder token,
                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;

            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "scheduleCreateService. token="
                        + token);
            }
            if (DEBUG_STORE_ENABLED) {
                DebugStore.recordScheduleServiceCreate(System.identityHashCode(s), info);
            }
            sendMessage(H.CREATE_SERVICE, s);
        }

        @Override
        public final void scheduleBindService(IBinder token, IBinder bindToken, Intent intent,
                boolean rebind, int processState, long bindSeq) {
            updateProcessState(processState, false);
            BindServiceData s = new BindServiceData();
            s.token = token;
            s.bindToken = bindToken;
            s.intent = intent;
            s.rebind = rebind;
            s.bindSeq = bindSeq;

            if (DEBUG_SERVICE)
                Slog.v(TAG, "scheduleBindService token=" + token + " bindToken=" + bindToken
                        + " intent=" + intent + " uid=" + Binder.getCallingUid()
                        + " pid=" + Binder.getCallingPid());

            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "scheduleBindService. token="
                        + token + " bindSeq=" + bindSeq);
            }
            if (DEBUG_STORE_ENABLED) {
                DebugStore.recordScheduleServiceBind(System.identityHashCode(s), s.intent);
            }
            sendMessage(H.BIND_SERVICE, s);
        }

        @Override
        public final void scheduleUnbindService(IBinder token, IBinder bindToken, Intent intent) {
            BindServiceData s = new BindServiceData();
            s.token = token;
            s.bindToken = bindToken;
            s.intent = intent;
            s.bindSeq = -1;

            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "scheduleUnbindService. token="
                        + token);
            }
            sendMessage(H.UNBIND_SERVICE, s);
        }

        @Override
        public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
            List<ServiceStartArgs> list = args.getList();

            for (int i = 0; i < list.size(); i++) {
                ServiceStartArgs ssa = list.get(i);
                ServiceArgsData s = new ServiceArgsData();
                s.token = token;
                s.taskRemoved = ssa.taskRemoved;
                s.startId = ssa.startId;
                s.flags = ssa.flags;
                s.args = ssa.args;

                if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                    Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "scheduleServiceArgs. token="
                            + token + " startId=" + s.startId);
                }
                if (DEBUG_STORE_ENABLED) {
                    DebugStore.recordScheduleServiceStart(System.identityHashCode(s), s.args);
                }
                sendMessage(H.SERVICE_ARGS, s);
            }
        }

        @Override
        public final void scheduleStopService(IBinder token) {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "scheduleStopService. token="
                        + token);
            }
            sendMessage(H.STOP_SERVICE, token);
        }

        @Override
        public final void scheduleTimeoutService(IBinder token, int startId) {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "scheduleTimeoutService. token="
                        + token);
            }
            sendMessage(H.TIMEOUT_SERVICE, token, startId);
        }

        @Override
        public final void schedulePing(RemoteCallback pong) {
            sendMessage(H.PING, pong);
        }

        @Override
        public final void scheduleTimeoutServiceForType(IBinder token, int startId,
                @ServiceInfo.ForegroundServiceType int fgsType) {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "scheduleTimeoutServiceForType. token=" + token);
            }
            sendMessage(H.TIMEOUT_SERVICE_FOR_TYPE, token, startId, fgsType);
        }

        @Override
        @RavenwoodThrow(comment = "See ActivityThread_ravenwood for initialization on Ravenwood")
        public final void bindApplication(
                String processName,
                ApplicationInfo appInfo,
                String sdkSandboxClientAppVolumeUuid,
                String sdkSandboxClientAppPackage,
                boolean isSdkInSandbox,
                ProviderInfoList providerList,
                ComponentName instrumentationName,
                ProfilerInfo profilerInfo,
                Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection,
                int debugMode,
                boolean enableBinderTracking,
                boolean trackAllocation,
                boolean isRestrictedBackupMode,
                boolean persistent,
                Configuration config,
                CompatibilityInfo compatInfo,
                Map services,
                Bundle coreSettings,
                String buildSerial,
                AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions,
                long[] disabledCompatChanges,
                long[] enabledCompatChanges,
                long[] loggableCompatChanges,
                boolean logChangeChecksToStatsD,
                SharedMemory serializedSystemFontMap,
                FileDescriptor applicationSharedMemoryFd,
                long startRequestedElapsedTime,
                long startRequestedUptime) {
            if (DEBUG_STORE_ENABLED) {
                DebugStore.recordScheduleBindApplication();
            }
            if (services != null) {
                if (false) {
                    // Test code to make sure the app could see the passed-in services.
                    for (Object oname : services.keySet()) {
                        if (services.get(oname) == null) {
                            continue; // AM just passed in a null service.
                        }
                        String name = (String) oname;

                        // See b/79378449 about the following exemption.
                        switch (name) {
                            case "package":
                            case Context.WINDOW_SERVICE:
                                continue;
                        }

                        if (ServiceManager.getService(name) == null) {
                            Log.wtf(TAG, "Service " + name + " should be accessible by this app");
                        }
                    }
                }

                // Setup the service cache in the ServiceManager
                ServiceManager.initServiceCache(services);
            }

            // This must be initialized as early as possible to ensure availability for any
            // downstream callers.
            ApplicationSharedMemory instance =
                    ApplicationSharedMemory.fromFileDescriptor(
                            applicationSharedMemoryFd, /* mutable= */ false);
            SystemFeaturesCache.setInstance(
                    new SystemFeaturesCache(instance.readSystemFeaturesCache()));
            instance.closeFileDescriptor();
            ApplicationSharedMemory.setInstance(instance);

            setCoreSettings(coreSettings);

            // Register the app for tracing as early as possible
            if (android.os.Flags.perfettoSdkTracingEnableAppRegistration()) {
                // TODO(b/303199244): This is a temporary solution for Perfetto SDK tracing rollout.
                // Tracing is enabled only for apps in a specific allowlist. The sharding can be
                // disabled for local debugging via the
                // 'perfetto_sdk_tracing_disable_app_registration_sharding' flag, allowing all apps
                // to register. This flag will never be enabled in the field.
                if (android.os.Flags.perfettoSdkTracingDisableAppRegistrationSharding() ||
                    PERFETTO_TRACING_ALLOWLIST.contains(appInfo.packageName)) {
                    Trace.registerWithPerfetto();
                }
            }

            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.sdkSandboxClientAppVolumeUuid = sdkSandboxClientAppVolumeUuid;
            data.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage;
            data.isSdkInSandbox = isSdkInSandbox;
            data.providers = providerList.getList();
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillOptions = autofillOptions;
            data.contentCaptureOptions = contentCaptureOptions;
            data.disabledCompatChanges = disabledCompatChanges;
            data.enabledCompatChanges = enabledCompatChanges;
            data.mLoggableCompatChanges = loggableCompatChanges;
            data.mLogChangeChecksToStatsD = logChangeChecksToStatsD;
            data.mSerializedSystemFontMap = serializedSystemFontMap;
            data.startRequestedElapsedTime = startRequestedElapsedTime;
            data.startRequestedUptime = startRequestedUptime;
            updateCompatOverrideScale(compatInfo);
            updateCameraCompatInfo(compatInfo);
            CompatibilityInfo.applyOverrideIfNeeded(config);
            sendMessage(H.BIND_APPLICATION, data);
        }

        private void updateCompatOverrideScale(CompatibilityInfo info) {
            if (info.hasOverrideScaling()) {
                CompatibilityInfo.setOverrideInvertedScale(info.applicationInvertedScale,
                        info.applicationDensityInvertedScale, info.overrideDensityDisplayIds);
            } else {
                CompatibilityInfo.setOverrideInvertedScale(/* invertScale */ 1f,
                        /* densityInvertScale */1f, /* densityDisplayIds */ null);
            }
        }

        private void updateCameraCompatInfo(@NonNull CompatibilityInfo info) {
            if (info.isOverrideCameraCompatibilityInfoRequired()) {
                CompatibilityInfo.setCameraCompatibilityInfo(info.cameraCompatibilityInfo);
            } else {
                CompatibilityInfo.resetCameraCompatibilityInfo();
            }
        }

        @Override
        public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = entryPoint;
            args.arg2 = entryPointArgs;
            sendMessage(H.RUN_ISOLATED_ENTRY_POINT, args);
        }

        @Override
        public final void scheduleExit() {
            sendMessage(H.EXIT_APPLICATION, null);
        }

        @Override
        public final void scheduleSuicide() {
            sendMessage(H.SUICIDE, null);
        }

        @Override
        public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
            synchronized (mResourcesManager) {
                var oldAi = mPendingAppInfoUpdates.put(ai.packageName, ai);
                if (oldAi != null && oldAi.createTimestamp > ai.createTimestamp) {
                    Slog.w(TAG, "Skipping application info changed for obsolete AI with TS "
                            + ai.createTimestamp + " < already pending TS "
                            + oldAi.createTimestamp);
                    mPendingAppInfoUpdates.put(ai.packageName, oldAi);
                    return;
                }
            }
            mResourcesManager.appendPendingAppInfoUpdate(new String[]{ai.sourceDir}, ai);
            mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai.packageName);
            sendMessage(H.APPLICATION_INFO_CHANGED, ai.packageName);
        }

        @Override
        public void updateTimeZone() {
            TimeZone.setDefault(null);
        }

        @Override
        public void clearDnsCache() {
            // a non-standard API to get this to libcore
            InetAddress.clearDnsCache();
            // Allow libcore to perform the necessary actions as it sees fit upon a network
            // configuration change.
            NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();
        }

        @Override
        public void updateHttpProxy() {
            final Application app;
            synchronized (ActivityThread.this) {
                app = getApplication();
                if (null == app) {
                    // The app is not bound yet. Make a note to update the HTTP proxy when the
                    // app is bound.
                    mUpdateHttpProxyOnBind = true;
                    return;
                }
            }
            // App is present, update the proxy inline.
            ActivityThread.updateHttpProxy(app);
        }

        @Override
        public void processInBackground() {
            mH.removeMessages(H.GC_WHEN_IDLE);
            mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
        }

        @Override
        public void dumpService(ParcelFileDescriptor pfd, IBinder servicetoken, String[] args) {
            DumpComponentInfo data = new DumpComponentInfo();
            try {
                data.fd = pfd.dup();
                data.token = servicetoken;
                data.args = args;
                sendMessage(H.DUMP_SERVICE, data, 0, 0, true /*async*/);
            } catch (IOException e) {
                Slog.w(TAG, "dumpService failed", e);
            } finally {
                IoUtils.closeQuietly(pfd);
            }
        }

        // This function exists to make sure all receiver dispatching is
        // correctly ordered, since these are one-way calls and the binder driver
        // applies transaction ordering per object for such calls.
        @Override
        public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
                int resultCode, String dataStr, Bundle extras, boolean ordered,
                boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
                int sendingUid, String sendingPackage)
                throws RemoteException {
            updateProcessState(processState, false);

            // We can't modify IIntentReceiver due to UnsupportedAppUsage, so
            // try our best to shortcut to known subclasses, and alert if
            // registered using a custom IIntentReceiver that isn't able to
            // report an expected delivery event
            if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
                ((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
                        resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser,
                        sendingUid, sendingPackage);
            } else {
                if (!assumeDelivered) {
                    Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
                            + " and " + intent + " without mechanism to finish delivery");
                }
                if (sendingUid != Process.INVALID_UID || sendingPackage != null) {
                    Log.wtf(TAG,
                            "scheduleRegisteredReceiver() called for " + receiver + " and " + intent
                                    + " from " + sendingPackage + " (UID: " + sendingUid
                                    + ") without mechanism to propagate the sender's identity");
                }
                receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
                        sendingUser);
            }
        }

        @Override
        public void scheduleLowMemory() {
            sendMessage(H.LOW_MEMORY, null);
        }

        @Override
        public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
            sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
        }

        @Override
        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String dumpBitmaps,
                String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
            DumpHeapData dhd = new DumpHeapData();
            dhd.managed = managed;
            dhd.mallocInfo = mallocInfo;
            dhd.dumpBitmaps = dumpBitmaps;
            dhd.runGc = runGc;
            dhd.path = path;
            try {
                // Since we're going to dump the heap asynchronously, dup the file descriptor before
                // it's closed on returning from the IPC call.
                dhd.fd = fd.dup();
            } catch (IOException e) {
                Slog.e(TAG, "Failed to duplicate heap dump file descriptor", e);
                return;
            } finally {
                IoUtils.closeQuietly(fd);
            }
            dhd.finishCallback = finishCallback;
            sendMessage(H.DUMP_HEAP, dhd, 0, 0, true /*async*/);
        }

        @Override
        public void attachAgent(String agent) {
            sendMessage(H.ATTACH_AGENT, agent);
        }

        @Override
        public void attachStartupAgents(String dataDir) {
            sendMessage(H.ATTACH_STARTUP_AGENTS, dataDir);
        }

        @Override
        public void setSchedulingGroup(int group) {
            // Note: do this immediately, since going into the foreground
            // should happen regardless of what pending work we have to do
            // and the activity manager will wait for us to report back that
            // we are done before sending us to the background.
            try {
                Process.setProcessGroup(Process.myPid(), group);
            } catch (Exception e) {
                Slog.w(TAG, "Failed setting process group to " + group, e);
            }
        }

        @Override
        public void dispatchPackageBroadcast(int cmd, String[] packages) {
            sendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
        }

        @Override
        public void scheduleCrash(String msg, int typeId, @Nullable Bundle extras) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = msg;
            args.arg2 = extras;
            sendMessage(H.SCHEDULE_CRASH, args, typeId);
        }

        @Override
        public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) {
            DumpResourcesData data = new DumpResourcesData();
            try {
                data.fd = fd.dup();
                data.finishCallback = callback;
                sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/);
            } catch (IOException e) {
                Slog.w(TAG, "dumpResources failed", e);
            } finally {
                IoUtils.closeQuietly(fd);
            }
        }

        @Override
        public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
                String prefix, String[] args) {
            DumpComponentInfo data = new DumpComponentInfo();
            try {
                data.fd = pfd.dup();
                data.token = activitytoken;
                data.prefix = prefix;
                data.args = args;
                sendMessage(H.DUMP_ACTIVITY, data, 0, 0, true /*async*/);
            } catch (IOException e) {
                Slog.w(TAG, "dumpActivity failed", e);
            } finally {
                IoUtils.closeQuietly(pfd);
            }
        }

        @Override
        public void dumpProvider(ParcelFileDescriptor pfd, IBinder providertoken,
                String[] args) {
            DumpComponentInfo data = new DumpComponentInfo();
            try {
                data.fd = pfd.dup();
                data.token = providertoken;
                data.args = args;
                sendMessage(H.DUMP_PROVIDER, data, 0, 0, true /*async*/);
            } catch (IOException e) {
                Slog.w(TAG, "dumpProvider failed", e);
            } finally {
                IoUtils.closeQuietly(pfd);
            }
        }

        @NeverCompile
        @Override
        public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
                boolean dumpUnreachable, boolean dumpAllocatorStats, String[] args) {
            FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
            PrintWriter pw = new FastPrintWriter(fout);
            try {
                dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly,
                            dumpUnreachable, dumpAllocatorStats);
            } finally {
                pw.flush();
                IoUtils.closeQuietly(pfd);
            }
        }

        @NeverCompile
        private void dumpMemInfoNativeAllocations(PrintWriter pw) {
            pw.println(" ");
            pw.println(" Native Allocations");
            printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "Count", "", "Total(kB)");
            printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "------", "", "------");

            for (NativeAllocationRegistry.Metrics m : NativeAllocationRegistry.getMetrics()) {
                // group into 3 major categories: Bitmap, HardwareBuffer and Other
                final String className = switch (m.getClassName()) {
                    case "android.graphics.Bitmap" -> "Bitmap";
                    case "android.hardware.HardwareBuffer" -> "HardwareBuffer";
                    default -> "Other";
                };

                if (m.getMallocedCount() != 0 || m.getMallocedBytes() != 0) {
                    printRow(pw, TWO_COUNT_COLUMNS, className + " (malloced):",
                        m.getMallocedCount(), "", m.getMallocedBytes() / 1024);
                }
                if (m.getNonmallocedCount() != 0 || m.getNonmallocedBytes() != 0) {
                    printRow(pw, TWO_COUNT_COLUMNS, className + " (nonmalloced):",
                        m.getNonmallocedCount(), "", m.getNonmallocedBytes() / 1024);
                }
            }
        }

        @NeverCompile
        private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
                boolean dumpUnreachable, boolean dumpAllocatorStats) {
            long nativeMax = Debug.getNativeHeapSize() / 1024;
            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;

            Runtime runtime = Runtime.getRuntime();
            runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.
            long dalvikMax = runtime.totalMemory() / 1024;
            long dalvikFree = runtime.freeMemory() / 1024;
            long dalvikAllocated = dalvikMax - dalvikFree;

            Class[] classesToCount = new Class[] {
                    ContextImpl.class,
                    Activity.class,
                    WebView.class,
                    View.class,
                    ViewRootImpl.class
            };
            long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
            long appContextInstanceCount = instanceCounts[0];
            long activityInstanceCount = instanceCounts[1];
            long webviewInstanceCount = instanceCounts[2];
            long viewInstanceCount = instanceCounts[3];
            long viewRootInstanceCount = instanceCounts[4];

            int globalAssetCount = AssetManager.getGlobalAssetCount();
            int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
            int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
            int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
            int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
            long parcelSize = Parcel.getGlobalAllocSize();
            long parcelCount = Parcel.getGlobalAllocCount();
            SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();

            dumpMemInfoTable(pw, memInfo, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly,
                    Process.myPid(),
                    (mBoundApplication != null) ? mBoundApplication.processName : "unknown",
                    nativeMax, nativeAllocated, nativeFree,
                    dalvikMax, dalvikAllocated, dalvikFree);

            if (checkin) {
                // NOTE: if you change anything significant below, also consider changing
                // ACTIVITY_THREAD_CHECKIN_VERSION.

                // Object counts
                pw.print(viewInstanceCount); pw.print(',');
                pw.print(viewRootInstanceCount); pw.print(',');
                pw.print(appContextInstanceCount); pw.print(',');
                pw.print(activityInstanceCount); pw.print(',');

                pw.print(globalAssetCount); pw.print(',');
                pw.print(globalAssetManagerCount); pw.print(',');
                pw.print(binderLocalObjectCount); pw.print(',');
                pw.print(binderProxyObjectCount); pw.print(',');

                pw.print(binderDeathObjectCount); pw.print(',');

                // SQL
                pw.print(stats.memoryUsed / 1024); pw.print(',');
                pw.print(stats.memoryUsed / 1024); pw.print(',');
                pw.print(stats.pageCacheOverflow / 1024); pw.print(',');
                pw.print(stats.largestMemAlloc / 1024);
                for (int i = 0; i < stats.dbStats.size(); i++) {
                    DbStats dbStats = stats.dbStats.get(i);
                    pw.print(','); pw.print(dbStats.dbName);
                    pw.print(','); pw.print(dbStats.pageSize);
                    pw.print(','); pw.print(dbStats.dbSize);
                    pw.print(','); pw.print(dbStats.lookaside);
                    pw.print(','); pw.print(dbStats.cacheHits);
                    pw.print(','); pw.print(dbStats.cacheMisses);
                    pw.print(','); pw.print(dbStats.cacheSize);
                }
                pw.println();

                return;
            }

            pw.println(" ");
            pw.println(" Objects");
            printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewRootImpl:",
                    viewRootInstanceCount);

            printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", appContextInstanceCount,
                    "Activities:", activityInstanceCount);

            printRow(pw, TWO_COUNT_COLUMNS, "Assets:", globalAssetCount,
                    "AssetManagers:", globalAssetManagerCount);

            printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", binderLocalObjectCount,
                    "Proxy Binders:", binderProxyObjectCount);
            printRow(pw, TWO_COUNT_COLUMNS, "Parcel memory:", parcelSize/1024,
                    "Parcel count:", parcelCount);
            printRow(pw, TWO_COUNT_COLUMNS, "Death Recipients:", binderDeathObjectCount,
                    "WebViews:", webviewInstanceCount);

            if (com.android.libcore.readonly.Flags.nativeMetrics()) {
                dumpMemInfoNativeAllocations(pw);
            }

            // SQLite mem info
            pw.println(" ");
            pw.println(" SQL");
            printRow(pw, ONE_COUNT_COLUMN, "MEMORY_USED:", stats.memoryUsed / 1024);
            printRow(pw, TWO_COUNT_COLUMNS, "PAGECACHE_OVERFLOW:",
                    stats.pageCacheOverflow / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024);
            pw.println(" ");
            int N = stats.dbStats.size();
            if (N > 0) {
                pw.println(" DATABASES");
                printRow(pw, DB_CONNECTION_INFO_HEADER, "pgsz", "dbsz", "Lookaside(b)",
                        "cache hits", "cache misses", "cache size", "Dbname");
                pw.println("PER CONNECTION STATS");
                for (int i = 0; i < N; i++) {
                    DbStats dbStats = stats.dbStats.get(i);
                    if (dbStats.arePoolStats) {
                        // these will be printed after
                        continue;
                    }
                    printRow(pw, DB_CONNECTION_INFO_FORMAT,
                            (dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ",
                            (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
                            (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
                            dbStats.cacheHits, dbStats.cacheMisses, dbStats.cacheSize,
                            dbStats.dbName);
                }
                // Print stats accumulated through all the connections that have existed in the
                // pool since it was opened.
                pw.println("POOL STATS");
                printRow(pw, DB_POOL_INFO_HEADER, "cache hits", "cache misses", "cache size",
                        "Dbname");
                for (int i = 0; i < N; i++) {
                    DbStats dbStats = stats.dbStats.get(i);
                    if (!dbStats.arePoolStats) {
                        continue;
                    }
                    printRow(pw, DB_POOL_INFO_FORMAT, dbStats.cacheHits, dbStats.cacheMisses,
                            dbStats.cacheSize, dbStats.dbName);
                }
            }

            // Asset details.
            String assetAlloc = AssetManager.getAssetAllocations();
            if (assetAlloc != null) {
                pw.println(" ");
                pw.println(" Asset Allocations");
                pw.print(assetAlloc);
            }

            // Task Snapshot
            if (TaskSnapshotManager.isUsed()) {
                TaskSnapshotManager.getInstance().dump(pw);
            }

            // Unreachable native memory
            if (dumpUnreachable) {
                boolean showContents = ((mBoundApplication != null)
                    && ((mBoundApplication.appInfo.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0))
                    || android.os.Build.IS_DEBUGGABLE;
                pw.println(" ");
                pw.println(" Unreachable memory");
                pw.print(Debug.getUnreachableMemory(100, showContents));
            }
            if (dumpAllocatorStats) {
                Debug.logAllocatorStats();
            }

            pw.println(" ");
        }

        @NeverCompile
        @Override
        public void dumpMemInfoProto(ParcelFileDescriptor pfd, Debug.MemoryInfo mem,
                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
                boolean dumpUnreachable, String[] args) {
            ProtoOutputStream proto = new ProtoOutputStream(pfd.getFileDescriptor());
            try {
                dumpMemInfo(proto, mem, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
            } finally {
                proto.flush();
                IoUtils.closeQuietly(pfd);
            }
        }

        @NeverCompile
        private void dumpMemInfo(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
                boolean dumpFullInfo, boolean dumpDalvik,
                boolean dumpSummaryOnly, boolean dumpUnreachable) {
            long nativeMax = Debug.getNativeHeapSize() / 1024;
            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;

            Runtime runtime = Runtime.getRuntime();
            runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.
            long dalvikMax = runtime.totalMemory() / 1024;
            long dalvikFree = runtime.freeMemory() / 1024;
            long dalvikAllocated = dalvikMax - dalvikFree;

            Class[] classesToCount = new Class[] {
                    ContextImpl.class,
                    Activity.class,
                    WebView.class,
                    View.class,
                    ViewRootImpl.class
            };
            long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
            long appContextInstanceCount = instanceCounts[0];
            long activityInstanceCount = instanceCounts[1];
            long webviewInstanceCount = instanceCounts[2];
            long viewInstanceCount = instanceCounts[3];
            long viewRootInstanceCount = instanceCounts[4];

            int globalAssetCount = AssetManager.getGlobalAssetCount();
            int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
            int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
            int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
            int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
            long parcelSize = Parcel.getGlobalAllocSize();
            long parcelCount = Parcel.getGlobalAllocCount();
            SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();

            final long mToken = proto.start(MemInfoDumpProto.AppData.PROCESS_MEMORY);
            proto.write(MemInfoDumpProto.ProcessMemory.PID, Process.myPid());
            proto.write(MemInfoDumpProto.ProcessMemory.PROCESS_NAME,
                    (mBoundApplication != null) ? mBoundApplication.processName : "unknown");
            dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly,
                    nativeMax, nativeAllocated, nativeFree,
                    dalvikMax, dalvikAllocated, dalvikFree);
            proto.end(mToken);

            final long oToken = proto.start(MemInfoDumpProto.AppData.OBJECTS);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT,
                    viewInstanceCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT,
                    viewRootInstanceCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT,
                    appContextInstanceCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT,
                    activityInstanceCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT,
                    globalAssetCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT,
                    globalAssetManagerCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT,
                    binderLocalObjectCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT,
                    binderProxyObjectCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_MEMORY_KB,
                    parcelSize / 1024);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT,
                    binderDeathObjectCount);
            proto.write(MemInfoDumpProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT,
                    webviewInstanceCount);
            proto.end(oToken);

            // SQLite mem info
            final long sToken = proto.start(MemInfoDumpProto.AppData.SQL);
            proto.write(MemInfoDumpProto.AppData.SqlStats.MEMORY_USED_KB,
                    stats.memoryUsed / 1024);
            proto.write(MemInfoDumpProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB,
                    stats.pageCacheOverflow / 1024);
            proto.write(MemInfoDumpProto.AppData.SqlStats.MALLOC_SIZE_KB,
                    stats.largestMemAlloc / 1024);
            int n = stats.dbStats.size();
            for (int i = 0; i < n; i++) {
                DbStats dbStats = stats.dbStats.get(i);

                final long dToken = proto.start(MemInfoDumpProto.AppData.SqlStats.DATABASES);
                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.NAME, dbStats.dbName);
                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize);
                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.LOOKASIDE_B,
                        dbStats.lookaside);
                proto.write(
                        MemInfoDumpProto.AppData.SqlStats.Database.CACHE_HITS, dbStats.cacheHits);
                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE_MISSES,
                        dbStats.cacheMisses);
                proto.write(
                        MemInfoDumpProto.AppData.SqlStats.Database.CACHE_SIZE, dbStats.cacheSize);
                proto.end(dToken);
            }
            proto.end(sToken);

            // Asset details.
            String assetAlloc = AssetManager.getAssetAllocations();
            if (assetAlloc != null) {
                proto.write(MemInfoDumpProto.AppData.ASSET_ALLOCATIONS, assetAlloc);
            }

            // Unreachable native memory
            if (dumpUnreachable) {
                int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags;
                boolean showContents = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
                        || android.os.Build.IS_DEBUGGABLE;
                proto.write(MemInfoDumpProto.AppData.UNREACHABLE_MEMORY,
                        Debug.getUnreachableMemory(100, showContents));
            }
        }

        @Override
        public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) {
            DumpComponentInfo data = new DumpComponentInfo();
            try {
                data.fd = pfd.dup();
                data.token = null;
                data.args = args;
                sendMessage(H.DUMP_GFXINFO, data, 0, 0, true /*async*/);
            } catch (IOException e) {
                Slog.w(TAG, "dumpGfxInfo failed", e);
            } finally {
                IoUtils.closeQuietly(pfd);
            }
        }

        @Override
        @NeverCompile
        public void dumpBitmapsProto(ParcelFileDescriptor pfd, String dumpFormat) {
            try {
                int pid = Process.myPid();
                String processName = (mBoundApplication != null)
                    ? mBoundApplication.processName
                    : Process.myProcessName();
                ActivityThread.dumpBitmapsProto(new ProtoOutputStream(pfd.getFileDescriptor()),
                    pid, processName, dumpFormat);
            } finally {
                IoUtils.closeQuietly(pfd);
            }
        }

        @Override
        public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
            try {
                PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
                BroadcastStickyCache.dumpCacheInfo(pfd);
            } finally {
                IoUtils.closeQuietly(pfd);
            }
        }

        private File getDatabasesDir(Context context) {
            // There's no simple way to get the databases/ path, so do it this way.
            return context.getDatabasePath("a").getParentFile();
        }

        private void dumpDatabaseInfo(ParcelFileDescriptor pfd, String[] args, boolean isSystem) {
            PrintWriter pw = new FastPrintWriter(
                    new FileOutputStream(pfd.getFileDescriptor()));
            PrintWriterPrinter printer = new PrintWriterPrinter(pw);
            SQLiteDebug.dump(printer, args, isSystem);
            pw.flush();
        }

        @Override
        public void dumpDbInfo(final ParcelFileDescriptor pfd, final String[] args) {
            if (mSystemThread) {
                // Ensure this invocation is asynchronous to prevent writer waiting if buffer cannot
                // be consumed. But it must duplicate the file descriptor first, since caller might
                // be closing it.
                final ParcelFileDescriptor dup;
                try {
                    dup = pfd.dup();
                } catch (IOException e) {
                    Log.w(TAG, "Could not dup FD " + pfd.getFileDescriptor().getInt$());
                    return;
                } finally {
                    IoUtils.closeQuietly(pfd);
                }

                AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            dumpDatabaseInfo(dup, args, true);
                        } finally {
                            IoUtils.closeQuietly(dup);
                        }
                    }
                });
            } else {
                dumpDatabaseInfo(pfd, args, false);
                IoUtils.closeQuietly(pfd);
            }
        }

        @Override
        public void unstableProviderDied(IBinder provider) {
            sendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
        }

        @Override
        public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
                int requestType, int sessionId, int flags) {
            RequestAssistContextExtras cmd = new RequestAssistContextExtras();
            cmd.activityToken = activityToken;
            cmd.requestToken = requestToken;
            cmd.requestType = requestType;
            cmd.sessionId = sessionId;
            cmd.flags = flags;
            sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
        }

        /**
         * Requests the data needed to start a handoff activity.
         *
         * @param requestToken The token of the request, used by ActivityTaskManager to identify
         *                     the request.
         * @param activityTokens The tokens of the activities to hand off.
         */
        @Override
        public void requestHandoffActivityData(IBinder requestToken, List<IBinder> activityTokens) {
            RequestHandoffActivityData cmd = new RequestHandoffActivityData();
            cmd.activityTokens = activityTokens;
            cmd.requestToken = requestToken;
            sendMessage(H.REQUEST_HANDOFF_ACTIVITY_DATA, cmd);
        }

        @Override
        public void setCoreSettings(Bundle coreSettings) {
            sendMessage(H.SET_CORE_SETTINGS, coreSettings);
        }

        @Override
        public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) {
            UpdateCompatibilityData ucd = new UpdateCompatibilityData();
            ucd.pkg = pkg;
            ucd.info = info;
            updateCompatOverrideScale(info);
            updateCameraCompatInfo(info);
            sendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
        }

        @Override
        public void scheduleTrimMemory(int level) {
            final Runnable r = PooledLambda.obtainRunnable(ActivityThread::handleTrimMemory,
                    ActivityThread.this, level).recycleOnUse();
            // Schedule trimming memory after drawing the frame to minimize jank-risk.
            Choreographer choreographer = Choreographer.getMainThreadInstance();
            if (choreographer != null) {
                choreographer.postCallback(Choreographer.CALLBACK_COMMIT, r, null);
            } else {
                mH.post(r);
            }
        }

        @Override
        public void scheduleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
            sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
        }

        @Override
        public void scheduleOnNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
            sendMessage(H.ON_NEW_SCENE_TRANSITION_INFO,
                    new Pair<IBinder, SceneTransitionInfo>(token, info));
        }

        @Override
        public void setProcessState(int state) {
            updateProcessState(state, true);
        }

        /**
         * Updates {@link #mNetworkBlockSeq}. This is used by ActivityManagerService to inform
         * the main thread that it needs to wait for the network rules to get updated before
         * launching an activity.
         */
        @Override
        public void setNetworkBlockSeq(long procStateSeq) {
            synchronized (mNetworkPolicyLock) {
                mNetworkBlockSeq = procStateSeq;
            }
        }

        @Override
        public void scheduleInstallProvider(ProviderInfo provider) {
            sendMessage(H.INSTALL_PROVIDER, provider);
        }

        @Override
        public final void updateTimePrefs(int timeFormatPreference) {
            final Boolean timeFormatPreferenceBool;
            // For convenience we are using the Intent extra values.
            if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR) {
                timeFormatPreferenceBool = Boolean.FALSE;
            } else if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR) {
                timeFormatPreferenceBool = Boolean.TRUE;
            } else {
                // timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT
                // (or unknown).
                timeFormatPreferenceBool = null;
            }
            DateFormat.set24HourTimePref(timeFormatPreferenceBool);
        }

        @Override
        public void scheduleEnterAnimationComplete(IBinder token) {
            sendMessage(H.ENTER_ANIMATION_COMPLETE, token);
        }

        @Override
        public void notifyCleartextNetwork(byte[] firstPacket) {
            if (StrictMode.vmCleartextNetworkEnabled()) {
                StrictMode.onCleartextNetworkDetected(firstPacket);
            }
        }

        @Override
        public void startBinderTracking() {
            sendMessage(H.START_BINDER_TRACKING, null);
        }

        @Override
        public void stopBinderTrackingAndDump(ParcelFileDescriptor pfd) {
            try {
                sendMessage(H.STOP_BINDER_TRACKING_AND_DUMP, pfd.dup());
            } catch (IOException e) {
            } finally {
                IoUtils.closeQuietly(pfd);
            }
        }

        @Override
        public void scheduleLocalVoiceInteractionStarted(IBinder token,
                IVoiceInteractor voiceInteractor) throws RemoteException {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = token;
            args.arg2 = voiceInteractor;
            sendMessage(H.LOCAL_VOICE_INTERACTION_STARTED, args);
        }

        @Override
        public void handleTrustStorageUpdate() {
            NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
        }

        @Override
        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
            ActivityThread.this.scheduleTransaction(transaction);
        }

        @Override
        public void scheduleTaskFragmentTransaction(@NonNull ITaskFragmentOrganizer organizer,
                @NonNull TaskFragmentTransaction transaction) throws RemoteException {
            // TODO(b/352665082): ITaskFragmentOrganizer can be cleanup to be a IBinder token
            organizer.onTransactionReady(transaction);
        }

        @Override
        public void requestDirectActions(@NonNull IBinder activityToken,
                @NonNull IVoiceInteractor interactor, @Nullable RemoteCallback cancellationCallback,
                @NonNull RemoteCallback callback) {
            final CancellationSignal cancellationSignal = new CancellationSignal();
            if (cancellationCallback != null) {
                final ICancellationSignal transport = createSafeCancellationTransport(
                        cancellationSignal);
                final Bundle cancellationResult = new Bundle();
                cancellationResult.putBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL,
                        transport.asBinder());
                cancellationCallback.sendResult(cancellationResult);
            }
            mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
                    ActivityThread.this, activityToken, interactor, cancellationSignal, callback,
                    REQUEST_DIRECT_ACTIONS_RETRY_MAX_COUNT));
        }

        @Override
        public void performDirectAction(@NonNull IBinder activityToken, @NonNull String actionId,
                @Nullable Bundle arguments, @Nullable RemoteCallback cancellationCallback,
                @NonNull RemoteCallback resultCallback) {
            final CancellationSignal cancellationSignal = new CancellationSignal();
            if (cancellationCallback != null) {
                final ICancellationSignal transport = createSafeCancellationTransport(
                        cancellationSignal);
                final Bundle cancellationResult = new Bundle();
                cancellationResult.putBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL,
                        transport.asBinder());
                cancellationCallback.sendResult(cancellationResult);
            }
            mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handlePerformDirectAction,
                    ActivityThread.this, activityToken, actionId, arguments,
                    cancellationSignal, resultCallback));
        }

        @Override
        public void notifyContentProviderPublishStatus(@NonNull ContentProviderHolder holder,
                @NonNull String authorities, int userId, boolean published) {
            final String auths[] = authorities.split(";");
            for (String auth: auths) {
                final ProviderKey key = getGetProviderKey(auth, userId);
                synchronized (key.mLock) {
                    key.mHolder = holder;
                    key.mLock.notifyAll();
                }
            }
        }

        @Override
        public void instrumentWithoutRestart(ComponentName instrumentationName,
                Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, ApplicationInfo targetInfo) {
            AppBindData data = new AppBindData();
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.appInfo = targetInfo;
            sendMessage(H.INSTRUMENT_WITHOUT_RESTART, data);
        }

        @Override
        public void updateUiTranslationState(IBinder activityToken, int state,
                TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
                UiTranslationSpec uiTranslationSpec) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = activityToken;
            args.arg2 = state;
            args.arg3 = sourceSpec;
            args.arg4 = targetSpec;
            args.arg5 = viewIds;
            args.arg6 = uiTranslationSpec;
            sendMessage(H.UPDATE_UI_TRANSLATION_STATE, args);
        }

        @Override
        public void getExecutableMethodFileOffsets(
                @NonNull MethodDescriptor methodDescriptor,
                @NonNull IOffsetCallback resultCallback) {
            Executable executable;
            if (android.security.Flags.dynamicInstrumentationAppClassloader()) {
                Application app = currentApplication();
                ClassLoader cl = (app != null) ? app.getClassLoader() : getClass().getClassLoader();
                executable = MethodDescriptorParser.parseMethodDescriptor(cl, methodDescriptor);
            } else {
                executable =
                        MethodDescriptorParser.parseMethodDescriptor(
                                getClass().getClassLoader(), methodDescriptor);
            }

            VMDebug.ExecutableMethodFileOffsets location;
            if (com.android.art.flags.Flags.executableMethodFileOffsetsV2()) {
                location = VMDebug.getExecutableMethodFileOffsets(executable);
            } else if (executable instanceof Method) {
                location = VMDebug.getExecutableMethodFileOffsets((Method) executable);
            } else {
                throw new UnsupportedOperationException();
            }
            try {
                if (location == null) {
                    resultCallback.onResult(null);
                    return;
                }
                ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
                ret.containerPath = location.getContainerPath();
                ret.containerOffset = location.getContainerOffset();
                ret.methodOffset = location.getMethodOffset();
                resultCallback.onResult(ret);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    private @NonNull SafeCancellationTransport createSafeCancellationTransport(
            @NonNull CancellationSignal cancellationSignal) {
        synchronized (ActivityThread.this) {
            if (mRemoteCancellations == null) {
                mRemoteCancellations = new ArrayMap<>();
            }
            final SafeCancellationTransport transport = new SafeCancellationTransport(
                    this, cancellationSignal);
            mRemoteCancellations.put(transport, cancellationSignal);
            return transport;
        }
    }

    private @NonNull CancellationSignal removeSafeCancellationTransport(
            @NonNull SafeCancellationTransport transport) {
        synchronized (ActivityThread.this) {
            final CancellationSignal cancellation = mRemoteCancellations.remove(transport);
            if (mRemoteCancellations.isEmpty()) {
                mRemoteCancellations = null;
            }
            return cancellation;
        }
    }

    private static final class SafeCancellationTransport extends ICancellationSignal.Stub {
        private final @NonNull WeakReference<ActivityThread> mWeakActivityThread;

        SafeCancellationTransport(@NonNull ActivityThread activityThread,
                @NonNull CancellationSignal cancellation) {
            mWeakActivityThread = new WeakReference<>(activityThread);
        }

        @Override
        public void cancel() {
            final ActivityThread activityThread = mWeakActivityThread.get();
            if (activityThread != null) {
                final CancellationSignal cancellation = activityThread
                        .removeSafeCancellationTransport(this);
                if (cancellation != null) {
                    cancellation.cancel();
                }
            }
        }
    }

    private void throwRemoteServiceException(String message, int typeId, @Nullable Bundle extras) {
        // Use a switch to ensure all the type IDs are unique.
        switch (typeId) {
            case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
                throw generateForegroundServiceDidNotStartInTimeException(message, extras);

            case ForegroundServiceDidNotStopInTimeException.TYPE_ID:
                throw generateForegroundServiceDidNotStopInTimeException(message, extras);

            case CannotPostForegroundServiceNotificationException.TYPE_ID:
                throw new CannotPostForegroundServiceNotificationException(message);

            case BadForegroundServiceNotificationException.TYPE_ID:
                throw new BadForegroundServiceNotificationException(message);

            case BadUserInitiatedJobNotificationException.TYPE_ID:
                throw new BadUserInitiatedJobNotificationException(message);

            case MissingRequestPasswordComplexityPermissionException.TYPE_ID:
                throw new MissingRequestPasswordComplexityPermissionException(message);

            case CrashedByAdbException.TYPE_ID:
                throw new CrashedByAdbException(message);

            case ExcessiveEnqueuedBroadcastsException.TYPE_ID:
                throw new ExcessiveEnqueuedBroadcastsException(message);

            default:
                throw new RemoteServiceException(message
                        + " (with unknown typeId:" + typeId + ")");
        }
    }

    private ForegroundServiceDidNotStartInTimeException
            generateForegroundServiceDidNotStartInTimeException(String message, Bundle extras) {
        final String serviceClassName =
                ForegroundServiceDidNotStartInTimeException.getServiceClassNameFromExtras(extras);
        final Exception inner = (serviceClassName == null) ? null
                : Service.getStartForegroundServiceStackTrace(serviceClassName);
        throw new ForegroundServiceDidNotStartInTimeException(message, inner);
    }

    private ForegroundServiceDidNotStopInTimeException
            generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) {
        final String serviceClassName =
                ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras);
        final Exception inner = (serviceClassName == null) ? null
                : Service.getStartForegroundServiceStackTrace(serviceClassName);
        throw new ForegroundServiceDidNotStopInTimeException(message, inner);
    }

    @RavenwoodKeepPartialClass
    class H extends Handler {
        @RavenwoodKeep
        H() {
        }

        public static final int BIND_APPLICATION        = 110;
        @UnsupportedAppUsage
        public static final int EXIT_APPLICATION        = 111;
        @UnsupportedAppUsage
        public static final int RECEIVER                = 113;
        @UnsupportedAppUsage
        public static final int CREATE_SERVICE          = 114;
        @UnsupportedAppUsage
        public static final int SERVICE_ARGS            = 115;
        @UnsupportedAppUsage
        public static final int STOP_SERVICE            = 116;

        public static final int CONFIGURATION_CHANGED   = 118;
        public static final int CLEAN_UP_CONTEXT        = 119;
        @UnsupportedAppUsage
        public static final int GC_WHEN_IDLE            = 120;
        @UnsupportedAppUsage
        public static final int BIND_SERVICE            = 121;
        @UnsupportedAppUsage
        public static final int UNBIND_SERVICE          = 122;
        public static final int DUMP_SERVICE            = 123;
        public static final int LOW_MEMORY              = 124;
        public static final int PROFILER_CONTROL        = 127;
        public static final int CREATE_BACKUP_AGENT     = 128;
        public static final int DESTROY_BACKUP_AGENT    = 129;
        public static final int SUICIDE                 = 130;
        @UnsupportedAppUsage
        public static final int REMOVE_PROVIDER         = 131;
        public static final int DISPATCH_PACKAGE_BROADCAST = 133;
        @UnsupportedAppUsage
        public static final int SCHEDULE_CRASH          = 134;
        public static final int DUMP_HEAP               = 135;
        public static final int DUMP_ACTIVITY           = 136;
        public static final int SLEEPING                = 137;
        public static final int SET_CORE_SETTINGS       = 138;
        public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
        @UnsupportedAppUsage
        public static final int DUMP_PROVIDER           = 141;
        public static final int UNSTABLE_PROVIDER_DIED  = 142;
        public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
        public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
        @UnsupportedAppUsage
        public static final int INSTALL_PROVIDER        = 145;
        public static final int ON_NEW_SCENE_TRANSITION_INFO = 146;
        @UnsupportedAppUsage
        public static final int ENTER_ANIMATION_COMPLETE = 149;
        public static final int START_BINDER_TRACKING = 150;
        public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
        public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
        public static final int ATTACH_AGENT = 155;
        public static final int APPLICATION_INFO_CHANGED = 156;
        public static final int RUN_ISOLATED_ENTRY_POINT = 158;
        public static final int EXECUTE_TRANSACTION = 159;
        public static final int RELAUNCH_ACTIVITY = 160;
        public static final int PURGE_RESOURCES = 161;
        public static final int ATTACH_STARTUP_AGENTS = 162;
        public static final int UPDATE_UI_TRANSLATION_STATE = 163;
        public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164;
        public static final int DUMP_GFXINFO = 165;
        public static final int DUMP_RESOURCES = 166;
        public static final int TIMEOUT_SERVICE = 167;
        public static final int PING = 168;

        public static final int INSTRUMENT_WITHOUT_RESTART = 170;
        public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;

        public static final int TIMEOUT_SERVICE_FOR_TYPE = 172;
        public static final int REQUEST_HANDOFF_ACTIVITY_DATA = 173;

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case BIND_APPLICATION: return "BIND_APPLICATION";
                    case EXIT_APPLICATION: return "EXIT_APPLICATION";
                    case RECEIVER: return "RECEIVER";
                    case CREATE_SERVICE: return "CREATE_SERVICE";
                    case SERVICE_ARGS: return "SERVICE_ARGS";
                    case STOP_SERVICE: return "STOP_SERVICE";
                    case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED";
                    case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT";
                    case GC_WHEN_IDLE: return "GC_WHEN_IDLE";
                    case BIND_SERVICE: return "BIND_SERVICE";
                    case UNBIND_SERVICE: return "UNBIND_SERVICE";
                    case DUMP_SERVICE: return "DUMP_SERVICE";
                    case LOW_MEMORY: return "LOW_MEMORY";
                    case PROFILER_CONTROL: return "PROFILER_CONTROL";
                    case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
                    case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
                    case SUICIDE: return "SUICIDE";
                    case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
                    case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
                    case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
                    case DUMP_HEAP: return "DUMP_HEAP";
                    case DUMP_ACTIVITY: return "DUMP_ACTIVITY";
                    case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS";
                    case UPDATE_PACKAGE_COMPATIBILITY_INFO:
                        return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
                    case DUMP_PROVIDER: return "DUMP_PROVIDER";
                    case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
                    case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
                    case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE";
                    case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
                    case ON_NEW_SCENE_TRANSITION_INFO: return "ON_NEW_SCENE_TRANSITION_INFO";
                    case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
                    case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
                    case ATTACH_AGENT: return "ATTACH_AGENT";
                    case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
                    case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT";
                    case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
                    case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
                    case PURGE_RESOURCES: return "PURGE_RESOURCES";
                    case ATTACH_STARTUP_AGENTS: return "ATTACH_STARTUP_AGENTS";
                    case UPDATE_UI_TRANSLATION_STATE: return "UPDATE_UI_TRANSLATION_STATE";
                    case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK:
                        return "SET_CONTENT_CAPTURE_OPTIONS_CALLBACK";
                    case DUMP_GFXINFO: return "DUMP GFXINFO";
                    case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART";
                    case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                        return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
                    case DUMP_RESOURCES: return "DUMP_RESOURCES";
                    case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
                    case PING: return "PING";
                    case TIMEOUT_SERVICE_FOR_TYPE: return "TIMEOUT_SERVICE_FOR_TYPE";
                    case REQUEST_HANDOFF_ACTIVITY_DATA: return "REQUEST_HANDOFF_ACTIVITY_DATA";
                }
            }
            return Integer.toString(code);
        }
        @Override
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            long debugStoreId = -1;
            // By default, log all long messages when the debug store is enabled,
            // unless this is overridden for certain message types, for which we have
            // more granular debug store logging.
            boolean shouldLogLongMessage = DEBUG_STORE_ENABLED;
            final long messageStartUptimeMs = SystemClock.uptimeMillis();
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    if (DEBUG_STORE_ENABLED) {
                        debugStoreId =
                                DebugStore.recordBindApplication();
                    }
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    if (DEBUG_STORE_ENABLED) {
                        DebugStore.recordEventEnd(debugStoreId);
                    }
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;
                case RECEIVER:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        ReceiverData rec = (ReceiverData) msg.obj;
                        if (rec.intent != null) {
                            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                                    "broadcastReceiveComp: " + rec.intent.getAction());
                        } else {
                            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                                    "broadcastReceiveComp");
                        }
                    }
                    ReceiverData receiverData = (ReceiverData) msg.obj;
                    try {
                        handleReceiver(receiverData);
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    }
                    break;
                case CREATE_SERVICE:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                                ("serviceCreate: " + String.valueOf(msg.obj)));
                    }
                    CreateServiceData createServiceData = (CreateServiceData) msg.obj;
                    if (DEBUG_STORE_ENABLED) {
                        debugStoreId =
                                DebugStore.recordServiceCreate(
                                        System.identityHashCode(msg.obj));
                    }

                    try {
                        handleCreateService(createServiceData);
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                        if (DEBUG_STORE_ENABLED) {
                            DebugStore.recordEventEnd(debugStoreId);
                            shouldLogLongMessage = false;
                        }
                    }
                    break;
                case BIND_SERVICE:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind: "
                                + String.valueOf(msg.obj));
                    }
                    BindServiceData bindData = (BindServiceData) msg.obj;
                    if (DEBUG_STORE_ENABLED) {
                        debugStoreId =
                                DebugStore.recordServiceBind(
                                        System.identityHashCode(msg.obj));
                    }
                    try {
                        handleBindService(bindData);
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                        if (DEBUG_STORE_ENABLED) {
                            DebugStore.recordEventEnd(debugStoreId);
                            shouldLogLongMessage = false;
                        }
                    }
                    break;
                case UNBIND_SERVICE:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind: "
                                + String.valueOf(msg.obj));
                    }
                    handleUnbindService((BindServiceData)msg.obj);
                    schedulePurgeIdler();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case SERVICE_ARGS:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                                ("serviceStart: " + String.valueOf(msg.obj)));
                    }
                    ServiceArgsData serviceData = (ServiceArgsData) msg.obj;
                    if (DEBUG_STORE_ENABLED) {
                        debugStoreId =
                                DebugStore.recordServiceStart(
                                        System.identityHashCode(msg.obj));
                    }

                    try {
                        handleServiceArgs(serviceData);
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                        if (DEBUG_STORE_ENABLED) {
                            DebugStore.recordEventEnd(debugStoreId);
                            shouldLogLongMessage = false;
                        }
                    }
                    break;
                case STOP_SERVICE:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop: "
                                + String.valueOf(msg.obj));
                    }
                    handleStopService((IBinder)msg.obj);
                    schedulePurgeIdler();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case TIMEOUT_SERVICE:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceTimeout: "
                                + String.valueOf(msg.obj));
                    }
                    handleTimeoutService((IBinder) msg.obj, msg.arg1);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case PING:
                    ((RemoteCallback) msg.obj).sendResult(null);
                    break;
                case TIMEOUT_SERVICE_FOR_TYPE:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                                "serviceTimeoutForType: " + msg.obj);
                    }
                    handleTimeoutServiceForType((IBinder) msg.obj, msg.arg1, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case CONFIGURATION_CHANGED:
                    mConfigurationController.handleConfigurationChanged((Configuration) msg.obj);
                    break;
                case CLEAN_UP_CONTEXT:
                    ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
                    cci.context.performFinalCleanup(cci.who, cci.what);
                    break;
                case GC_WHEN_IDLE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "gcWhenIdle");
                    try {
                        scheduleGcIdler();
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    }
                    break;
                case DUMP_SERVICE:
                    handleDumpService((DumpComponentInfo)msg.obj);
                    break;
                case DUMP_GFXINFO:
                    handleDumpGfxInfo((DumpComponentInfo) msg.obj);
                    break;
                case LOW_MEMORY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory");
                    handleLowMemory();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case PROFILER_CONTROL:
                    handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
                    break;
                case CREATE_BACKUP_AGENT:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent");
                    handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case DESTROY_BACKUP_AGENT:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupDestroyAgent");
                    handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case SUICIDE:
                    Process.killProcess(Process.myPid());
                    break;
                case REMOVE_PROVIDER:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove");
                    completeRemoveProvider((ProviderRefCount)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case DISPATCH_PACKAGE_BROADCAST:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage");
                    handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case SCHEDULE_CRASH: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    String message = (String) args.arg1;
                    Bundle extras = (Bundle) args.arg2;
                    args.recycle();
                    throwRemoteServiceException(message, msg.arg1, extras);
                    break;
                }
                case DUMP_HEAP:
                    handleDumpHeap((DumpHeapData) msg.obj);
                    break;
                case DUMP_RESOURCES:
                    handleDumpResources((DumpResourcesData) msg.obj);
                    break;
                case DUMP_ACTIVITY:
                    handleDumpActivity((DumpComponentInfo)msg.obj);
                    break;
                case DUMP_PROVIDER:
                    handleDumpProvider((DumpComponentInfo)msg.obj);
                    break;
                case SET_CORE_SETTINGS:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setCoreSettings");
                    handleSetCoreSettings((Bundle) msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case UPDATE_PACKAGE_COMPATIBILITY_INFO:
                    handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj);
                    break;
                case UNSTABLE_PROVIDER_DIED:
                    handleUnstableProviderDied((IBinder)msg.obj, false);
                    break;
                case REQUEST_ASSIST_CONTEXT_EXTRAS:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            "handleRequestAssistContextExtras");
                    handleRequestAssistContextExtras((RequestAssistContextExtras)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case TRANSLUCENT_CONVERSION_COMPLETE:
                    handleTranslucentConversionComplete((IBinder)msg.obj, msg.arg1 == 1);
                    break;
                case INSTALL_PROVIDER:
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerInstall: "
                                + String.valueOf(msg.obj));
                    }
                    try {
                        handleInstallProvider((ProviderInfo) msg.obj);
                    } finally {
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    }
                    break;
                case ON_NEW_SCENE_TRANSITION_INFO:
                    Pair<IBinder, SceneTransitionInfo> pair =
                            (Pair<IBinder, SceneTransitionInfo>) msg.obj;
                    onNewSceneTransitionInfo(pair.first, pair.second);
                    break;
                case ENTER_ANIMATION_COMPLETE:
                    handleEnterAnimationComplete((IBinder) msg.obj);
                    break;
                case START_BINDER_TRACKING:
                    handleStartBinderTracking();
                    break;
                case STOP_BINDER_TRACKING_AND_DUMP:
                    handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
                    break;
                case LOCAL_VOICE_INTERACTION_STARTED:
                    handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
                            (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
                    break;
                case ATTACH_AGENT: {
                    Application app = getApplication();
                    handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null);
                    break;
                }
                case APPLICATION_INFO_CHANGED:
                    applyPendingApplicationInfoChanges((String) msg.obj);
                    break;
                case RUN_ISOLATED_ENTRY_POINT:
                    handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
                            (String[]) ((SomeArgs) msg.obj).arg2);
                    break;
                case EXECUTE_TRANSACTION:
                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    final ClientTransactionListenerController controller =
                            ClientTransactionListenerController.getInstance();
                    controller.onClientTransactionStarted();
                    try {
                        mTransactionExecutor.execute(transaction);
                    } finally {
                        controller.onClientTransactionFinished();
                    }
                    break;
                case RELAUNCH_ACTIVITY:
                    handleRelaunchActivityLocally((IBinder) msg.obj);
                    break;
                case PURGE_RESOURCES:
                    schedulePurgeIdler();
                    break;
                case ATTACH_STARTUP_AGENTS:
                    handleAttachStartupAgents((String) msg.obj);
                    break;
                case UPDATE_UI_TRANSLATION_STATE:
                    final SomeArgs args = (SomeArgs) msg.obj;
                    updateUiTranslationState((IBinder) args.arg1, (int) args.arg2,
                            (TranslationSpec) args.arg3, (TranslationSpec) args.arg4,
                            (List<AutofillId>) args.arg5, (UiTranslationSpec) args.arg6);
                    break;
                case SET_CONTENT_CAPTURE_OPTIONS_CALLBACK:
                    handleSetContentCaptureOptionsCallback((String) msg.obj);
                    break;
                case INSTRUMENT_WITHOUT_RESTART:
                    handleInstrumentWithoutRestart((AppBindData) msg.obj);
                    break;
                case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                    handleFinishInstrumentationWithoutRestart();
                    break;
                case REQUEST_HANDOFF_ACTIVITY_DATA:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            "handleHandoffActivityDataRequest");
                    handleHandoffActivityDataRequest((RequestHandoffActivityData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
            }
            long messageElapsedTimeMs = SystemClock.uptimeMillis() - messageStartUptimeMs;
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
                ((SomeArgs) obj).recycle();
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
            if (shouldLogLongMessage
                    && messageElapsedTimeMs > LONG_MESSAGE_THRESHOLD_MS) {
                DebugStore.recordLongLooperMessage(msg.what, msg.getTarget().getClass().getName(),
                        messageElapsedTimeMs);
            }
        }
    }

    private class Idler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            boolean stopProfiling = false;
            if (mBoundApplication != null && mProfiler.profileFd != null
                    && mProfiler.autoStopProfiler) {
                stopProfiling = true;
            }
            final ActivityClient ac = ActivityClient.getInstance();
            while (mNewActivities.size() > 0) {
                final ActivityClientRecord a = mNewActivities.remove(0);
                if (localLOGV) {
                    Slog.v(TAG, "Reporting idle of " + a + " finished="
                            + (a.activity != null && a.activity.mFinished));
                }
                if (a.activity != null && !a.activity.mFinished) {
                    ac.activityIdle(a.token, a.createdConfig, stopProfiling);
                    a.createdConfig = null;
                }
            }
            if (stopProfiling) {
                mProfiler.stopProfiling();
            }
            return false;
        }
    }

    @RavenwoodKeepWholeClass(comment = "needed by ActivityThread constructor")
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        @RavenwoodIgnore
        public final boolean queueIdle() {
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }

    @RavenwoodKeepWholeClass(comment = "needed by ActivityThread constructor")
    final class PurgeIdler implements MessageQueue.IdleHandler {
        @Override
        @RavenwoodIgnore
        public boolean queueIdle() {
            purgePendingResources();
            return false;
        }
    }

    /** Backdoor to set private static fields */
    @RavenwoodReplace
    static void staticInitForRavenwood(ActivityThread instance) {
        throw new IllegalStateException(); // shouldn't be called on a real device.
    }

    static void staticInitForRavenwood$ravenwood(ActivityThread instance) {
        sCurrentActivityThread = instance;
        sMainThreadHandler = instance.getHandler();
    }

    @UnsupportedAppUsage
    @RavenwoodKeep
    public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
    }

    public static boolean isSystem() {
        return (sCurrentActivityThread != null) ? sCurrentActivityThread.mSystemThread : false;
    }

    public static String currentOpPackageName() {
        ActivityThread am = currentActivityThread();
        return (am != null && am.getApplication() != null)
                ? am.getApplication().getOpPackageName() : null;
    }

    public static AttributionSource currentAttributionSource() {
        ActivityThread am = currentActivityThread();
        return (am != null && am.getApplication() != null)
                ? am.getApplication().getAttributionSource() : null;
    }

    @UnsupportedAppUsage
    @RavenwoodKeep
    public static String currentPackageName() {
        ActivityThread am = currentActivityThread();
        return (am != null && am.mBoundApplication != null)
            ? am.mBoundApplication.appInfo.packageName : null;
    }

    @UnsupportedAppUsage
    public static String currentProcessName() {
        ActivityThread am = currentActivityThread();
        return (am != null && am.mBoundApplication != null)
            ? am.mBoundApplication.processName : null;
    }

    @UnsupportedAppUsage
    @RavenwoodKeep
    public static Application currentApplication() {
        ActivityThread am = currentActivityThread();
        return am != null ? am.mInitialApplication : null;
    }

    /**
     * Same as {@code ActivityThread.currentActivityThread().getSystemContext()}, but
     * it'll return a {@link Context} (not a {@link ContextImpl}) and is supported on Ravenwood.
     */
    @RavenwoodKeep
    public static Context currentSystemContext() {
        ActivityThread am = currentActivityThread();
        return am != null ? am.getSystemContext() : null;
    }

    @UnsupportedAppUsage
    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
        final IBinder b = ServiceManager.getService("package");
        sPackageManager = IPackageManager.Stub.asInterface(b);
        return sPackageManager;
    }

    /** Returns the permission manager */
    public static IPermissionManager getPermissionManager() {
        if (sPermissionManager != null) {
            return sPermissionManager;
        }
        final IBinder b = ServiceManager.getService("permissionmgr");
        sPermissionManager = IPermissionManager.Stub.asInterface(b);
        return sPermissionManager;
    }

    /**
     * Creates the top level resources for the given package. Will return an existing
     * Resources if one has already been created.
     */
    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] legacyOverlayDirs,
                    String[] overlayPaths, String[] libDirs, LoadedApk pkgInfo,
                    Configuration overrideConfig) {
        return mResourcesManager.getResources(null, resDir, splitResDirs, legacyOverlayDirs,
                overlayPaths, libDirs, null, overrideConfig, pkgInfo.getCompatibilityInfo(),
                pkgInfo.getClassLoader(), null);
    }

    @UnsupportedAppUsage
    @RavenwoodKeep
    public Handler getHandler() {
        return mH;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
            int flags) {
        return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId());
    }

    public LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
            int flags, int userId) {
        final boolean differentUser = (UserHandle.myUserId() != userId);
        ApplicationInfo ai = PackageManager.getApplicationInfoAsUserCached(
                packageName,
                PackageManager.GET_SHARED_LIBRARY_FILES
                | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                (userId < 0) ? UserHandle.myUserId() : userId);
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                // Caching not supported across users
                ref = null;
            } else if ((flags & Context.CONTEXT_INCLUDE_CODE) != 0) {
                ref = mPackages.get(packageName);
            } else {
                ref = mResourcePackages.get(packageName);
            }

            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (ai != null && packageInfo != null) {
                if (!isLoadedApkResourceDirsUpToDate(packageInfo, ai)) {
                    List<String> oldPaths = new ArrayList<>();
                    LoadedApk.makePaths(this, ai, oldPaths);
                    packageInfo.updateApplicationInfo(ai, oldPaths);
                }

                if (packageInfo.isSecurityViolation()
                        && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) {
                    throw new SecurityException(
                            "Requesting code from " + packageName
                            + " to be run in process "
                            + mBoundApplication.processName
                            + "/" + mBoundApplication.appInfo.uid);
                }
                return packageInfo;
            }
        }

        if (ai != null) {
            return getPackageInfo(ai, compatInfo, flags);
        }

        return null;
    }

    @UnsupportedAppUsage(trackingBug = 171933273)
    public LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
            int flags) {
        boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
        boolean securityViolation = includeCode && ai.uid != 0
                && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
                        ? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
                        : true);
        boolean registerPackage = includeCode && (flags&Context.CONTEXT_REGISTER_PACKAGE) != 0;
        if ((flags&(Context.CONTEXT_INCLUDE_CODE
                |Context.CONTEXT_IGNORE_SECURITY))
                == Context.CONTEXT_INCLUDE_CODE) {
            if (securityViolation) {
                String msg = "Requesting code from " + ai.packageName
                        + " (with uid " + ai.uid + ")";
                if (mBoundApplication != null) {
                    msg = msg + " to be run in process "
                        + mBoundApplication.processName + " (with uid "
                        + mBoundApplication.appInfo.uid + ")";
                }
                throw new SecurityException(msg);
            }
        }
        return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode,
                registerPackage);
    }

    @UnsupportedAppUsage
    public LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
    }

    @Override
    public LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
        return getPackageInfo(ai, mCompatibilityInfo, null /* baseLoader */,
                false /* securityViolation */, true /* includeCode */, false /* registerPackage */);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public LoadedApk peekPackageInfo(String packageName, boolean includeCode) {
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (includeCode) {
                ref = mPackages.get(packageName);
            } else {
                ref = mResourcePackages.get(packageName);
            }
            return ref != null ? ref.get() : null;
        }
    }

    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage) {
        return getPackageInfo(aInfo, compatInfo, baseLoader, securityViolation, includeCode,
                registerPackage, Process.isSdkSandbox());
    }

    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage, boolean isSdkSandbox) {
        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (differentUser || isSdkSandbox) {
                // Caching not supported across users and for sdk sandboxes
                ref = null;
            } else if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }

            LoadedApk packageInfo = ref != null ? ref.get() : null;

            if (packageInfo != null) {
                if (!isLoadedApkResourceDirsUpToDate(packageInfo, aInfo)) {
                    if (packageInfo.getApplicationInfo().createTimestamp > aInfo.createTimestamp) {
                        // The cached loaded apk is newer than the one passed in, we should not
                        // update the cached version
                        Slog.w(TAG, "getPackageInfo() called with an older ApplicationInfo "
                                + "than the cached version for package " + aInfo.packageName);
                    } else {
                        Slog.v(TAG, "getPackageInfo() caused update to cached ApplicationInfo "
                                + "for package " + aInfo.packageName);
                        List<String> oldPaths = new ArrayList<>();
                        LoadedApk.makePaths(this, aInfo, oldPaths);
                        packageInfo.updateApplicationInfo(aInfo, oldPaths);
                    }
                }

                return packageInfo;
            }

            if (localLOGV) {
                Slog.v(TAG, (includeCode ? "Loading code package "
                        : "Loading resource-only package ") + aInfo.packageName
                        + " (in " + (mBoundApplication != null
                        ? mBoundApplication.processName : null)
                        + ")");
            }

            packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode
                            && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

            if (mSystemThread && "android".equals(aInfo.packageName)) {
                packageInfo.installSystemApplicationInfo(aInfo,
                        getSystemContext().mPackageInfo.getClassLoader());
            }

            if (differentUser || isSdkSandbox) {
                // Caching not supported across users and for sdk sandboxes
            } else if (includeCode) {
                mPackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            } else {
                mResourcePackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            }

            return packageInfo;
        }
    }

    private static boolean isLoadedApkResourceDirsUpToDate(LoadedApk loadedApk,
            ApplicationInfo appInfo) {
        Resources packageResources = loadedApk.mResources;
        boolean resourceDirsUpToDate = Arrays.equals(
                ArrayUtils.defeatNullable(appInfo.resourceDirs),
                ArrayUtils.defeatNullable(loadedApk.getOverlayDirs()));
        boolean overlayPathsUpToDate = Arrays.equals(
                ArrayUtils.defeatNullable(appInfo.overlayPaths),
                ArrayUtils.defeatNullable(loadedApk.getOverlayPaths()));

        return (packageResources == null || packageResources.getAssets().isUpToDate())
                && resourceDirsUpToDate && overlayPathsUpToDate;
    }

    @UnsupportedAppUsage
    @RavenwoodKeep
    ActivityThread() {
        mResourcesManager = ResourcesManager.getInstance();
    }

    /**
     * Creates and initialize a new system activity thread, to be used for testing. This does not
     * call {@link #attach}, so it does not modify static state.
     */
    @VisibleForTesting
    @NonNull
    public static ActivityThread createSystemActivityThreadForTesting() {
        final var thread = new ActivityThread();
        thread.mSystemThread = true;
        initializeSystemThread(thread);
        return thread;
    }

    @UnsupportedAppUsage
    public ApplicationThread getApplicationThread()
    {
        return mAppThread;
    }

    @UnsupportedAppUsage
    @RavenwoodKeep
    public Instrumentation getInstrumentation()
    {
        return mInstrumentation;
    }

    @NeverCompile // Only called by tests.
    @NeverInline  // Only called by tests.
    @RavenwoodReplace
    public static void throwIfNotInstrumenting() {
        final ActivityThread activityThread = ActivityThread.currentActivityThread();
        if (activityThread == null) {
            // Only tests can reach here.
            return;
        }
        final Instrumentation instrumentation = activityThread.getInstrumentation();
        if (instrumentation == null) {
            // Only tests can reach here.
            return;
        }
        if (instrumentation.isInstrumenting()) {
            return;
        }
        throw new IllegalStateException();
    }

    public static void throwIfNotInstrumenting$ravenwood() {
        // Treat Ravenwood tests as instrumenting, so that they can call code that is
        // used in instrumentation tests.
    }

    public boolean isProfiling() {
        return mProfiler != null && mProfiler.profileFile != null
                && mProfiler.profileFd == null;
    }

    public String getProfileFilePath() {
        return mProfiler.profileFile;
    }

    @UnsupportedAppUsage
    @RavenwoodKeep
    public Looper getLooper() {
        return mLooper;
    }

    @RavenwoodKeep
    public Executor getExecutor() {
        return mExecutor;
    }

    @Override
    @UnsupportedAppUsage
    public Application getApplication() {
        return mInitialApplication;
    }

    @UnsupportedAppUsage
    public String getProcessName() {
        return mBoundApplication.processName;
    }

    @Override
    @UnsupportedAppUsage
    @RavenwoodKeep
    public ContextImpl getSystemContext() {
        synchronized (this) {
            if (mSystemContext == null) {
                mSystemContext = ContextImpl.createSystemContext(this);
            }
            return mSystemContext;
        }
    }

    @NonNull
    public Context getSystemUiContext() {
        return getSystemUiContext(DEFAULT_DISPLAY);
    }

    /**
     * Gets the context instance base on system resources & display information which used for UI.
     * @param displayId The ID of the display where the UI is shown.
     * @see ContextImpl#createSystemUiContext(ContextImpl, int)
     */
    @NonNull
    public Context getSystemUiContext(int displayId) {
        synchronized (this) {
            if (mDisplaySystemUiContexts == null) {
                mDisplaySystemUiContexts = new ArrayList<>();
            }

            mDisplaySystemUiContexts.removeIf(contextRef -> contextRef.refersTo(null));

            Context context = getSystemUiContextNoCreateLocked(displayId);
            if (context != null) {
                return context;
            }

            context = ContextImpl.createSystemUiContext(getSystemContext(), displayId);
            mDisplaySystemUiContexts.add(new WeakReference<>(context));
            return context;
        }
    }

    /**
     * Creates a {@code SystemUiContext} for testing.
     * <p>
     * DO NOT use it in production code.
     */
    @VisibleForTesting
    @NonNull
    public Context createSystemUiContextForTesting(int displayId) {
        return ContextImpl.createSystemUiContext(getSystemContext(), displayId);
    }

    @Nullable
    @Override
    public Context getSystemUiContextNoCreate() {
        synchronized (this) {
            if (mDisplaySystemUiContexts == null) {
                return null;
            }
            return getSystemUiContextNoCreateLocked(DEFAULT_DISPLAY);
        }
    }

    @GuardedBy("this")
    @Nullable
    private Context getSystemUiContextNoCreateLocked(int displayId) {
        for (int i = 0; i < mDisplaySystemUiContexts.size(); i++) {
            Context context = mDisplaySystemUiContexts.get(i).get();
            if (context != null && context.getDisplayId() == displayId) {
                return context;
            }
        }
        return null;
    }

    void onSystemUiContextCleanup(ContextImpl context) {
        synchronized (this) {
            if (mDisplaySystemUiContexts == null) return;
            mDisplaySystemUiContexts.removeIf(
                    contextRef -> contextRef.refersTo(null) || contextRef.refersTo(context));
        }
    }

    public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
        synchronized (this) {
            getSystemContext().installSystemApplicationInfo(info, classLoader);
            final ContextImpl sysUiContextImpl = ContextImpl.getImpl(getSystemUiContext());
            sysUiContextImpl.installSystemApplicationInfo(info, classLoader);

            // give ourselves a default profiler
            mProfiler = new Profiler();
        }
    }

    @UnsupportedAppUsage
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    void unscheduleGcIdler() {
        if (mGcIdlerScheduled) {
            mGcIdlerScheduled = false;
            Looper.myQueue().removeIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    void schedulePurgeIdler() {
        if (!mPurgeIdlerScheduled) {
            mPurgeIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mPurgeIdler);
        }
        mH.removeMessages(H.PURGE_RESOURCES);
    }

    void unschedulePurgeIdler() {
        if (mPurgeIdlerScheduled) {
            mPurgeIdlerScheduled = false;
            Looper.myQueue().removeIdleHandler(mPurgeIdler);
        }
        mH.removeMessages(H.PURGE_RESOURCES);
    }

    void doGcIfNeeded() {
        doGcIfNeeded("bg");
    }

    void doGcIfNeeded(String reason) {
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
        //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
        //        + "m now=" + now);
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc(reason);
        }
    }

    private static final String HEAP_FULL_COLUMN =
            "%13s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s";
    private static final String HEAP_COLUMN =
            "%13s %8s %8s %8s %8s %8s %8s %8s %8s";
    private static final String ONE_COUNT_COLUMN = "%21s %8d";
    private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
    private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8d %21s %8d";
    private static final String TWO_COUNT_COLUMN_HEADER = "%21s %8s %21s %8s";
    private static final String ONE_ALT_COUNT_COLUMN = "%21s %8s %21s %8d";

    // Formatting for checkin service - update version if row format changes
    private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 4;

    static void printRow(PrintWriter pw, String format, Object...objs) {
        pw.println(String.format(Locale.US, format, objs));
    }

    @NeverCompile
    public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
            boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
            int pid, String processName,
            long nativeMax, long nativeAllocated, long nativeFree,
            long dalvikMax, long dalvikAllocated, long dalvikFree) {

        // For checkin, we print one long comma-separated list of values
        if (checkin) {
            // NOTE: if you change anything significant below, also consider changing
            // ACTIVITY_THREAD_CHECKIN_VERSION.

            // Header
            pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(',');
            pw.print(pid); pw.print(',');
            pw.print(processName); pw.print(',');

            // Heap info - max
            pw.print(nativeMax); pw.print(',');
            pw.print(dalvikMax); pw.print(',');
            pw.print("N/A,");
            pw.print(nativeMax + dalvikMax); pw.print(',');

            // Heap info - allocated
            pw.print(nativeAllocated); pw.print(',');
            pw.print(dalvikAllocated); pw.print(',');
            pw.print("N/A,");
            pw.print(nativeAllocated + dalvikAllocated); pw.print(',');

            // Heap info - free
            pw.print(nativeFree); pw.print(',');
            pw.print(dalvikFree); pw.print(',');
            pw.print("N/A,");
            pw.print(nativeFree + dalvikFree); pw.print(',');

            // Heap info - proportional set size
            pw.print(memInfo.nativePss); pw.print(',');
            pw.print(memInfo.dalvikPss); pw.print(',');
            pw.print(memInfo.otherPss); pw.print(',');
            pw.print(memInfo.getTotalPss()); pw.print(',');

            // Heap info - swappable set size
            pw.print(memInfo.nativeSwappablePss); pw.print(',');
            pw.print(memInfo.dalvikSwappablePss); pw.print(',');
            pw.print(memInfo.otherSwappablePss); pw.print(',');
            pw.print(memInfo.getTotalSwappablePss()); pw.print(',');

            // Heap info - shared dirty
            pw.print(memInfo.nativeSharedDirty); pw.print(',');
            pw.print(memInfo.dalvikSharedDirty); pw.print(',');
            pw.print(memInfo.otherSharedDirty); pw.print(',');
            pw.print(memInfo.getTotalSharedDirty()); pw.print(',');

            // Heap info - shared clean
            pw.print(memInfo.nativeSharedClean); pw.print(',');
            pw.print(memInfo.dalvikSharedClean); pw.print(',');
            pw.print(memInfo.otherSharedClean); pw.print(',');
            pw.print(memInfo.getTotalSharedClean()); pw.print(',');

            // Heap info - private Dirty
            pw.print(memInfo.nativePrivateDirty); pw.print(',');
            pw.print(memInfo.dalvikPrivateDirty); pw.print(',');
            pw.print(memInfo.otherPrivateDirty); pw.print(',');
            pw.print(memInfo.getTotalPrivateDirty()); pw.print(',');

            // Heap info - private Clean
            pw.print(memInfo.nativePrivateClean); pw.print(',');
            pw.print(memInfo.dalvikPrivateClean); pw.print(',');
            pw.print(memInfo.otherPrivateClean); pw.print(',');
            pw.print(memInfo.getTotalPrivateClean()); pw.print(',');

            // Heap info - swapped out
            pw.print(memInfo.nativeSwappedOut); pw.print(',');
            pw.print(memInfo.dalvikSwappedOut); pw.print(',');
            pw.print(memInfo.otherSwappedOut); pw.print(',');
            pw.print(memInfo.getTotalSwappedOut()); pw.print(',');

            // Heap info - swapped out pss
            if (memInfo.hasSwappedOutPss) {
                pw.print(memInfo.nativeSwappedOutPss); pw.print(',');
                pw.print(memInfo.dalvikSwappedOutPss); pw.print(',');
                pw.print(memInfo.otherSwappedOutPss); pw.print(',');
                pw.print(memInfo.getTotalSwappedOutPss()); pw.print(',');
            } else {
                pw.print("N/A,");
                pw.print("N/A,");
                pw.print("N/A,");
                pw.print("N/A,");
            }

            // Heap info - other areas
            for (int i=0; i<Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
                pw.print(Debug.MemoryInfo.getOtherLabel(i)); pw.print(',');
                pw.print(memInfo.getOtherPss(i)); pw.print(',');
                pw.print(memInfo.getOtherSwappablePss(i)); pw.print(',');
                pw.print(memInfo.getOtherSharedDirty(i)); pw.print(',');
                pw.print(memInfo.getOtherSharedClean(i)); pw.print(',');
                pw.print(memInfo.getOtherPrivateDirty(i)); pw.print(',');
                pw.print(memInfo.getOtherPrivateClean(i)); pw.print(',');
                pw.print(memInfo.getOtherSwappedOut(i)); pw.print(',');
                if (memInfo.hasSwappedOutPss) {
                    pw.print(memInfo.getOtherSwappedOutPss(i)); pw.print(',');
                } else {
                    pw.print("N/A,");
                }
            }
            return;
        }

        if (!dumpSummaryOnly) {
            if (dumpFullInfo) {
                printRow(pw, HEAP_FULL_COLUMN, "", "Pss", "Pss", "Shared", "Private",
                        "Shared", "Private", memInfo.hasSwappedOutPss ? "SwapPss" : "Swap",
                        "Rss", "Heap", "Heap", "Heap");
                printRow(pw, HEAP_FULL_COLUMN, "", "Total", "Clean", "Dirty", "Dirty",
                        "Clean", "Clean", "Dirty", "Total",
                        "Size", "Alloc", "Free");
                printRow(pw, HEAP_FULL_COLUMN, "", "------", "------", "------", "------",
                        "------", "------", "------", "------", "------", "------", "------");
                printRow(pw, HEAP_FULL_COLUMN, "Native Heap", memInfo.nativePss,
                        memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
                        memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
                        memInfo.nativePrivateClean, memInfo.hasSwappedOutPss ?
                        memInfo.nativeSwappedOutPss : memInfo.nativeSwappedOut,
                        memInfo.nativeRss, nativeMax, nativeAllocated, nativeFree);
                printRow(pw, HEAP_FULL_COLUMN, "Dalvik Heap", memInfo.dalvikPss,
                        memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
                        memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
                        memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss ?
                        memInfo.dalvikSwappedOutPss : memInfo.dalvikSwappedOut,
                        memInfo.dalvikRss, dalvikMax, dalvikAllocated, dalvikFree);
            } else {
                printRow(pw, HEAP_COLUMN, "", "Pss", "Private",
                        "Private", memInfo.hasSwappedOutPss ? "SwapPss" : "Swap",
                        "Rss", "Heap", "Heap", "Heap");
                printRow(pw, HEAP_COLUMN, "", "Total", "Dirty",
                        "Clean", "Dirty", "Total", "Size", "Alloc", "Free");
                printRow(pw, HEAP_COLUMN, "", "------", "------", "------",
                        "------", "------", "------", "------", "------", "------");
                printRow(pw, HEAP_COLUMN, "Native Heap", memInfo.nativePss,
                        memInfo.nativePrivateDirty,
                        memInfo.nativePrivateClean,
                        memInfo.hasSwappedOutPss ? memInfo.nativeSwappedOutPss :
                        memInfo.nativeSwappedOut, memInfo.nativeRss,
                        nativeMax, nativeAllocated, nativeFree);
                printRow(pw, HEAP_COLUMN, "Dalvik Heap", memInfo.dalvikPss,
                        memInfo.dalvikPrivateDirty,
                        memInfo.dalvikPrivateClean,
                        memInfo.hasSwappedOutPss ? memInfo.dalvikSwappedOutPss :
                        memInfo.dalvikSwappedOut, memInfo.dalvikRss,
                        dalvikMax, dalvikAllocated, dalvikFree);
            }

            int otherPss = memInfo.otherPss;
            int otherSwappablePss = memInfo.otherSwappablePss;
            int otherSharedDirty = memInfo.otherSharedDirty;
            int otherPrivateDirty = memInfo.otherPrivateDirty;
            int otherSharedClean = memInfo.otherSharedClean;
            int otherPrivateClean = memInfo.otherPrivateClean;
            int otherSwappedOut = memInfo.otherSwappedOut;
            int otherSwappedOutPss = memInfo.otherSwappedOutPss;
            int otherRss = memInfo.otherRss;

            for (int i=0; i<Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
                final int myPss = memInfo.getOtherPss(i);
                final int mySwappablePss = memInfo.getOtherSwappablePss(i);
                final int mySharedDirty = memInfo.getOtherSharedDirty(i);
                final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
                final int mySharedClean = memInfo.getOtherSharedClean(i);
                final int myPrivateClean = memInfo.getOtherPrivateClean(i);
                final int mySwappedOut = memInfo.getOtherSwappedOut(i);
                final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
                final int myRss = memInfo.getOtherRss(i);
                if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
                        || mySharedClean != 0 || myPrivateClean != 0 || myRss != 0
                        || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
                    if (dumpFullInfo) {
                        printRow(pw, HEAP_FULL_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
                                myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
                                mySharedClean, myPrivateClean,
                                memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
                                myRss, "", "", "");
                    } else {
                        printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
                                myPss, myPrivateDirty,
                                myPrivateClean,
                                memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
                                myRss, "", "", "");
                    }
                    otherPss -= myPss;
                    otherSwappablePss -= mySwappablePss;
                    otherSharedDirty -= mySharedDirty;
                    otherPrivateDirty -= myPrivateDirty;
                    otherSharedClean -= mySharedClean;
                    otherPrivateClean -= myPrivateClean;
                    otherSwappedOut -= mySwappedOut;
                    otherSwappedOutPss -= mySwappedOutPss;
                    otherRss -= myRss;
                }
            }

            if (dumpFullInfo) {
                printRow(pw, HEAP_FULL_COLUMN, "Unknown", otherPss, otherSwappablePss,
                        otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
                        memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut,
                        otherRss, "", "", "");
                printRow(pw, HEAP_FULL_COLUMN, "TOTAL", memInfo.getTotalPss(),
                        memInfo.getTotalSwappablePss(),
                        memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
                        memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
                        memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() :
                        memInfo.getTotalSwappedOut(), memInfo.getTotalRss(),
                        nativeMax+dalvikMax, nativeAllocated+dalvikAllocated,
                        nativeFree+dalvikFree);
            } else {
                printRow(pw, HEAP_COLUMN, "Unknown", otherPss,
                        otherPrivateDirty, otherPrivateClean,
                        memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut,
                        otherRss, "", "", "");
                printRow(pw, HEAP_COLUMN, "TOTAL", memInfo.getTotalPss(),
                        memInfo.getTotalPrivateDirty(),
                        memInfo.getTotalPrivateClean(),
                        memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() :
                        memInfo.getTotalSwappedOut(), memInfo.getTotalRss(),
                        nativeMax+dalvikMax,
                        nativeAllocated+dalvikAllocated, nativeFree+dalvikFree);
            }

            if (dumpDalvik) {
                pw.println(" ");
                pw.println(" Dalvik Details");

                for (int i=Debug.MemoryInfo.NUM_OTHER_STATS;
                     i<Debug.MemoryInfo.NUM_OTHER_STATS + Debug.MemoryInfo.NUM_DVK_STATS; i++) {
                    final int myPss = memInfo.getOtherPss(i);
                    final int mySwappablePss = memInfo.getOtherSwappablePss(i);
                    final int mySharedDirty = memInfo.getOtherSharedDirty(i);
                    final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
                    final int mySharedClean = memInfo.getOtherSharedClean(i);
                    final int myPrivateClean = memInfo.getOtherPrivateClean(i);
                    final int mySwappedOut = memInfo.getOtherSwappedOut(i);
                    final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
                    final int myRss = memInfo.getOtherRss(i);
                    if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
                            || mySharedClean != 0 || myPrivateClean != 0
                            || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
                        if (dumpFullInfo) {
                            printRow(pw, HEAP_FULL_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
                                    myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
                                    mySharedClean, myPrivateClean,
                                    memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
                                    myRss, "", "", "");
                        } else {
                            printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
                                    myPss, myPrivateDirty,
                                    myPrivateClean,
                                    memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
                                    myRss, "", "", "");
                        }
                    }
                }
            }
        }

        pw.println(" ");
        pw.println(" App Summary");
        printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "Pss(KB)", "", "Rss(KB)");
        printRow(pw, TWO_COUNT_COLUMN_HEADER, "", "------", "", "------");
        printRow(pw, TWO_COUNT_COLUMNS,
                "Java Heap:", memInfo.getSummaryJavaHeap(), "", memInfo.getSummaryJavaHeapRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Native Heap:", memInfo.getSummaryNativeHeap(), "",
                memInfo.getSummaryNativeHeapRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Code:", memInfo.getSummaryCode(), "", memInfo.getSummaryCodeRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Stack:", memInfo.getSummaryStack(), "", memInfo.getSummaryStackRss());
        printRow(pw, TWO_COUNT_COLUMNS,
                "Graphics:", memInfo.getSummaryGraphics(), "", memInfo.getSummaryGraphicsRss());
        printRow(pw, ONE_COUNT_COLUMN,
                "Private Other:", memInfo.getSummaryPrivateOther());
        printRow(pw, ONE_COUNT_COLUMN,
                "System:", memInfo.getSummarySystem());
        printRow(pw, ONE_ALT_COUNT_COLUMN,
                "Unknown:", "", "", memInfo.getSummaryUnknownRss());
        pw.println(" ");
        if (memInfo.hasSwappedOutPss) {
            printRow(pw, THREE_COUNT_COLUMNS,
                    "TOTAL PSS:", memInfo.getSummaryTotalPss(),
                    "TOTAL RSS:", memInfo.getTotalRss(),
                    "TOTAL SWAP PSS:", memInfo.getSummaryTotalSwapPss());
        } else {
            printRow(pw, THREE_COUNT_COLUMNS,
                    "TOTAL PSS:", memInfo.getSummaryTotalPss(),
                    "TOTAL RSS:", memInfo.getTotalRss(),
                    "TOTAL SWAP (KB):", memInfo.getSummaryTotalSwap());
        }
    }

    /**
     * Dump heap info to proto.
     *
     * @param hasSwappedOutPss determines whether to use dirtySwap or dirtySwapPss
     */
    private static void dumpMemoryInfo(ProtoOutputStream proto, long fieldId, String name,
            int pss, int cleanPss, int sharedDirty, int privateDirty,
            int sharedClean, int privateClean,
            boolean hasSwappedOutPss, int dirtySwap, int dirtySwapPss, int rss) {
        final long token = proto.start(fieldId);

        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.NAME, name);
        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss);
        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss);
        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
        if (hasSwappedOutPss) {
            proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
        } else {
            proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
        }
        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.TOTAL_RSS_KB, rss);

        proto.end(token);
    }

    /**
     * Dump mem info data to proto.
     */
    @NeverCompile
    public static void dumpMemInfoTable(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
            boolean dumpDalvik, boolean dumpSummaryOnly,
            long nativeMax, long nativeAllocated, long nativeFree,
            long dalvikMax, long dalvikAllocated, long dalvikFree) {

        if (!dumpSummaryOnly) {
            final long nhToken = proto.start(MemInfoDumpProto.ProcessMemory.NATIVE_HEAP);
            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap",
                    memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
                    memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
                    memInfo.nativePrivateClean, memInfo.hasSwappedOutPss,
                    memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss,
                    memInfo.nativeRss);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree);
            proto.end(nhToken);

            final long dvToken = proto.start(MemInfoDumpProto.ProcessMemory.DALVIK_HEAP);
            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap",
                    memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
                    memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
                    memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss,
                    memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss,
                    memInfo.dalvikRss);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree);
            proto.end(dvToken);

            int otherPss = memInfo.otherPss;
            int otherSwappablePss = memInfo.otherSwappablePss;
            int otherSharedDirty = memInfo.otherSharedDirty;
            int otherPrivateDirty = memInfo.otherPrivateDirty;
            int otherSharedClean = memInfo.otherSharedClean;
            int otherPrivateClean = memInfo.otherPrivateClean;
            int otherSwappedOut = memInfo.otherSwappedOut;
            int otherSwappedOutPss = memInfo.otherSwappedOutPss;
            int otherRss = memInfo.otherRss;

            for (int i = 0; i < Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
                final int myPss = memInfo.getOtherPss(i);
                final int mySwappablePss = memInfo.getOtherSwappablePss(i);
                final int mySharedDirty = memInfo.getOtherSharedDirty(i);
                final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
                final int mySharedClean = memInfo.getOtherSharedClean(i);
                final int myPrivateClean = memInfo.getOtherPrivateClean(i);
                final int mySwappedOut = memInfo.getOtherSwappedOut(i);
                final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
                final int myRss = memInfo.getOtherRss(i);
                if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
                        || mySharedClean != 0 || myPrivateClean != 0 || myRss != 0
                        || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
                    dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.OTHER_HEAPS,
                            Debug.MemoryInfo.getOtherLabel(i),
                            myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
                            mySharedClean, myPrivateClean,
                            memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss, myRss);

                    otherPss -= myPss;
                    otherSwappablePss -= mySwappablePss;
                    otherSharedDirty -= mySharedDirty;
                    otherPrivateDirty -= myPrivateDirty;
                    otherSharedClean -= mySharedClean;
                    otherPrivateClean -= myPrivateClean;
                    otherSwappedOut -= mySwappedOut;
                    otherSwappedOutPss -= mySwappedOutPss;
                    otherRss -= myRss;
                }
            }

            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.UNKNOWN_HEAP, "Unknown",
                    otherPss, otherSwappablePss,
                    otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
                    memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss, otherRss);
            final long tToken = proto.start(MemInfoDumpProto.ProcessMemory.TOTAL_HEAP);
            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL",
                    memInfo.getTotalPss(), memInfo.getTotalSwappablePss(),
                    memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
                    memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
                    memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(),
                    memInfo.getTotalSwappedOutPss(), memInfo.getTotalRss());
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB,
                    nativeMax + dalvikMax);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB,
                    nativeAllocated + dalvikAllocated);
            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB,
                    nativeFree + dalvikFree);
            proto.end(tToken);

            if (dumpDalvik) {
                for (int i = Debug.MemoryInfo.NUM_OTHER_STATS;
                        i < Debug.MemoryInfo.NUM_OTHER_STATS + Debug.MemoryInfo.NUM_DVK_STATS;
                        i++) {
                    final int myPss = memInfo.getOtherPss(i);
                    final int mySwappablePss = memInfo.getOtherSwappablePss(i);
                    final int mySharedDirty = memInfo.getOtherSharedDirty(i);
                    final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
                    final int mySharedClean = memInfo.getOtherSharedClean(i);
                    final int myPrivateClean = memInfo.getOtherPrivateClean(i);
                    final int mySwappedOut = memInfo.getOtherSwappedOut(i);
                    final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
                    final int myRss = memInfo.getOtherRss(i);
                    if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
                            || mySharedClean != 0 || myPrivateClean != 0
                            || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
                        dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.DALVIK_DETAILS,
                                Debug.MemoryInfo.getOtherLabel(i),
                                myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
                                mySharedClean, myPrivateClean,
                                memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss, myRss);
                    }
                }
            }
        }

        final long asToken = proto.start(MemInfoDumpProto.ProcessMemory.APP_SUMMARY);
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB,
                memInfo.getSummaryJavaHeap());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB,
                memInfo.getSummaryNativeHeap());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.CODE_PSS_KB,
                memInfo.getSummaryCode());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.STACK_PSS_KB,
                memInfo.getSummaryStack());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB,
                memInfo.getSummaryGraphics());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB,
                memInfo.getSummaryPrivateOther());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB,
                memInfo.getSummarySystem());
        if (memInfo.hasSwappedOutPss) {
            proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
                    memInfo.getSummaryTotalSwapPss());
        } else {
            proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
                    memInfo.getSummaryTotalSwap());
        }
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.JAVA_HEAP_RSS_KB,
                memInfo.getSummaryJavaHeapRss());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.NATIVE_HEAP_RSS_KB,
                memInfo.getSummaryNativeHeapRss());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.CODE_RSS_KB,
                memInfo.getSummaryCodeRss());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.STACK_RSS_KB,
                memInfo.getSummaryStackRss());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.GRAPHICS_RSS_KB,
                memInfo.getSummaryGraphicsRss());
        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.UNKNOWN_RSS_KB,
                memInfo.getSummaryUnknownRss());

        proto.end(asToken);
    }

    @NeverCompile
    public static void dumpBitmapsProto(ProtoOutputStream proto, int pid,
                String processName, String dumpFormat) {
        try {
            proto.write(BitmapDumpProto.AppBitmapInfo.PID, pid);
            proto.write(BitmapDumpProto.AppBitmapInfo.PROCESS_NAME, processName);
            Bitmap.dumpAll(proto, dumpFormat);
        } finally {
            proto.flush();
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void registerOnActivityPausedListener(Activity activity,
            OnActivityPausedListener listener) {
        synchronized (mOnPauseListeners) {
            ArrayList<OnActivityPausedListener> list =
                    mOnPauseListeners.computeIfAbsent(activity, k -> new ArrayList<>());
            list.add(listener);
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void unregisterOnActivityPausedListener(Activity activity,
            OnActivityPausedListener listener) {
        synchronized (mOnPauseListeners) {
            ArrayList<OnActivityPausedListener> list = mOnPauseListeners.get(activity);
            if (list != null) {
                list.remove(listener);
            }
        }
    }

    public ActivityInfo resolveActivityInfo(Intent intent) {
        ActivityInfo aInfo = intent.resolveActivityInfo(
                mInitialApplication.getPackageManager(), PackageManager.GET_SHARED_LIBRARY_FILES);
        if (aInfo == null) {
            // Throw an exception.
            Instrumentation.checkStartActivityResult(
                    ActivityManager.START_CLASS_NOT_FOUND, intent);
        }
        return aInfo;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public final Activity startActivityNow(Activity parent, String id,
            Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
            Activity.NonConfigurationInstances lastNonConfigurationInstances, IBinder assistToken,
            IBinder shareableActivityToken) {
        ActivityClientRecord r = new ActivityClientRecord();
            r.token = token;
            r.assistToken = assistToken;
            r.shareableActivityToken = shareableActivityToken;
            r.ident = 0;
            r.intent = intent;
            r.state = state;
            r.parent = parent;
            r.embeddedID = id;
            r.activityInfo = activityInfo;
            r.lastNonConfigurationInstances = lastNonConfigurationInstances;
        if (localLOGV) {
            ComponentName compname = intent.getComponent();
            String name;
            if (compname != null) {
                name = compname.toShortString();
            } else {
                name = "(Intent " + intent + ").getComponent() returned null";
            }
            Slog.v(TAG, "Performing launch: action=" + intent.getAction()
                    + ", comp=" + name
                    + ", token=" + token);
        }
        // TODO(lifecycler): Can't switch to use #handleLaunchActivity() because it will try to
        // call #reportSizeConfigurations(), but the server might not know anything about the
        // activity if it was launched from LocalAcvitivyManager.
        return performLaunchActivity(r, null /* customIntent */);
    }

    @UnsupportedAppUsage
    @Override
    public final Activity getActivity(IBinder token) {
        final ActivityClientRecord activityRecord = mActivities.get(token);
        return activityRecord != null ? activityRecord.activity : null;
    }

    /**
     * Returns the most recent created activity that's still running.
     */
    @Nullable
    public Activity getLastCreatedActivity() {
        if (mActivities.isEmpty()) {
            return null;
        }

        return mActivities.valueAt(mActivities.size() - 1).activity;
    }

    @Override
    public ActivityClientRecord getActivityClient(IBinder token) {
        return mActivities.get(token);
    }

    @VisibleForTesting(visibility = PACKAGE)
    public Configuration getConfiguration() {
        return mConfigurationController.getConfiguration();
    }

    /**
     * @hide
     */
    public void addConfigurationChangedListener(Executor executor,
            Consumer<IBinder> consumer) {
        mConfigurationChangedListenerController.addListener(executor, consumer);
    }

    /**
     * @hide
     */
    public void removeConfigurationChangedListener(Consumer<IBinder> consumer) {
        mConfigurationChangedListenerController.removeListener(consumer);
    }

    @Override
    public void updatePendingConfiguration(Configuration config) {
        final Configuration updatedConfig =
                mConfigurationController.updatePendingConfiguration(config);
        // This is only done to maintain @UnsupportedAppUsage and should be removed someday.
        if (updatedConfig != null) {
            mPendingConfiguration = updatedConfig;
        }
    }

    @Override
    public void updateProcessState(int processState, boolean fromIpc) {
        synchronized (mAppThread) {
            if (mLastProcessState == processState) {
                return;
            }
            updateVmProcessState(mLastProcessState, processState);
            mLastProcessState = processState;
            if (localLOGV) {
                Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
                        + (fromIpc ? " (from ipc" : ""));
            }
        }
        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
            Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                    "updateProcessState: processState=" + processState);
        }
    }

    /** Converts a process state to a VM process state. */
    private static int toVmProcessState(int processState) {
        if (ActivityManager.isProcStateJankPerceptible(processState)) {
            return VM_PROCESS_STATE_JANK_PERCEPTIBLE;
        }

        if (Flags.jankPerceptibleNarrow() && !Flags.jankPerceptibleNarrowHoldback()) {
            // Unlike other persistent processes, system server is often on
            // the critical path for application startup. Mark it explicitly
            // as jank perceptible regardless of processState.
            if (isSystem()) {
                return VM_PROCESS_STATE_JANK_PERCEPTIBLE;
            }
        }

        return VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
    }

    /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
    private void updateVmProcessState(int lastProcessState, int newProcessState) {
        final int state = toVmProcessState(newProcessState);
        if (lastProcessState == PROCESS_STATE_UNKNOWN
                || state != toVmProcessState(lastProcessState)) {
            VMRuntime.getRuntime().updateProcessState(state);
        }
    }

    @Override
    public void countLaunchingActivities(int num) {
        mNumLaunchingActivities.getAndAdd(num);
    }

    @UnsupportedAppUsage
    public void sendActivityResult(
            IBinder activityToken, String id, int requestCode,
            int resultCode, Intent data) {
        if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
                + " req=" + requestCode + " res=" + resultCode + " data=" + data);
        final ArrayList<ResultInfo> list = new ArrayList<>();
        list.add(new ResultInfo(id, requestCode, resultCode, data, activityToken));
        final ClientTransaction clientTransaction = new ClientTransaction(mAppThread);
        final ActivityResultItem activityResultItem = new ActivityResultItem(activityToken, list);
        clientTransaction.addTransactionItem(activityResultItem);
        try {
            mAppThread.scheduleTransaction(clientTransaction);
        } catch (RemoteException e) {
            // Local scheduling
        }
    }

    @Override
    TransactionExecutor getTransactionExecutor() {
        return mTransactionExecutor;
    }

    @Override
    void sendMessage(int what, Object obj) {
        sendMessage(what, obj, 0, 0, false);
    }

    private void sendMessage(int what, Object obj, int arg1) {
        sendMessage(what, obj, arg1, 0, false);
    }

    private void sendMessage(int what, Object obj, int arg1, int arg2) {
        sendMessage(what, obj, arg1, arg2, false);
    }

    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) {
            Slog.v(TAG,
                    "SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj);
        }
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

    final void scheduleContextCleanup(ContextImpl context, String who,
            String what) {
        ContextCleanupInfo cci = new ContextCleanupInfo();
        cci.context = context;
        cci.who = who;
        cci.what = what;
        sendMessage(H.CLEAN_UP_CONTEXT, cci);
    }

    /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;

        if (getInstrumentation() != null
                && getInstrumentation().getContext() != null
                && getInstrumentation().getContext().getApplicationInfo() != null
                && getInstrumentation().isSdkSandboxAllowedToStartActivities()) {
            // Activities launched from CTS-in-sandbox tests use a customized ApplicationInfo. See
            // also {@link SdkSandboxManagerLocal#getSdkSandboxApplicationInfoForInstrumentation}.
            r.packageInfo =
                    getPackageInfo(
                            getInstrumentation().getContext().getApplicationInfo(),
                            mCompatibilityInfo,
                            Context.CONTEXT_INCLUDE_CODE);
        } else if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        boolean isSandboxActivityContext = SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(
                                mSystemContext, r.intent);
        boolean isSandboxedSdkContextUsed = false;
        ContextImpl activityBaseContext;
        if (isSandboxActivityContext) {
            activityBaseContext = createBaseContextForSandboxActivity(r);
            if (activityBaseContext == null) {
                // Failed to retrieve the SDK based sandbox activity context, falling back to the
                // app based context.
                activityBaseContext = createBaseContextForActivity(r);
            } else {
                isSandboxedSdkContextUsed = true;
            }
        } else {
            activityBaseContext = createBaseContextForActivity(r);
        }
        Activity activity = null;
        try {
            java.lang.ClassLoader cl;
            if (isSandboxedSdkContextUsed) {
                // In case of sandbox activity, the context refers to the an SDK with no visibility
                // on the SandboxedActivity java class, the App context should be used instead.
                cl = activityBaseContext.getApplicationContext().getClassLoader();
            } else {
                cl = activityBaseContext.getClassLoader();
            }
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
                    activityBaseContext.getAttributionSource());
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component, e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
            if (localLOGV) Slog.v(
                    TAG, r + ": app=" + app
                    + ", appName=" + app.getPackageName()
                    + ", pkg=" + r.packageInfo.getPackageName()
                    + ", comp=" + r.intent.getComponent().toShortString()
                    + ", dir=" + r.packageInfo.getAppDir());

            // updatePendingActivityConfiguration() reads from mActivities to update
            // ActivityClientRecord which runs in a different thread. Protect modifications to
            // mActivities to avoid race.
            synchronized (mResourcesManager) {
                mActivities.put(r.token, r);
            }

            if (activity != null) {
                CharSequence title =
                        r.activityInfo.loadLabel(activityBaseContext.getPackageManager());
                Configuration config =
                        new Configuration(mConfigurationController.getCompatConfiguration());
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }

                // Activity resources must be initialized with the same loaders as the
                // application context.
                activityBaseContext.getResources().addLoaders(
                        app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

                activityBaseContext.setOuterContext(activity);
                activity.attach(activityBaseContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                        r.assistToken, r.shareableActivityToken, r.initialCallerInfoAccessToken);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                checkAndBlockForNetworkAccess();
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                if (r.mSceneTransitionInfo != null) {
                    activity.mSceneTransitionInfo = r.mSceneTransitionInfo;
                    r.mSceneTransitionInfo = null;
                }
                activity.mLaunchedFromBubble = r.mLaunchedFromBubble;
                activity.mCalled = false;
                // Assigning the activity to the record before calling onCreate() allows
                // ActivityThread#getActivity() lookup for the callbacks triggered from
                // ActivityLifecycleCallbacks#onActivityCreated() or
                // ActivityLifecycleCallback#onActivityPostCreated().
                r.activity = activity;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode();
            }
            r.setState(ON_CREATE);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component, e);
            }
        }

        return activity;
    }

    @Override
    public void handleStartActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, SceneTransitionInfo sceneTransitionInfo) {
        final Activity activity = r.activity;
        if (!r.stopped) {
            throw new IllegalStateException("Can't start activity that is not stopped.");
        }
        if (r.activity.mFinished) {
            // TODO(lifecycler): How can this happen?
            return;
        }

        unscheduleGcIdler();
        if (sceneTransitionInfo != null) {
            activity.mSceneTransitionInfo = sceneTransitionInfo;
        }

        // Start
        activity.performStart("handleStartActivity");
        r.setState(ON_START);

        if (pendingActions == null) {
            // No more work to do.
            return;
        }

        // Restore instance state
        if (pendingActions.shouldRestoreInstanceState()) {
            if (r.isPersistable()) {
                if (r.state != null || r.persistentState != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                            r.persistentState);
                }
            } else if (r.state != null) {
                mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
            }
        }

        // Call postOnCreate()
        if (pendingActions.shouldCallOnPostCreate()) {
            activity.mCalled = false;
            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onPostCreate");
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnPostCreate(activity, r.state,
                        r.persistentState);
            } else {
                mInstrumentation.callActivityOnPostCreate(activity, r.state);
            }
            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
            if (!activity.mCalled) {
                throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString()
                                + " did not call through to super.onPostCreate()");
            }
        }

        updateVisibility(r, true /* show */);
        mSomeActivitiesChanged = true;
    }

    /**
     * Checks if {@link #mNetworkBlockSeq} is {@link #INVALID_PROC_STATE_SEQ} and if so, returns
     * immediately. Otherwise, makes a blocking call to ActivityManagerService to wait for the
     * network rules to get updated.
     */
    private void checkAndBlockForNetworkAccess() {
        synchronized (mNetworkPolicyLock) {
            if (mNetworkBlockSeq != INVALID_PROC_STATE_SEQ) {
                try {
                    ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
                    mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
                } catch (RemoteException ignored) {}
                // InetAddress will cache UnknownHostException failures. If the rules got
                // updated and the app has network access now, we need to clear the negative
                // cache to ensure valid dns queries can work immediately.
                // TODO: b/329133769 - Clear only the negative cache once it is available.
                InetAddress.clearDnsCache();
            }
        }
    }

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        final int displayId = com.android.window.flags.Flags.useKnownDisplayIdForBaseContext()
                ? r.mDisplayId : ActivityClient.getInstance().getDisplayId(r.token);
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
        // For debugging purposes, if the activity's package name contains the value of
        // the "debug.use-second-display" system property as a substring, then show
        // its content on a secondary display if there is one.
        String pkgName = SystemProperties.get("debug.second-display.pkg");
        if (pkgName != null && !pkgName.isEmpty()
                && r.packageInfo.mPackageName.contains(pkgName)) {
            for (int id : dm.getDisplayIds()) {
                if (id != DEFAULT_DISPLAY) {
                    Display display =
                            dm.getCompatibleDisplay(id, appContext.getResources());
                    appContext = (ContextImpl) appContext.createDisplayContext(display);
                    break;
                }
            }
        }
        return appContext;
    }

    /**
     * Creates the base context for the sandbox activity based on its corresponding SDK {@link
     * ApplicationInfo} and flags.
     */
    @Nullable
    private ContextImpl createBaseContextForSandboxActivity(@NonNull ActivityClientRecord r) {
        SdkSandboxActivityAuthority sdkSandboxActivityAuthority =
                SdkSandboxActivityAuthority.getInstance();

        ActivityContextInfo contextInfo;
        try {
            contextInfo = sdkSandboxActivityAuthority.getActivityContextInfo(r.intent);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Passed intent does not match an expected sandbox activity", e);
            return null;
        } catch (IllegalStateException e) {
            Log.e(TAG, "SDK customized context flag is disabled", e);
            return null;
        } catch (Exception e) { // generic catch to unexpected exceptions
            Log.e(TAG, "Failed to create context for sandbox activity", e);
            return null;
        }

        final int displayId = com.android.window.flags.Flags.useKnownDisplayIdForBaseContext()
                ? r.mDisplayId : ActivityClient.getInstance().getDisplayId(r.token);
        final LoadedApk sdkApk = getPackageInfo(
                contextInfo.getSdkApplicationInfo(),
                r.packageInfo.getCompatibilityInfo(),
                contextInfo.getContextFlags());

        final ContextImpl activityContext = ContextImpl.createActivityContext(
                this, sdkApk, r.activityInfo, r.token, displayId, r.overrideConfig);

        // Set sandbox app's context as the application context for sdk context
        activityContext.mPackageInfo.makeApplicationInner(
                /*forceDefaultAppClass=*/false, mInstrumentation);

        return activityContext;
    }

    /**
     * Extended implementation of activity launch. Used when server requests a launch or relaunch.
     */
    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, int deviceId, Intent customIntent) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        if (r.profilerInfo != null) {
            mProfiler.setProfiler(r.profilerInfo);
            mProfiler.startProfiling();
        }

        // Make sure we are running with the most recent config and resource paths.
        applyPendingApplicationInfoChanges(r.activityInfo.packageName);
        mConfigurationController.handleConfigurationChanged(null, null);
        updateDeviceIdForNonUIContexts(deviceId);

        if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

        // Initialize before creating the activity
        if (ThreadedRenderer.sRendererEnabled
                && (r.activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            final int tid = HardwareRenderer.preload();
            // Adjust the RenderThread priority as soon as it's created.
            if (tid > 0) {
                try {
                    ActivityManager.getService().setRenderThread(tid);
                } catch (Throwable t) {
                    Log.w(TAG, "Failed to set scheduler for RenderThread", t);
                }
            }
        }

        if (android.tracing.Flags.surfaceControlRegistryProtolog()
                || android.tracing.Flags.imetrackerProtolog()) {
            if (android.tracing.Flags.protologAsyncInit()) {
                ProtoLog.initAsync();
            } else {
                ProtoLog.init();
            }
        }

        WindowManagerGlobal.initialize();

        // Hint the GraphicsEnvironment that an activity is launching on the process.
        GraphicsEnvironment.hintActivityLaunch();

        final Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfigurationController.getConfiguration());
            reportSizeConfigurations(r);
            if (!r.activity.mFinished && pendingActions != null) {
                pendingActions.setOldState(r.state);
                pendingActions.setRestoreInstanceState(true);
                pendingActions.setCallOnPostCreate(true);
            }

            // Trigger ActivityWindowInfo callback if first launch or change from relaunch.
            handleActivityWindowInfoChanged(r);
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
                    null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        }

        return a;
    }

    private void reportSizeConfigurations(ActivityClientRecord r) {
        if (mActivitiesToBeDestroyed.containsKey(r.token)) {
            // Size configurations of a destroyed activity is meaningless.
            return;
        }
        Configuration[] configurations = r.activity.getResources().getResourceConfigurations();
        if (configurations == null) {
            return;
        }
        r.mSizeConfigurations = new SizeConfigurationBuckets(configurations);
        ActivityClient.getInstance().reportSizeConfigurations(r.token, r.mSizeConfigurations);
    }

    private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
        final int N = intents.size();
        for (int i=0; i<N; i++) {
            ReferrerIntent intent = intents.get(i);
            intent.setExtrasClassLoader(r.activity.getClassLoader());
            intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
                    r.activity.getAttributionSource());
            r.activity.mFragments.noteStateNotSaved();
            if (android.security.Flags.contentUriPermissionApis()) {
                ComponentCaller caller = new ComponentCaller(r.token, intent.mCallerToken);
                mInstrumentation.callActivityOnNewIntent(r.activity, intent, caller);
            } else {
                mInstrumentation.callActivityOnNewIntent(r.activity, intent);
            }
        }
    }

    @Override
    public void handleNewIntent(ActivityClientRecord r, List<ReferrerIntent> intents) {
        checkAndBlockForNetworkAccess();
        deliverNewIntents(r, intents);
    }

    private void handleHandoffActivityDataRequest(RequestHandoffActivityData cmd) {
        final HandoffActivityDataRequestInfo requestInfo = new HandoffActivityDataRequestInfo(true);
        final List<HandoffActivityData> handoffActivityData = new ArrayList<>();
        for (IBinder activityToken : cmd.activityTokens) {
            final ActivityClientRecord r = mActivities.get(activityToken);
            if (r != null) {
                final HandoffActivityData dataForActivity =
                        r.activity.onHandoffActivityDataRequested(requestInfo);
                if (dataForActivity == null) {
                    Log.w(TAG, "HandoffActivityData is null for " + r.activity.getComponentName());
                }
                handoffActivityData.add(dataForActivity);
            } else {
                Log.w(TAG, "Attempted to fetch HandoffActivityData for non-existent activity.");
                handoffActivityData.add(null);
            }
        }

        IActivityTaskManager mgr = ActivityTaskManager.getService();
        try {
            mgr.reportHandoffActivityData(cmd.requestToken, handoffActivityData);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
        // Filling for autofill has a few differences:
        // - it does not need an AssistContent
        // - it does not call onProvideAssistData()
        // - it needs an IAutoFillCallback
        boolean forAutofill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTOFILL;
        // When only the AssistContent is requested, omit the AsssistStructure
        boolean requestedOnlyContent = cmd.requestType == ActivityManager.ASSIST_CONTEXT_CONTENT;

        // TODO: decide if lastSessionId logic applies to autofill sessions
        if (mLastSessionId != cmd.sessionId) {
            // Clear the existing structures
            mLastSessionId = cmd.sessionId;
            for (int i = mLastAssistStructures.size() - 1; i >= 0; i--) {
                AssistStructure structure = mLastAssistStructures.get(i).get();
                if (structure != null) {
                    structure.clearSendChannel();
                }
                mLastAssistStructures.remove(i);
            }
        }

        Bundle data = new Bundle();
        AssistStructure structure = null;
        AssistContent content = forAutofill ? null : new AssistContent();
        final long startTime = SystemClock.uptimeMillis();
        ActivityClientRecord r = mActivities.get(cmd.activityToken);
        Uri referrer = null;
        if (r != null) {
            if (!forAutofill) {
                r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
                r.activity.onProvideAssistData(data);
                referrer = r.activity.onProvideReferrer();
            }
            boolean requiresData =
                    cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL
                            || cmd.requestType
                                    == ActivityManager.ASSIST_CONTEXT_SKIP_SCREEN_CONTENT;
            if (requiresData || forAutofill || requestedOnlyContent) {
                if (!requestedOnlyContent) {
                    structure = new AssistStructure(r.activity, forAutofill, cmd.flags);
                }
                Intent activityIntent = r.activity.getIntent();
                boolean notSecure = r.window == null ||
                        (r.window.getAttributes().flags
                                & WindowManager.LayoutParams.FLAG_SECURE) == 0;
                if (activityIntent != null && notSecure) {
                    if (!forAutofill) {
                        Intent intent = new Intent(activityIntent);
                        intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
                        content.setDefaultIntent(intent);
                    }
                } else {
                    if (!forAutofill) {
                        content.setDefaultIntent(new Intent());
                    }
                }
                if (!forAutofill) {
                    r.activity.onProvideAssistContent(content);
                }
            }
        }

        if (!requestedOnlyContent) {
            if (structure == null) {
                structure = new AssistStructure();
            }

            // TODO: decide if lastSessionId logic applies to autofill sessions

            structure.setAcquisitionStartTime(startTime);
            structure.setAcquisitionEndTime(SystemClock.uptimeMillis());

            mLastAssistStructures.add(new WeakReference<>(structure));
        }

        IActivityTaskManager mgr = ActivityTaskManager.getService();
        try {
            mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /** Fetches the user actions for the corresponding activity */
    private void handleRequestDirectActions(@NonNull IBinder activityToken,
            @NonNull IVoiceInteractor interactor, @NonNull CancellationSignal cancellationSignal,
            @NonNull RemoteCallback callback, int retryCount) {
        final ActivityClientRecord r = mActivities.get(activityToken);
        if (r == null) {
            Log.w(TAG, "requestDirectActions(): no activity for " + activityToken);
            callback.sendResult(null);
            return;
        }
        final int lifecycleState = r.getLifecycleState();
        if (lifecycleState < ON_START) {
            // TODO(b/234173463): requestDirectActions callback should indicate errors
            if (retryCount > 0) {
                mH.sendMessageDelayed(
                        PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
                                ActivityThread.this, activityToken, interactor, cancellationSignal,
                                callback, retryCount - 1), REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS);
                return;
            }
            Log.w(TAG, "requestDirectActions(" + r + "): wrong lifecycle: " + lifecycleState);
            callback.sendResult(null);
            return;
        }
        if (lifecycleState >= ON_STOP) {
            Log.w(TAG, "requestDirectActions(" + r + "): wrong lifecycle: " + lifecycleState);
            callback.sendResult(null);
            return;
        }
        if (r.activity.mVoiceInteractor == null
                || r.activity.mVoiceInteractor.mInteractor.asBinder()
                != interactor.asBinder()) {
            if (r.activity.mVoiceInteractor != null) {
                r.activity.mVoiceInteractor.destroy();
            }
            r.activity.mVoiceInteractor = new VoiceInteractor(interactor, r.activity,
                    r.activity, Looper.myLooper());
        }
        r.activity.onGetDirectActions(cancellationSignal, (actions) -> {
            Objects.requireNonNull(actions);
            Preconditions.checkCollectionElementsNotNull(actions, "actions");
            if (!actions.isEmpty()) {
                final int actionCount = actions.size();
                for (int i = 0; i < actionCount; i++) {
                    final DirectAction action = actions.get(i);
                    action.setSource(r.activity.getTaskId(), r.activity.getAssistToken());
                }
                final Bundle result = new Bundle();
                result.putParcelable(DirectAction.KEY_ACTIONS_LIST,
                        new ParceledListSlice<>(actions));
                callback.sendResult(result);
            } else {
                callback.sendResult(null);
            }
        });
    }

    /** Performs an actions in the corresponding activity */
    private void handlePerformDirectAction(@NonNull IBinder activityToken,
            @NonNull String actionId, @Nullable Bundle arguments,
            @NonNull CancellationSignal cancellationSignal,
            @NonNull RemoteCallback resultCallback) {
        final ActivityClientRecord r = mActivities.get(activityToken);
        if (r != null) {
            final int lifecycleState = r.getLifecycleState();
            if (lifecycleState < ON_START || lifecycleState >= ON_STOP) {
                resultCallback.sendResult(null);
                return;
            }

            if (actionId.equals(AutofillManager.DIRECT_ACTION_ID_REMOTE_AUTOFILL)) {
                AutofillId autofillId =
                        arguments.getParcelable(
                                AutofillManager.EXTRA_REMOTE_AUTOFILL_ID, AutofillId.class);
                AutofillValue autofillValue =
                        arguments.getParcelable(
                                AutofillManager.EXTRA_REMOTE_AUTOFILL_VALUE, AutofillValue.class);
                if (autofillId != null && autofillValue != null) {
                    r.activity.autofillViewIfAvailable(autofillId, autofillValue);
                }
            }

            final Bundle nonNullArguments = (arguments != null) ? arguments : Bundle.EMPTY;
            r.activity.onPerformDirectAction(actionId, nonNullArguments, cancellationSignal,
                    resultCallback::sendResult);
        } else {
            resultCallback.sendResult(null);
        }
    }

    public void handleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
        ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            r.activity.onTranslucentConversionComplete(drawComplete);
        }
    }

    public void onNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
        ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            r.activity.onNewSceneTransitionInfo(info);
        }
    }

    public void handleInstallProvider(ProviderInfo info) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
            installContentProviders(mInitialApplication, Arrays.asList(info));
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    private void handleEnterAnimationComplete(IBinder token) {
        ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            r.activity.dispatchEnterAnimationComplete();
        }
    }

    private void handleStartBinderTracking() {
        Binder.enableStackTracking();
    }

    private void handleStopBinderTrackingAndDump(ParcelFileDescriptor fd) {
        try {
            Binder.disableStackTracking();
            Binder.getTransactionTracker().writeTracesToFile(fd);
        } finally {
            IoUtils.closeQuietly(fd);
            Binder.getTransactionTracker().clearTraces();
        }
    }

    @Override
    public void handlePictureInPictureRequested(ActivityClientRecord r) {
        final boolean receivedByApp = r.activity.onPictureInPictureRequested();
        if (!receivedByApp) {
            // Previous recommendation was for apps to enter picture-in-picture in
            // onUserLeavingHint() for cases such as the app being put into the background. For
            // backwards compatibility with apps that are not using the newer
            // onPictureInPictureRequested() callback, we schedule the life cycle events needed to
            // trigger onUserLeavingHint(), then we return the activity to its previous state.
            schedulePauseWithUserLeaveHintAndReturnToCurrentState(r);
        }
    }

    @Override
    public void handlePictureInPictureStateChanged(@NonNull ActivityClientRecord r,
            PictureInPictureUiState pipState) {
        r.activity.onPictureInPictureUiStateChanged(pipState);
    }

    /**
     * Register a splash screen manager to this process.
     */
    public void registerSplashScreenManager(
            @NonNull SplashScreen.SplashScreenManagerGlobal manager) {
        synchronized (this) {
            mSplashScreenGlobal = manager;
        }
    }

    @Override
    public boolean isHandleSplashScreenExit(@NonNull IBinder token) {
        synchronized (this) {
            return mSplashScreenGlobal != null && mSplashScreenGlobal.containsExitListener(token);
        }
    }

    @Override
    public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
            @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
            @NonNull SurfaceControl startingWindowLeash) {
        final DecorView decorView = r.window != null ? (DecorView) r.window.peekDecorView() : null;
        if (parcelable != null && decorView != null) {
            createSplashScreen(r, decorView, parcelable, startingWindowLeash);
        } else {
            // shouldn't happen!
            Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
        }
    }

    private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
            SplashScreenView.SplashScreenViewParcelable parcelable,
            @NonNull SurfaceControl startingWindowLeash) {
        final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
        final SplashScreenView view = builder.createFromParcel(parcelable).build();
        view.attachHostWindow(r.window);
        decorView.addView(view);
        view.requestLayout();

        view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            private boolean mHandled = false;
            @Override
            public boolean onPreDraw() {
                if (mHandled) {
                    return true;
                }
                mHandled = true;
                // Transfer the splash screen view from shell to client.
                // Call syncTransferSplashscreenViewTransaction at the first onPreDraw, so we can
                // ensure the client view is ready to show, and can use applyTransactionOnDraw to
                // make all transitions happen at the same frame.
                syncTransferSplashscreenViewTransaction(
                        view, r.token, decorView, startingWindowLeash);
                view.post(() -> view.getViewTreeObserver().removeOnPreDrawListener(this));
                return true;
            }
        });
    }

    private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) {
        Trace.instant(Trace.TRACE_TAG_VIEW, "reportSplashscreenViewShown");
        ActivityClient.getInstance().reportSplashScreenAttached(token);
        synchronized (this) {
            if (mSplashScreenGlobal != null) {
                mSplashScreenGlobal.handOverSplashScreenView(token, view);
            }
        }
    }

    private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token,
            View decorView, @NonNull SurfaceControl startingWindowLeash) {
        // Ensure splash screen view is shown before remove the splash screen window.
        // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw
        // to ensure the transfer of surface view and hide starting window are happen at the same
        // frame.
        final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
        transaction.hide(startingWindowLeash);
        startingWindowLeash.release();
        decorView.getViewRootImpl().applyTransactionOnDraw(transaction);
        view.syncTransferSurfaceOnDraw();

        if (decorView.isHardwareAccelerated()) {
            decorView.getViewRootImpl().registerRtFrameCallback(
                    new HardwareRenderer.FrameDrawingCallback() {
                        @Override
                        public void onFrameDraw(long frame) { }
                        @Override
                        public HardwareRenderer.FrameCommitCallback onFrameDraw(
                                int syncResult, long frame) {
                            return didProduceBuffer -> {
                                Trace.instant(Trace.TRACE_TAG_VIEW, "transferSplashscreenView");
                                // Tell server we can remove the starting window after frame commit.
                                decorView.postOnAnimation(() ->
                                        reportSplashscreenViewShown(token, view));
                            };
                        }
                    });
        } else {
            Trace.instant(Trace.TRACE_TAG_VIEW, "transferSplashscreenView_software");
            // Tell server we can remove the starting window after frame commit.
            decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));
        }
    }

    /**
     * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
     * return to its previous state. This allows activities that rely on onUserLeaveHint instead of
     * onPictureInPictureRequested to enter picture-in-picture.
     */
    private void schedulePauseWithUserLeaveHintAndReturnToCurrentState(ActivityClientRecord r) {
        final int prevState = r.getLifecycleState();
        if (prevState != ON_RESUME && prevState != ON_PAUSE) {
            return;
        }

        switch (prevState) {
            case ON_RESUME:
                // Schedule a PAUSE then return to RESUME.
                schedulePauseWithUserLeavingHint(r);
                scheduleResume(r);
                break;
            case ON_PAUSE:
                // Schedule a RESUME then return to PAUSE.
                scheduleResume(r);
                schedulePauseWithUserLeavingHint(r);
                break;
        }
    }

    private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
        final ClientTransaction transaction = new ClientTransaction(mAppThread);
        final PauseActivityItem pauseActivityItem = new PauseActivityItem(r.token,
                r.activity.isFinishing(), /* userLeaving */ true,
                /* dontReport */ false, /* autoEnteringPip */ false);
        transaction.addTransactionItem(pauseActivityItem);
        executeTransaction(transaction);
    }

    private void scheduleResume(ActivityClientRecord r) {
        final ClientTransaction transaction = new ClientTransaction(mAppThread);
        final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(r.token,
                /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
        transaction.addTransactionItem(resumeActivityItem);
        executeTransaction(transaction);
    }

    private void handleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor interactor) {
        final ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            r.voiceInteractor = interactor;
            r.activity.setVoiceInteractor(interactor);
            if (interactor == null) {
                r.activity.onLocalVoiceInteractionStopped();
            } else {
                r.activity.onLocalVoiceInteractionStarted();
            }
        }
    }

    private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) {
        try {
            VMDebug.attachAgent(agent, classLoader);
            return true;
        } catch (IOException e) {
            Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent);
            return false;
        }
    }

    static void handleAttachAgent(String agent, LoadedApk loadedApk) {
        ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null;
        if (attemptAttachAgent(agent, classLoader)) {
            return;
        }
        if (classLoader != null) {
            attemptAttachAgent(agent, null);
        }
    }

    static void handleAttachStartupAgents(String dataDir) {
        try {
            Path codeCache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
            if (!Files.exists(codeCache)) {
                return;
            }
            Path startupPath = codeCache.resolve("startup_agents");
            if (Files.exists(startupPath)) {
                try (DirectoryStream<Path> startupFiles = Files.newDirectoryStream(startupPath)) {
                    for (Path p : startupFiles) {
                        handleAttachAgent(
                                p.toAbsolutePath().toString()
                                        + "="
                                        + dataDir,
                                null);
                    }
                }
            }
        } catch (Exception e) {
            // Ignored.
        }
    }

    private void updateUiTranslationState(IBinder activityToken, int state,
            TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds,
            UiTranslationSpec uiTranslationSpec) {
        final ActivityClientRecord r = mActivities.get(activityToken);
        if (r == null) {
            Log.w(TAG, "updateUiTranslationState(): no activity for " + activityToken);
            return;
        }
        r.activity.updateUiTranslationState(
                state, sourceSpec, targetSpec, viewIds, uiTranslationSpec);
    }

    private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();

    /**
     * Return the Intent that's currently being handled by a
     * BroadcastReceiver on this thread, or null if none.
     * @hide
     */
    public static Intent getIntentBeingBroadcast() {
        return sCurrentBroadcastIntent.get();
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private void handleReceiver(ReceiverData data) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();

        String component = data.intent.getComponent().getClassName();

        final LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);

        IActivityManager mgr = ActivityManager.getService();

        Application app;
        BroadcastReceiver receiver;
        ContextImpl context;
        try {
            app = packageInfo.makeApplicationInner(false, mInstrumentation);
            context = (ContextImpl) app.getBaseContext();
            if (data.info.splitName != null) {
                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
            }
            if (data.info.attributionTags != null && data.info.attributionTags.length > 0) {
                final String attributionTag = data.info.attributionTags[0];
                context = (ContextImpl) context.createAttributionContext(attributionTag);
            }
            context = (ContextImpl) createDisplayContextIfNeeded(context, data);
            java.lang.ClassLoader cl = context.getClassLoader();
            data.intent.setExtrasClassLoader(cl);
            data.intent.prepareToEnterProcess(
                    isProtectedComponent(data.info) || isProtectedBroadcast(data.intent),
                    context.getAttributionSource());
            data.setExtrasClassLoader(cl);
            receiver = packageInfo.getAppFactory()
                    .instantiateReceiver(cl, data.info.name, data.intent);
        } catch (Exception e) {
            if (DEBUG_BROADCAST) Slog.i(TAG,
                    "Finishing failed broadcast to " + data.intent.getComponent());
            data.sendFinished(mgr);
            throw new RuntimeException(
                "Unable to instantiate receiver " + component, e);
        }

        long debugStoreId = -1;
        if(DEBUG_STORE_ENABLED) {
            debugStoreId = DebugStore.recordBroadcastReceive(System.identityHashCode(data),
                receiver.getClass().getSimpleName());
        }
        try {
            if (localLOGV) Slog.v(
                TAG, "Performing receive of " + data.intent
                + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + packageInfo.getPackageName()
                + ", comp=" + data.intent.getComponent().toShortString()
                + ", dir=" + packageInfo.getAppDir());

            sCurrentBroadcastIntent.set(data.intent);
            receiver.setPendingResult(data);
            receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);
        } catch (Exception e) {
            if (DEBUG_BROADCAST) Slog.i(TAG,
                    "Finishing failed broadcast to " + data.intent.getComponent());
            data.sendFinished(mgr);
            if (!mInstrumentation.onException(receiver, e)) {
                throw new RuntimeException(
                    "Unable to start receiver " + component, e);
            }
        } finally {
            sCurrentBroadcastIntent.set(null);
            if(DEBUG_STORE_ENABLED) {
                DebugStore.recordEventEnd(debugStoreId);
            }
        }

        if (receiver.getPendingResult() != null) {
            data.finish();
        }
    }

    /**
     * Creates a display context if the broadcast was initiated with a launch display ID.
     *
     * <p>When a broadcast initiates from a widget on a secondary display, the originating
     * display ID is included as an extra in the intent. This is accomplished by
     * {@link PendingIntentRecord#createSafeActivityOptionsBundle}, which transfers the launch
     * display ID from ActivityOptions into the intent's extras bundle. This method checks for
     * the presence of that extra and creates a display context associated with the initiated
     * display if it exists. This ensures that when the {@link BroadcastReceiver} invokes
     * {@link Context#startActivity(Intent)}, the activity is launched on the correct display.
     *
     * @param context The original context of the receiver.
     * @param data    The {@link ReceiverData} containing optional display information.
     * @return A display context if applicable; otherwise the original context.
     */
    @NonNull
    @VisibleForTesting(visibility = PRIVATE)
    public Context createDisplayContextIfNeeded(@NonNull Context context,
            @NonNull ReceiverData data) {
        final ActivityOptions options = data.mOptions;
        if (options == null) {
            return context;
        }

        final int launchDisplayId = options.getLaunchDisplayId();
        if (launchDisplayId == INVALID_DISPLAY) {
            return context;
        }

        final DisplayManager dm = context.getSystemService(DisplayManager.class);
        if (dm == null) {
            return context;
        }

        final Display display = dm.getDisplay(launchDisplayId);
        if (display == null) {
            Slog.w(TAG, "Unable to create a display context for nonexistent display "
                    + launchDisplayId);
            return context;
        }

        return context.createDisplayContext(display);
    }

    // Instantiate a BackupAgent and tell it that it's alive
    private void handleCreateBackupAgent(CreateBackupAgentData data) {
        if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data);

        // Validity check the requested target package's uid against ours
        try {
            PackageInfo requestedPackage = getPackageManager().getPackageInfo(
                    data.appInfo.packageName, 0, UserHandle.myUserId());
            int backupAgentUid = enablePccFrameworkSupport()
                    ? requestedPackage.applicationInfo.getBackupAgentUid()
                    : requestedPackage.applicationInfo.uid;
            if (backupAgentUid != Process.myUid()) {
                Slog.w(TAG, "Asked to instantiate non-matching package or UID "
                        + data.appInfo.packageName);
                return;
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }

        // no longer idle; we have backup work to do
        unscheduleGcIdler();

        // instantiate the BackupAgent class named in the manifest
        final LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
        String packageName = packageInfo.mPackageName;
        if (packageName == null) {
            Slog.d(TAG, "Asked to create backup agent for nonexistent package");
            return;
        }

        String classname = getBackupAgentName(data);

        try {
            IBinder binder = null;
            ArrayMap<String, BackupAgent> backupAgents = getBackupAgentsForUser(data.userId);
            BackupAgent agent = backupAgents.get(packageName);
            if (agent != null) {
                // reusing the existing instance
                if (DEBUG_BACKUP) {
                    Slog.v(TAG, "Reusing existing agent instance");
                }
                binder = agent.onBind();
            } else {
                try {
                    if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);

                    java.lang.ClassLoader cl = packageInfo.getClassLoader();
                    if (customBackupagentCreation()) {
                        agent = packageInfo.getAppFactory().instantiateBackupAgent(cl, classname);
                    } else {
                        agent = (BackupAgent) cl.loadClass(classname).newInstance();
                    }

                    // set up the agent's context
                    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
                    context.setOuterContext(agent);
                    agent.attach(context);

                    agent.onCreate(UserHandle.of(data.userId), data.backupDestination,
                            getOperationTypeFromBackupMode(data.backupMode));
                    binder = agent.onBind();
                    backupAgents.put(packageName, agent);
                } catch (Exception e) {
                    // If this is during restore, fail silently; otherwise go
                    // ahead and let the user see the crash.
                    Slog.e(TAG, "Agent threw during creation", e);
                    if (data.backupMode != ApplicationThreadConstants.BACKUP_MODE_RESTORE
                            && data.backupMode !=
                                    ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL) {
                        throw e;
                    }
                    // falling through with 'binder' still null
                }
            }

            // tell the OS that we're live now
            try {
                ActivityManager.getService().backupAgentCreated(packageName, binder, data.userId);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            throw new RuntimeException("Unable to create BackupAgent "
                    + classname, e);
        }
    }

    @OperationType
    private static int getOperationTypeFromBackupMode(int backupMode) {
        switch (backupMode) {
            case ApplicationThreadConstants.BACKUP_MODE_RESTORE:
            case ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL:
                return OperationType.RESTORE;
            case ApplicationThreadConstants.BACKUP_MODE_FULL:
            case ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL:
                return OperationType.BACKUP;
            default:
                Slog.w(TAG, "Invalid backup mode when initialising BackupAgent: "
                        + backupMode);
                return OperationType.UNKNOWN;
        }
    }

    private String getBackupAgentName(CreateBackupAgentData data) {
        String agentName = data.appInfo.backupAgentName;
        // full backup operation but no app-supplied agent?  use the default implementation
        if (agentName == null && (data.backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
                || data.backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL)) {
            agentName = DEFAULT_FULL_BACKUP_AGENT;
        }
        return agentName;
    }

    // Tear down a BackupAgent
    private void handleDestroyBackupAgent(CreateBackupAgentData data) {
        if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);

        final LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
        String packageName = packageInfo.mPackageName;
        ArrayMap<String, BackupAgent> backupAgents = getBackupAgentsForUser(data.userId);
        BackupAgent agent = backupAgents.get(packageName);
        if (agent != null) {
            try {
                agent.onDestroy();
            } catch (Exception e) {
                Slog.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
                e.printStackTrace();
            }
            backupAgents.remove(packageName);
        } else {
            Slog.w(TAG, "Attempt to destroy unknown backup agent " + data);
        }
    }

    private ArrayMap<String, BackupAgent> getBackupAgentsForUser(int userId) {
        ArrayMap<String, BackupAgent> backupAgents = mBackupAgentsByUser.get(userId);
        if (backupAgents == null) {
            backupAgents = new ArrayMap<>();
            mBackupAgentsByUser.put(userId, backupAgents);
        }
        return backupAgents;
    }

    @UnsupportedAppUsage
    private void handleCreateService(CreateServiceData data) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();

        final LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);
        Service service = null;
        try {
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

            Application app = packageInfo.makeApplicationInner(false, mInstrumentation);

            final java.lang.ClassLoader cl;
            if (data.info.splitName != null) {
                cl = packageInfo.getSplitClassLoader(data.info.splitName);
            } else {
                cl = packageInfo.getClassLoader();
            }
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
            ContextImpl context = ContextImpl.getImpl(service
                    .createServiceBaseContext(this, packageInfo));
            if (data.info.splitName != null) {
                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
            }
            if (data.info.attributionTags != null && data.info.attributionTags.length > 0) {
                final String attributionTag = data.info.attributionTags[0];
                context = (ContextImpl) context.createAttributionContext(attributionTag);
            }
            // Service resources must be initialized with the same loaders as the application
            // context.
            context.getResources().addLoaders(
                    app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

            context.setOuterContext(service);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
            if (!service.isUiContext()) { // WindowProviderService is a UI Context.
                if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT) {
                    service.updateDeviceId(mLastReportedDeviceId);
                } else {
                    VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
                    if (vdm != null && vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
                        service.updateDeviceId(mLastReportedDeviceId);
                    }
                }
            }
            service.onCreate();
            mServicesData.put(data.token, data);
            mServices.put(data.token, service);
            try {
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to create service " + data.info.name, e);
            }
        }
    }

    private void handleBindService(BindServiceData data) {
        CreateServiceData createData = mServicesData.get(data.token);
        Service s = mServices.get(data.token);
        if (DEBUG_SERVICE)
            Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
        if (s != null) {
            try {
                data.intent.setExtrasClassLoader(s.getClassLoader());
                data.intent.prepareToEnterProcess(isProtectedComponent(createData.info),
                        s.getAttributionSource());
                try {
                    if (!data.rebind) {
                        IBinder binder = s.onBind(data.intent);
                        ActivityManager.getService().publishService(
                                data.token, data.bindToken, binder);
                    } else {
                        s.onRebind(data.intent);
                        ActivityManager.getService().serviceDoneExecuting(
                                data.token, SERVICE_DONE_EXECUTING_REBIND, 0, 0);
                    }
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to bind to service " + s
                            + " with " + data.intent, e);
                }
            }
        }
    }

    private void handleUnbindService(BindServiceData data) {
        CreateServiceData createData = mServicesData.get(data.token);
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                data.intent.setExtrasClassLoader(s.getClassLoader());
                data.intent.prepareToEnterProcess(isProtectedComponent(createData.info),
                        s.getAttributionSource());
                boolean doRebind = s.onUnbind(data.intent);
                try {
                    if (doRebind) {
                        ActivityManager.getService().unbindFinished(
                                data.token, data.bindToken);
                    } else {
                        ActivityManager.getService().serviceDoneExecuting(
                                data.token, SERVICE_DONE_EXECUTING_UNBIND, 0, 0);
                    }
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to unbind to service " + s
                            + " with " + data.intent, e);
                }
            }
        }
    }

    private void handleDumpGfxInfo(DumpComponentInfo info) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
            ThreadedRenderer.handleDumpGfxInfo(info.fd.getFileDescriptor(), info.args);
        } catch (Exception e) {
            Log.w(TAG, "Caught exception from dumpGfxInfo()", e);
        } finally {
            IoUtils.closeQuietly(info.fd);
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    private void handleDumpService(DumpComponentInfo info) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
            Service s = mServices.get(info.token);
            if (s != null) {
                PrintWriter pw = new FastPrintWriter(new FileOutputStream(
                        info.fd.getFileDescriptor()));
                s.dump(info.fd.getFileDescriptor(), pw, info.args);
                pw.flush();
            }
        } finally {
            IoUtils.closeQuietly(info.fd);
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    private void handleDumpResources(DumpResourcesData info) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
            PrintWriter pw = new FastPrintWriter(new FileOutputStream(
                    info.fd.getFileDescriptor()));

            Resources.dumpHistory(pw, "");
            pw.flush();
            ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh");
            if (info.finishCallback != null) {
                info.finishCallback.sendResult(null);
            }
        } finally {
            IoUtils.closeQuietly(info.fd);
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    private void handleDumpActivity(DumpComponentInfo info) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
            ActivityClientRecord r = mActivities.get(info.token);
            if (r != null && r.activity != null) {
                PrintWriter pw = new FastPrintWriter(new FileOutputStream(
                        info.fd.getFileDescriptor()));
                r.activity.dumpInternal(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
                pw.flush();
            }
        } finally {
            IoUtils.closeQuietly(info.fd);
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    private void handleDumpProvider(DumpComponentInfo info) {
        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
            ProviderClientRecord r = mLocalProviders.get(info.token);
            if (r != null && r.mLocalProvider != null) {
                PrintWriter pw = new FastPrintWriter(new FileOutputStream(
                        info.fd.getFileDescriptor()));
                r.mLocalProvider.dump(info.fd.getFileDescriptor(), pw, info.args);
                pw.flush();
            }
        } finally {
            IoUtils.closeQuietly(info.fd);
            StrictMode.setThreadPolicy(oldPolicy);
        }
    }

    private void handleServiceArgs(ServiceArgsData data) {
        CreateServiceData createData = mServicesData.get(data.token);
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                if (data.args != null) {
                    data.args.setExtrasClassLoader(s.getClassLoader());
                    data.args.prepareToEnterProcess(isProtectedComponent(createData.info),
                            s.getAttributionSource());
                }
                int res;
                if (!data.taskRemoved) {
                    res = s.onStartCommand(data.args, data.flags, data.startId);
                } else {
                    s.onTaskRemoved(data.args);
                    res = Service.START_TASK_REMOVED_COMPLETE;
                }

                QueuedWork.waitToFinish();

                try {
                    ActivityManager.getService().serviceDoneExecuting(data.token,
                            SERVICE_DONE_EXECUTING_START, data.startId, res);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to start service " + s
                            + " with " + data.args, e);
                }
            }
        }
    }

    private void handleStopService(IBinder token) {
        mServicesData.remove(token);
        Service s = mServices.remove(token);
        if (s != null) {
            try {
                if (localLOGV) Slog.v(TAG, "Destroying service " + s);
                s.onDestroy();
                s.detachAndCleanUp();
                Context context = s.getBaseContext();
                if (context instanceof ContextImpl) {
                    final String who = s.getClassName();
                    ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
                }

                QueuedWork.waitToFinish();

                try {
                    ActivityManager.getService().serviceDoneExecuting(
                            token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException("Unable to stop service " + s, e);
                }
                Slog.i(TAG, "handleStopService: exception for " + token, e);
            }
        } else {
            Slog.i(TAG, "handleStopService: token=" + token + " not found.");
        }
        //Slog.i(TAG, "Running services: " + mServices);
    }

    private void handleTimeoutService(IBinder token, int startId) {
        Service s = mServices.get(token);
        if (s != null) {
            try {
                if (localLOGV) Slog.v(TAG, "Timeout short service " + s);

                // Unlike other service callbacks, we don't do serviceDoneExecuting() here.
                // "service executing" state is used to boost the procstate / oom-adj, but
                // for short-FGS timeout, we have a specific control for them anyway, so
                // we don't have to do that.
                s.callOnTimeout(startId);
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to call onTimeout on service " + s, e);
                }
                Slog.i(TAG, "handleTimeoutService: exception for " + token, e);
            }
        } else {
            Slog.wtf(TAG, "handleTimeoutService: token=" + token + " not found.");
        }
    }

    private void handleTimeoutServiceForType(IBinder token, int startId,
            @ServiceInfo.ForegroundServiceType int fgsType) {
        Service s = mServices.get(token);
        if (s != null) {
            try {
                if (localLOGV) Slog.v(TAG, "Timeout service " + s);

                s.callOnTimeLimitExceeded(startId, fgsType);
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to call onTimeLimitExceeded on service " + s, e);
                }
                Slog.i(TAG, "handleTimeoutServiceForType: exception for " + token, e);
            }
        } else {
            Slog.wtf(TAG, "handleTimeoutServiceForType: token=" + token + " not found.");
        }
    }

    /**
     * Resume the activity.
     * @param r Target activity record.
     * @param finalStateRequest Flag indicating if this is part of final state resolution for a
     *                          transaction.
     * @param reason Reason for performing the action.
     *
     * @return {@code true} that was resumed, {@code false} otherwise.
     */
    @VisibleForTesting
    public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            String reason) {
        if (localLOGV) {
            Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
        }
        if (r.activity.mFinished) {
            return false;
        }
        if (r.getLifecycleState() == ON_RESUME) {
            if (!finalStateRequest) {
                final RuntimeException e = new IllegalStateException(
                        "Trying to resume activity which is already resumed");
                Slog.e(TAG, e.getMessage(), e);
                Slog.e(TAG, r.getStateString());
                // TODO(lifecycler): A double resume request is possible when an activity
                // receives two consequent transactions with relaunch requests and "resumed"
                // final state requests and the second relaunch is omitted. We still try to
                // handle two resume requests for the final state. For cases other than this
                // one, we don't expect it to happen.
            }
            return false;
        }
        if (finalStateRequest) {
            r.hideForNow = false;
            r.activity.mStartedActivity = false;
        }
        try {
            r.activity.onStateNotSaved();
            r.activity.mFragments.noteStateNotSaved();
            checkAndBlockForNetworkAccess();
            if (r.pendingIntents != null) {
                deliverNewIntents(r, r.pendingIntents);
                r.pendingIntents = null;
            }
            if (r.pendingResults != null) {
                deliverResults(r, r.pendingResults, reason);
                r.pendingResults = null;
            }
            r.activity.performResume(r.startsNotResumed, reason);

            r.state = null;
            r.persistentState = null;
            r.setState(ON_RESUME);

            reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException("Unable to resume activity "
                        + r.intent.getComponent().toShortString(), e);
            }
        }
        return true;
    }

    static void cleanUpPendingRemoveWindows(ActivityClientRecord r, boolean force) {
        if (r.mPreserveWindow && !force) {
            return;
        }
        if (r.mPendingRemoveWindow != null) {
            r.mPendingRemoveWindowManager.removeViewImmediate(
                    r.mPendingRemoveWindow.getDecorView());
            IBinder wtoken = r.mPendingRemoveWindow.getDecorView().getWindowToken();
            if (wtoken != null) {
                WindowManagerGlobal.getInstance().closeAll(wtoken,
                        r.activity.getClass().getName(), "Activity");
            }
        }
        r.mPendingRemoveWindow = null;
        r.mPendingRemoveWindowManager = null;
    }

    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        // TODO Push resumeArgs into the activity for consideration
        // skip below steps for double-resume and r.mFinish = true case.
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        if (mActivitiesToBeDestroyed.containsKey(r.token)) {
            // Although the activity is resumed, it is going to be destroyed. So the following
            // UI operations are unnecessary and also prevents exception because its token may
            // be gone that window manager cannot recognize it. All necessary cleanup actions
            // performed below will be done while handling destruction.
            return;
        }

        final Activity a = r.activity;

        if (localLOGV) {
            Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
                    + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
        }

        final int forwardBit = isForward
                ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

        // If the window hasn't yet been added to the window manager,
        // and this guy didn't finish itself or start another activity,
        // then go ahead and add the window.
        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            willBeVisible = ActivityClient.getInstance().willActivityBeVisible(
                    a.getActivityToken());
        }
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }

        // Get rid of anything left hanging around.
        cleanUpPendingRemoveWindows(r, false /* force */);

        // The window is now visible if it has been added, we are not
        // simply finishing, and we are not starting another activity.
        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
            if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
            ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
            WindowManager.LayoutParams l = impl != null
                    ? impl.mWindowAttributes : r.window.getAttributes();
            if ((l.softInputMode
                    & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                    != forwardBit) {
                l.softInputMode = (l.softInputMode
                        & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                        | forwardBit;
                if (r.activity.mVisibleFromClient) {
                    ViewManager wm = a.getWindowManager();
                    View decor = r.window.getDecorView();
                    wm.updateViewLayout(decor, l);
                }
            }

            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }

            if (shouldSendCompatFakeFocus) {
                // Attaching to a window is asynchronous with the activity being resumed,
                // so it's possible we will need to send a fake focus event after attaching
                if (impl != null) {
                    impl.dispatchCompatFakeFocus();
                } else {
                    r.window.getDecorView().fakeFocusAfterAttachingToWindow();
                }
            }
        }

        mNewActivities.add(r);
        if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
        Looper.myQueue().addIdleHandler(new Idler());
    }


    @Override
    public void handleTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
            String reason) {
        if (DEBUG_ORDER) {
            Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
        }

        if (r.isTopResumedActivity == onTop) {
            if (!Build.IS_DEBUGGABLE) {
                Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
                return;
            }
            // TODO(b/209744518): Remove this short-term workaround while fixing the binder failure.
            Slog.e(TAG, "Activity top position already set to onTop=" + onTop);
        }

        r.isTopResumedActivity = onTop;

        if (r.getLifecycleState() == ON_RESUME) {
            reportTopResumedActivityChanged(r, onTop, "topStateChangedWhenResumed");
        } else {
            if (DEBUG_ORDER) {
                Slog.d(TAG, "Won't deliver top position change in state=" + r.getLifecycleState());
            }
        }
    }

    /**
     * Call {@link Activity#onTopResumedActivityChanged(boolean)} if its top resumed state changed
     * since the last report.
     */
    private void reportTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
            String reason) {
        if (r.lastReportedTopResumedState != onTop) {
            r.lastReportedTopResumedState = onTop;
            r.activity.performTopResumedActivityChanged(onTop, reason);
        }
    }

    @Override
    public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
            boolean autoEnteringPip, PendingTransactionActions pendingActions,
            String reason) {
        if (userLeaving) {
            performUserLeavingActivity(r);
        }

        if (autoEnteringPip) {
            // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also
            // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}.
            r.activity.mIsInPictureInPictureMode = true;
        }
        performPauseActivity(r, finished, reason, pendingActions);

        // Make sure any pending writes are now committed.
        if (r.isPreHoneycomb()) {
            QueuedWork.waitToFinish();
        }
        mSomeActivitiesChanged = true;
    }

    final void performUserLeavingActivity(ActivityClientRecord r) {
        mInstrumentation.callActivityOnPictureInPictureRequested(r.activity);
        mInstrumentation.callActivityOnUserLeaving(r.activity);
    }

    final Bundle performPauseActivity(IBinder token, boolean finished, String reason,
            PendingTransactionActions pendingActions) {
        ActivityClientRecord r = mActivities.get(token);
        return r != null ? performPauseActivity(r, finished, reason, pendingActions) : null;
    }

    /**
     * Pause the activity.
     * @return Saved instance state for pre-Honeycomb apps if it was saved, {@code null} otherwise.
     */
    private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason,
            PendingTransactionActions pendingActions) {
        if (r.paused) {
            if (r.activity.mFinished) {
                // If we are finishing, we won't call onResume() in certain cases.
                // So here we likewise don't want to call onPause() if the activity
                // isn't resumed.
                return null;
            }
            RuntimeException e = new RuntimeException(
                    "Performing pause of activity that is not resumed: "
                    + r.intent.getComponent().toShortString());
            Slog.e(TAG, e.getMessage(), e);
        }
        if (finished) {
            r.activity.mFinished = true;
        }

        // Pre-Honeycomb apps always save their state before pausing
        final boolean shouldSaveState = !r.activity.mFinished && r.isPreHoneycomb();
        if (shouldSaveState) {
            callActivityOnSaveInstanceState(r);
        }

        performPauseActivityIfNeeded(r, reason);

        // Notify any outstanding on paused listeners
        ArrayList<OnActivityPausedListener> listeners;
        synchronized (mOnPauseListeners) {
            listeners = mOnPauseListeners.remove(r.activity);
        }
        int size = (listeners != null ? listeners.size() : 0);
        for (int i = 0; i < size; i++) {
            listeners.get(i).onPaused(r.activity);
        }

        final Bundle oldState = pendingActions != null ? pendingActions.getOldState() : null;
        if (oldState != null) {
            // We need to keep around the original state, in case we need to be created again.
            // But we only do this for pre-Honeycomb apps, which always save their state when
            // pausing, so we can not have them save their state when restarting from a paused
            // state. For HC and later, we want to (and can) let the state be saved as the
            // normal part of stopping the activity.
            if (r.isPreHoneycomb()) {
                r.state = oldState;
            }
        }

        return shouldSaveState ? r.state : null;
    }

    private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {
        if (r.paused) {
            // You are already paused silly...
            return;
        }

        // Always reporting top resumed position loss when pausing an activity. If necessary, it
        // will be restored in performResumeActivity().
        reportTopResumedActivityChanged(r, false /* onTop */, "pausing");

        try {
            r.activity.mCalled = false;
            mInstrumentation.callActivityOnPause(r.activity);
            if (!r.activity.mCalled) {
                throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
                        + " did not call through to super.onPause()");
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException("Unable to pause activity "
                        + safeToComponentShortString(r.intent), e);
            }
        }
        r.setState(ON_PAUSE);
    }

    // TODO(b/176961850): Make LocalActivityManager call performStopActivityInner. We cannot remove
    // this since it's a high usage hidden API.
    /** Called from {@link LocalActivityManager}. */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 176961850,
            publicAlternatives = "{@code N/A}")
    final void performStopActivity(IBinder token, boolean saveState, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
                reason);
    }

    private static final class ProviderRefCount {
        public final ContentProviderHolder holder;
        public final ProviderClientRecord client;
        public int stableCount;
        public int unstableCount;

        // When this is set, the stable and unstable ref counts are 0 and
        // we have a pending operation scheduled to remove the ref count
        // from the activity manager.  On the activity manager we are still
        // holding an unstable ref, though it is not reflected in the counts
        // here.
        public boolean removePending;

        ProviderRefCount(ContentProviderHolder inHolder,
                ProviderClientRecord inClient, int sCount, int uCount) {
            holder = inHolder;
            client = inClient;
            stableCount = sCount;
            unstableCount = uCount;
        }
    }

    /**
     * Core implementation of stopping an activity.
     * @param r Target activity client record.
     * @param info Action that will report activity stop to server.
     * @param saveState Flag indicating whether the activity state should be saved.
     * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
     *                          request for a transaction.
     * @param reason Reason for performing this operation.
     */
    private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
            boolean saveState, boolean finalStateRequest, String reason) {
        if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
        if (r.stopped) {
            if (r.activity.mFinished) {
                // If we are finishing, we won't call onResume() in certain
                // cases.  So here we likewise don't want to call onStop()
                // if the activity isn't resumed.
                return;
            }
            if (!finalStateRequest) {
                final RuntimeException e = new RuntimeException(
                        "Performing stop of activity that is already stopped: "
                                + r.intent.getComponent().toShortString());
                Slog.e(TAG, e.getMessage(), e);
                Slog.e(TAG, r.getStateString());
            }
        }

        // One must first be paused before stopped...
        performPauseActivityIfNeeded(r, reason);

        if (info != null) {
            try {
                // First create a thumbnail for the activity...
                // For now, don't create the thumbnail here; we are
                // doing that by doing a screen snapshot.
                info.setDescription(r.activity.onCreateDescription());
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to save state of activity "
                            + r.intent.getComponent().toShortString(), e);
                }
            }
        }

        callActivityOnStop(r, saveState, reason);
    }

    /**
     * Calls {@link Activity#onStop()} and {@link Activity#onSaveInstanceState(Bundle)}, and updates
     * the client record's state.
     * All calls to stop an activity must be done through this method to make sure that
     * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call.
     */
    private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
        // Before P onSaveInstanceState was called before onStop, starting with P it's
        // called after. Before Honeycomb state was always saved before onPause.
        final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
                && !r.isPreHoneycomb();
        final boolean isPreP = r.isPreP();
        if (shouldSaveState && isPreP) {
            callActivityOnSaveInstanceState(r);
        }

        try {
            r.activity.performStop(r.mPreserveWindow, reason);
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException(
                        "Unable to stop activity "
                                + r.intent.getComponent().toShortString(), e);
            }
        }
        r.setState(ON_STOP);

        if (shouldSaveState && !isPreP) {
            callActivityOnSaveInstanceState(r);
        }
    }

    private void updateVisibility(ActivityClientRecord r, boolean show) {
        View v = r.activity.mDecor;
        if (v != null) {
            if (show) {
                if (!r.activity.mVisibleFromServer) {
                    r.activity.mVisibleFromServer = true;
                    mNumVisibleActivities++;
                    if (r.activity.mVisibleFromClient) {
                        r.activity.makeVisible();
                    }
                }
            } else {
                if (r.activity.mVisibleFromServer) {
                    r.activity.mVisibleFromServer = false;
                    mNumVisibleActivities--;
                    v.setVisibility(View.INVISIBLE);
                }
            }
        }
    }

    @Override
    public void handleStopActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {

        final StopInfo stopInfo = new StopInfo();
        performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
                reason);

        if (localLOGV) Slog.v(
            TAG, "Finishing stop of " + r + ": win=" + r.window);

        updateVisibility(r, false);

        // Make sure any pending writes are now committed.
        if (!r.isPreHoneycomb()) {
            QueuedWork.waitToFinish();
        }

        stopInfo.setActivity(r);
        stopInfo.setState(r.state);
        stopInfo.setPersistentState(r.persistentState);
        if (android.companion.Flags.taskContinuity()) {
            stopInfo.setHandoffActivityData(r.handoffActivityData);
        }

        pendingActions.setStopInfo(stopInfo);
        mSomeActivitiesChanged = true;
    }

    /**
     * Schedule the call to tell the activity manager we have stopped.  We don't do this
     * immediately, because we want to have a chance for any other pending work (in particular
     * memory trim requests) to complete before you tell the activity manager to proceed and allow
     * us to go fully into the background.
     */
    @Override
    public void reportStop(PendingTransactionActions pendingActions) {
        mH.post(pendingActions.getStopInfo());
    }

    @Override
    public void performRestartActivity(ActivityClientRecord r, boolean start) {
        if (r.stopped) {
            r.activity.performRestart(start);
            if (start) {
                r.setState(ON_START);
            }
        }
    }

    @Override
    public void reportRefresh(ActivityClientRecord r) {
        ActivityClient.getInstance().activityRefreshed(r.token);
    }

    private void handleSetCoreSettings(Bundle coreSettings) {
        synchronized (mCoreSettingsLock) {
            mCoreSettings = coreSettings;
        }
        onCoreSettingsChange();
    }

    /**
     * Handles updates to core settings. This may trigger a relaunch of all activities if
     * settings that affect the UI, like debug view attributes, have changed.
     */
    private void onCoreSettingsChange() {
        if (updateDebugViewAttributeState()) {
            // request all activities to relaunch for the changes to take place
            relaunchAllActivities(true /* preserveWindows */, "onCoreSettingsChange");
        }
    }

    private boolean updateDebugViewAttributeState() {
        boolean previousState = View.sDebugViewAttributes;

        // mCoreSettings is only updated from the main thread, while this function is only called
        // from main thread as well, so no need to lock here.
        final Bundle defaultDeviceCoreSettings = getCoreSettingsForDefaultDeviceLocked();
        View.sDebugViewAttributesApplicationPackage = defaultDeviceCoreSettings.getString(
                Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE, "");
        String currentPackage = (mBoundApplication != null && mBoundApplication.appInfo != null)
                ? mBoundApplication.appInfo.packageName : "<unknown-app>";
        View.sDebugViewAttributes =
                defaultDeviceCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0
                        || View.sDebugViewAttributesApplicationPackage.equals(currentPackage);
        return previousState != View.sDebugViewAttributes;
    }

    private void relaunchAllActivities(boolean preserveWindows, String reason) {
        Log.i(TAG, "Relaunch all activities: " + reason);
        for (int i = mActivities.size() - 1; i >= 0; i--) {
            scheduleRelaunchActivityIfPossible(mActivities.valueAt(i), preserveWindows);
        }
    }

    private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
        mCompatibilityInfo = data.info;
        LoadedApk apk = peekPackageInfo(data.pkg, false);
        if (apk != null) {
            apk.setCompatibilityInfo(data.info);
        }
        apk = peekPackageInfo(data.pkg, true);
        if (apk != null) {
            apk.setCompatibilityInfo(data.info);
        }
        mConfigurationController.handleConfigurationChanged(data.info);
    }

    private void deliverResults(ActivityClientRecord r, List<ResultInfo> results, String reason) {
        final int N = results.size();
        for (int i=0; i<N; i++) {
            ResultInfo ri = results.get(i);
            try {
                if (ri.mData != null) {
                    ri.mData.setExtrasClassLoader(r.activity.getClassLoader());
                    ri.mData.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
                            r.activity.getAttributionSource());
                }
                if (DEBUG_RESULTS) Slog.v(TAG,
                        "Delivering result to activity " + r + " : " + ri);
                if (android.security.Flags.contentUriPermissionApis()) {
                    ComponentCaller caller = new ComponentCaller(r.token, ri.mCallerToken);
                    r.activity.dispatchActivityResult(ri.mResultWho,
                            ri.mRequestCode, ri.mResultCode, ri.mData, caller, reason);
                } else {
                    r.activity.dispatchActivityResult(ri.mResultWho,
                            ri.mRequestCode, ri.mResultCode, ri.mData, reason);
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Failure delivering result " + ri + " to activity "
                            + r.intent.getComponent().toShortString(), e);
                }
            }
        }
    }

    @Override
    public void handleSendResult(ActivityClientRecord r, List<ResultInfo> results, String reason) {
        if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
        final boolean resumed = !r.paused;
        if (!r.activity.mFinished && r.activity.mDecor != null
                && r.hideForNow && resumed) {
            // We had hidden the activity because it started another
            // one...  we have gotten a result back and we are not
            // paused, so make sure our window is visible.
            updateVisibility(r, true);
        }
        if (resumed) {
            try {
                // Now we are idle.
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnPause(r.activity);
                if (!r.activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString()
                        + " did not call through to super.onPause()");
                }
            } catch (SuperNotCalledException e) {
                throw e;
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to pause activity "
                            + r.intent.getComponent().toShortString(), e);
                }
            }
        }
        checkAndBlockForNetworkAccess();
        deliverResults(r, results, reason);
        if (resumed) {
            r.activity.performResume(false, reason);
        }
    }

    /** Core implementation of activity destroy call. */
    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            boolean getNonConfigInstance, String reason) {
        Class<? extends Activity> activityClass;
        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
        activityClass = r.activity.getClass();
        if (finishing) {
            r.activity.mFinished = true;
        }

        performPauseActivityIfNeeded(r, "destroy");

        if (!r.stopped) {
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        if (getNonConfigInstance) {
            try {
                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException("Unable to retain activity "
                            + r.intent.getComponent().toShortString(), e);
                }
            }
        }
        try {
            r.activity.mCalled = false;
            mInstrumentation.callActivityOnDestroy(r.activity);
            if (!r.activity.mCalled) {
                throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
                        + " did not call through to super.onDestroy()");
            }
            if (r.window != null) {
                r.window.closeAllPanels();
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException("Unable to destroy activity "
                        + safeToComponentShortString(r.intent), e);
            }
        }
        r.setState(ON_DESTROY);
        schedulePurgeIdler();
        synchronized (this) {
            if (mSplashScreenGlobal != null) {
                mSplashScreenGlobal.tokenDestroyed(r.token);
            }
        }
        // updatePendingActivityConfiguration() reads from mActivities to update
        // ActivityClientRecord which runs in a different thread. Protect modifications to
        // mActivities to avoid race.
        synchronized (mResourcesManager) {
            mActivities.remove(r.token);
        }
        StrictMode.decrementExpectedActivityCount(activityClass);
    }

    private static String safeToComponentShortString(Intent intent) {
        ComponentName component = intent.getComponent();
        return component == null ? "[Unknown]" : component.toShortString();
    }

    @Override
    public Map<IBinder, DestroyActivityItem> getActivitiesToBeDestroyed() {
        return mActivitiesToBeDestroyed;
    }

    @Override
    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing,
            boolean getNonConfigInstance, String reason) {
        performDestroyActivity(r, finishing, getNonConfigInstance, reason);
        cleanUpPendingRemoveWindows(r, finishing);
        WindowManager wm = r.activity.getWindowManager();
        View v = r.activity.mDecor;
        if (v != null) {
            if (r.activity.mVisibleFromServer) {
                mNumVisibleActivities--;
            }
            IBinder wtoken = v.getWindowToken();
            if (r.activity.mWindowAdded) {
                if (r.mPreserveWindow) {
                    // Hold off on removing this until the new activity's window is being added.
                    r.mPendingRemoveWindow = r.window;
                    r.mPendingRemoveWindowManager = wm;
                    // We can only keep the part of the view hierarchy that we control,
                    // everything else must be removed, because it might not be able to
                    // behave properly when activity is relaunching.
                    r.window.clearContentView();
                } else {
                    final ViewRootImpl viewRoot = v.getViewRootImpl();
                    if (viewRoot != null) {
                        // Clear callbacks to avoid the destroyed activity from receiving
                        // configuration or camera compat changes that are no longer effective.
                        viewRoot.setActivityConfigCallback(null);
                    }
                    wm.removeViewImmediate(v);
                }
            }
            if (wtoken != null && r.mPendingRemoveWindow == null) {
                WindowManagerGlobal.getInstance().closeAll(wtoken,
                        r.activity.getClass().getName(), "Activity");
            } else if (r.mPendingRemoveWindow != null) {
                // We're preserving only one window, others should be closed so app views
                // will be detached before the final tear down. It should be done now because
                // some components (e.g. WebView) rely on detach callbacks to perform receiver
                // unregister and other cleanup.
                WindowManagerGlobal.getInstance().closeAllExceptView(r.token, v,
                        r.activity.getClass().getName(), "Activity");
            }
            r.activity.mDecor = null;
        }
        if (r.mPendingRemoveWindow == null) {
            // If we are delaying the removal of the activity window, then
            // we can't clean up all windows here.  Note that we can't do
            // so later either, which means any windows that aren't closed
            // by the app will leak.  Well we try to warning them a lot
            // about leaking windows, because that is a bug, so if they are
            // using this recreate facility then they get to live with leaks.
            WindowManagerGlobal.getInstance().closeAll(r.token,
                    r.activity.getClass().getName(), "Activity");
        }

        // Mocked out contexts won't be participating in the normal
        // process lifecycle, but if we're running with a proper
        // ApplicationContext we need to have it tear down things
        // cleanly.
        Context c = r.activity.getBaseContext();
        if (c instanceof ContextImpl) {
            ((ContextImpl) c).scheduleFinalCleanup(r.activity.getClass().getName(), "Activity");
        }
        if (finishing) {
            ActivityClient.getInstance().activityDestroyed(r.token);
            mNewActivities.remove(r);
        }
        mSomeActivitiesChanged = true;
    }

    @Override
    public ActivityClientRecord prepareRelaunchActivity(@NonNull IBinder token,
            @Nullable List<ResultInfo> pendingResults,
            @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
            @NonNull MergedConfiguration config, boolean preserveWindow,
            @NonNull ActivityWindowInfo activityWindowInfo, int displayId) {
        ActivityClientRecord target = null;
        boolean scheduleRelaunch = false;

        synchronized (mResourcesManager) {
            for (int i=0; i<mRelaunchingActivities.size(); i++) {
                ActivityClientRecord r = mRelaunchingActivities.get(i);
                if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + this + ", trying: " + r);
                if (r.token == token) {
                    target = r;
                    if (pendingResults != null) {
                        if (r.pendingResults != null) {
                            r.pendingResults.addAll(pendingResults);
                        } else {
                            r.pendingResults = pendingResults;
                        }
                    }
                    if (pendingNewIntents != null) {
                        if (r.pendingIntents != null) {
                            r.pendingIntents.addAll(pendingNewIntents);
                        } else {
                            r.pendingIntents = pendingNewIntents;
                        }
                    }
                    break;
                }
            }

            if (target == null) {
                if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null");
                target = new ActivityClientRecord();
                target.token = token;
                target.pendingResults = pendingResults;
                target.pendingIntents = pendingNewIntents;
                target.mPreserveWindow = preserveWindow;
                mRelaunchingActivities.add(target);
                scheduleRelaunch = true;
            }
            target.createdConfig = config.getGlobalConfiguration();
            target.overrideConfig = config.getOverrideConfiguration();
            target.pendingConfigChanges |= configChanges;
            target.mActivityWindowInfo.set(activityWindowInfo);
            target.mDisplayId = displayId;
        }

        return scheduleRelaunch ? target : null;
    }

    @Override
    public void handleRelaunchActivity(@NonNull ActivityClientRecord tmp,
            @NonNull PendingTransactionActions pendingActions) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        int configChanges = 0;

        // First: make sure we have the most recent configuration and most
        // recent version of the activity, or skip it if some previous call
        // had taken a more recent version.
        synchronized (mResourcesManager) {
            int N = mRelaunchingActivities.size();
            IBinder token = tmp.token;
            tmp = null;
            for (int i=0; i<N; i++) {
                ActivityClientRecord r = mRelaunchingActivities.get(i);
                if (r.token == token) {
                    tmp = r;
                    configChanges |= tmp.pendingConfigChanges;
                    mRelaunchingActivities.remove(i);
                    i--;
                    N--;
                }
            }

            if (tmp == null) {
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Abort, activity not relaunching!");
                return;
            }

            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
                    + tmp.token + " with configChanges=0x"
                    + Integer.toHexString(configChanges));
        }

        Configuration changedConfig = mConfigurationController.getPendingConfiguration(
                true /* clearPending */);
        mPendingConfiguration = null;

        if (tmp.createdConfig != null) {
            // If the activity manager is passing us its current config,
            // assume that is really what we want regardless of what we
            // may have pending.
            final Configuration config = mConfigurationController.getConfiguration();
            if (config == null
                    || (tmp.createdConfig.isOtherSeqNewer(config)
                            && config.diff(tmp.createdConfig) != 0)) {
                if (changedConfig == null
                        || tmp.createdConfig.isOtherSeqNewer(changedConfig)) {
                    changedConfig = tmp.createdConfig;
                }
            }
        }

        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
                + tmp.token + ": changedConfig=" + changedConfig);

        // If there was a pending configuration change, execute it first.
        if (changedConfig != null) {
            mConfigurationController.updateDefaultDensity(changedConfig.densityDpi);
            mConfigurationController.handleConfigurationChanged(changedConfig, null);

            // These are only done to maintain @UnsupportedAppUsage and should be removed someday.
            mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
            mConfiguration = mConfigurationController.getConfiguration();
        }

        ActivityClientRecord r = mActivities.get(tmp.token);
        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
        if (r == null) {
            return;
        }

        r.activity.mConfigChangeFlags |= configChanges;
        r.mPreserveWindow = r.activity.mWindowAdded && tmp.mPreserveWindow;

        r.activity.mChangingConfigurations = true;

        handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,
                tmp.mDisplayId, "handleRelaunchActivity");
    }

    void scheduleRelaunchActivity(IBinder token) {
        final ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            Log.i(TAG, "Schedule relaunch activity: " + r.activityInfo.name);
            scheduleRelaunchActivityIfPossible(r, !r.stopped /* preserveWindow */);
        }
    }

    /**
     * Post a message to relaunch the activity. We do this instead of launching it immediately,
     * because this will destroy the activity from which it was called and interfere with the
     * lifecycle changes it was going through before. We need to make sure that we have finished
     * handling current transaction item before relaunching the activity.
     */
    private void scheduleRelaunchActivityIfPossible(@NonNull ActivityClientRecord r,
            boolean preserveWindow) {
        if ((r.activity != null && r.activity.mFinished) || r.token instanceof Binder) {
            // Do not schedule relaunch if the activity is finishing or is a local object (e.g.
            // created by ActivtiyGroup that server side doesn't recognize it).
            return;
        }
        if (preserveWindow && r.window != null) {
            r.mPreserveWindow = true;
        }
        mH.removeMessages(H.RELAUNCH_ACTIVITY, r.token);
        sendMessage(H.RELAUNCH_ACTIVITY, r.token);
    }

    /** Performs the activity relaunch locally vs. requesting from system-server. */
    public void handleRelaunchActivityLocally(IBinder token) {
        final ActivityClientRecord r = mActivities.get(token);
        if (r == null) {
            Log.w(TAG, "Activity to relaunch no longer exists");
            return;
        }

        final int prevState = r.getLifecycleState();

        if (prevState < ON_START || prevState > ON_STOP) {
            Log.w(TAG, "Activity state must be in [ON_START..ON_STOP] in order to be relaunched,"
                    + "current state is " + prevState);
            return;
        }

        ActivityClient.getInstance().activityLocalRelaunch(r.token);
        // Initialize a relaunch request.
        final MergedConfiguration mergedConfiguration = new MergedConfiguration(
                r.createdConfig != null
                        ? r.createdConfig : mConfigurationController.getConfiguration(),
                r.overrideConfig);
        final ActivityRelaunchItem activityRelaunchItem = new ActivityRelaunchItem(
                r.token, null /* pendingResults */, null /* pendingIntents */,
                0 /* configChanges */, mergedConfiguration, r.mPreserveWindow,
                r.getActivityWindowInfo(), r.activity.getDisplayId());
        // Make sure to match the existing lifecycle state in the end of the transaction.
        final ActivityLifecycleItem lifecycleRequest =
                TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
        // Schedule the transaction.
        final ClientTransaction transaction = new ClientTransaction(mAppThread);
        transaction.addTransactionItem(activityRelaunchItem);
        transaction.addTransactionItem(lifecycleRequest);
        executeTransaction(transaction);
    }

    private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r,
            @Nullable List<ResultInfo> pendingResults,
            @Nullable List<ReferrerIntent> pendingIntents,
            @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,
            @NonNull Configuration overrideConfig, @NonNull ActivityWindowInfo activityWindowInfo,
            int displayId, @NonNull String reason) {
        // Preserve last used intent, it may be set from Activity#setIntent().
        final Intent customIntent = r.activity.mIntent;
        // Need to ensure state is saved.
        if (!r.paused) {
            performPauseActivity(r, false, reason, null /* pendingActions */);
        }
        if (!r.stopped) {
            callActivityOnStop(r, true /* saveState */, reason);
        }

        handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason);

        r.activity = null;
        r.window = null;
        r.hideForNow = false;
        // Merge any pending results and pending intents; don't just replace them
        if (pendingResults != null) {
            if (r.pendingResults == null) {
                r.pendingResults = pendingResults;
            } else {
                r.pendingResults.addAll(pendingResults);
            }
        }
        if (pendingIntents != null) {
            if (r.pendingIntents == null) {
                r.pendingIntents = pendingIntents;
            } else {
                r.pendingIntents.addAll(pendingIntents);
            }
        }
        r.startsNotResumed = startsNotResumed;
        r.overrideConfig = overrideConfig;
        r.mActivityWindowInfo.set(activityWindowInfo);
        r.mDisplayId = displayId;

        handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
    }

    @Override
    public void reportRelaunch(ActivityClientRecord r) {
        ActivityClient.getInstance().activityRelaunched(r.token);
    }

    private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
        r.state = new Bundle();
        r.state.setAllowFds(false);

        if (android.companion.Flags.taskContinuity() && r.activity.isHandoffEnabled()) {
            final HandoffActivityDataRequestInfo requestInfo
                    = new HandoffActivityDataRequestInfo(false /* isActiveRequest */);
            r.handoffActivityData = r.activity.onHandoffActivityDataRequested(requestInfo);
        }

        if (r.isPersistable()) {
            r.persistentState = new PersistableBundle();
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
                    r.persistentState);
        } else {
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
        }
    }

    @Override
    public ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts) {
        ArrayList<ComponentCallbacks2> callbacks
                = new ArrayList<ComponentCallbacks2>();

        synchronized (mResourcesManager) {
            final int NAPP = mAllApplications.size();
            for (int i=0; i<NAPP; i++) {
                callbacks.add(mAllApplications.get(i));
            }
            if (includeUiContexts) {
                for (int i = mActivities.size() - 1; i >= 0; i--) {
                    final Activity a = mActivities.valueAt(i).activity;
                    if (a != null && !a.mFinished) {
                        callbacks.add(a);
                    }
                }
            }
            final int NSVC = mServices.size();
            for (int i=0; i<NSVC; i++) {
                final Service service = mServices.valueAt(i);
                // If {@code includeUiContext} is set to false, WindowProviderService should not be
                // collected because WindowProviderService is a UI Context.
                if (includeUiContexts || !(service instanceof WindowProviderService)) {
                    callbacks.add(service);
                }
            }
        }
        synchronized (mProviderMap) {
            final int NPRV = mLocalProviders.size();
            for (int i=0; i<NPRV; i++) {
                callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);
            }
        }

        return callbacks;
    }

    /**
     * Updates the configuration for an Activity. The ActivityClientRecord's
     * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
     * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
     * the updated Configuration.
     * @param r ActivityClientRecord representing the Activity.
     * @param newBaseConfig The new configuration to use. This may be augmented with
     *                      {@link ActivityClientRecord#overrideConfig}.
     * @param displayId The id of the display where the Activity currently resides.
     * @return {@link Configuration} instance sent to client, null if not sent.
     */
    private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
            Configuration newBaseConfig, int displayId, boolean alwaysReportChange) {
        r.tmpConfig.setTo(newBaseConfig);
        if (r.overrideConfig != null) {
            r.tmpConfig.updateFrom(r.overrideConfig);
        }
        final Configuration reportedConfig = performActivityConfigurationChanged(r,
                r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
        freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
        return reportedConfig;
    }

    /**
     * Decides whether to update an Activity's configuration and whether to inform it.
     * @param r The activity client record to notify of configuration change.
     * @param newConfig The new configuration.
     * @param amOverrideConfig The override config that differentiates the Activity's configuration
     *                         from the base global configuration. This is supplied by
     *                         ActivityManager.
     * @param displayId Id of the display where activity currently resides.
     * @return Configuration sent to client, null if no changes and not moved to different display.
     */
    private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
            Configuration newConfig, Configuration amOverrideConfig, int displayId,
            boolean alwaysReportChange) {
        final Activity activity = r.activity;
        final IBinder activityToken = activity.getActivityToken();

        // WindowConfiguration differences aren't considered as public, check it separately.
        // multi-window / pip mode changes, if any, should be sent before the configuration
        // change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
        handleWindowingModeChangeIfNeeded(r, newConfig);

        final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
                displayId);
        final Configuration currentResConfig = activity.getResources().getConfiguration();
        final int diff = currentResConfig.diffPublicOnly(newConfig);
        final boolean hasPublicResConfigChange = diff != 0;
        // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
        // ResourcesImpl constructions.
        final boolean shouldUpdateResources = hasPublicResConfigChange
                || shouldUpdateResources(activityToken, currentResConfig, newConfig,
                amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);

        final boolean skipActivityRelaunchWhenDocking = shouldSkipActivityRelaunchWhenDocking(
                activity);
        int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged();
        if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig,
                newConfig)) {
            // If we're not relaunching this activity when docking, we should send the configuration
            // changed event. Pretend as if the activity is handling uiMode config changes in its
            // manifest so that we'll report any dock changes.
            handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE;
        }

        final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
                r.mSizeConfigurations, handledConfigChanges, alwaysReportChange);
        // Nothing significant, don't proceed with updating and reporting.
        if (!shouldUpdateResources && !shouldReportChange) {
            return null;
        }

        // Propagate the configuration change to ResourcesManager and Activity.

        // ContextThemeWrappers may override the configuration for that context. We must check and
        // apply any overrides defined.
        Configuration contextThemeWrapperOverrideConfig = activity.getOverrideConfiguration();

        // We only update an Activity's configuration if this is not a global configuration change.
        // This must also be done before the callback, or else we violate the contract that the new
        // resources are available in ComponentCallbacks2#onConfigurationChanged(Configuration).
        // Also apply the ContextThemeWrapper override if necessary.
        // NOTE: Make sure the configurations are not modified, as they are treated as immutable in
        // many places.
        final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
                amOverrideConfig, contextThemeWrapperOverrideConfig);
        mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);

        // Apply the ContextThemeWrapper override if necessary.
        // NOTE: Make sure the configurations are not modified, as they are treated as immutable
        // in many places.
        final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
                contextThemeWrapperOverrideConfig);

        if (movedToDifferentDisplay) {
            activity.dispatchMovedToDisplay(displayId, configToReport);
        }

        activity.mConfigChangeFlags = 0;
        if (shouldReportChange) {
            activity.mCalled = false;
            activity.mCurrentConfig = new Configuration(newConfig);
            activity.onConfigurationChanged(configToReport);
            if (!activity.mCalled) {
                throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
                                " did not call through to super.onConfigurationChanged()");
            }
        }
        mConfigurationChangedListenerController
                .dispatchOnConfigurationChanged(activity.getActivityToken());

        return configToReport;
    }

    /**
     * Returns true if the uiMode configuration changed, and desk mode
     * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
     */
    private boolean onlyDeskInUiModeChanged(Configuration oldConfig, Configuration newConfig) {
        boolean deskModeChanged = isInDeskUiMode(oldConfig) != isInDeskUiMode(newConfig);

        // UI mode contains fields other than the UI mode type, so determine if any other fields
        // changed.
        boolean uiModeOtherFieldsChanged =
                (oldConfig.uiMode & ~UI_MODE_TYPE_MASK) != (newConfig.uiMode & ~UI_MODE_TYPE_MASK);

        return deskModeChanged && !uiModeOtherFieldsChanged;
    }

    private static boolean isInDeskUiMode(Configuration config) {
        return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
    }

    /**
     * Returns true if we should skip the activity recreation when the device is docked or undocked
     * and the application does not have desk mode resources.
     */
    private boolean shouldSkipActivityRelaunchWhenDocking(@NonNull Activity activity) {
        return (com.android.window.flags.Flags.enableLessActivityRecreationOnConfigChange()
                && CompatChanges.isChangeEnabled(
                        ActivityInfo.SKIP_ACTIVITY_RECREATION_ON_CONFIG_CHANGE))
                || activity.getResources().getBoolean(
                        R.bool.config_skipActivityRelaunchWhenDocking);
    }

    /**
     * Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
     * dispatched.
     *
     * @param currentConfig The current configuration cached in {@link Activity#mCurrentConfig}.
     *                      It is {@code null} before the first config update from the server side.
     * @param newConfig The updated {@link Configuration}
     * @param sizeBuckets The Activity's {@link SizeConfigurationBuckets} if not {@code null}
     * @param handledConfigChanges Bit mask of configuration changes that the activity can handle
     * @return {@code true} if the config change should be reported to the Activity
     */
    @VisibleForTesting
    public static boolean shouldReportChange(@Nullable Configuration currentConfig,
            @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
            int handledConfigChanges, boolean alwaysReportChange) {
        final int publicDiff = currentConfig.diffPublicOnly(newConfig);
        // Don't report the change if there's no public diff between current and new config.
        if (publicDiff == 0) {
            return false;
        }

        // Report the change regardless if the changes across size-config-buckets.
        if (alwaysReportChange) {
            return true;
        }

        if ((handledConfigChanges & CONFIG_RESOURCES_UNUSED) != 0) {
            // Report the change if activities claim they do not use resources at all.
            return true;
        }

        final int diffWithBucket = SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig,
                newConfig, sizeBuckets);
        // Compare to the diff which filter the change without crossing size buckets with
        // {@code handledConfigChanges}. The small changes should not block Activity to receive
        // its handled config updates. Also, if Activity handles all small changes, we should
        // dispatch the updated config to it.
        final int diff = diffWithBucket != 0 ? diffWithBucket : publicDiff;
        // If this activity doesn't handle any of the config changes, then don't bother
        // calling onConfigurationChanged. Otherwise, report to the activity for the
        // changes.
        return (~handledConfigChanges & diff) == 0;
    }

    public final void applyConfigurationToResources(Configuration config) {
        synchronized (mResourcesManager) {
            mResourcesManager.applyConfigurationToResources(config, null);
        }
    }

    /**
     * Updates the deviceId for the non UI Contexts (Application, Service, ContentProvider)
     * belonging to the same process
     * @param deviceId The new value to be set for the deviceId of the non UI Contexts
     */
    @VisibleForTesting
    public void updateDeviceIdForNonUIContexts(int deviceId) {
        // Invalid device id is treated as a no-op.
        if (deviceId == Context.DEVICE_ID_INVALID) {
            return;
        }
        if (deviceId == mLastReportedDeviceId) {
            return;
        }
        mLastReportedDeviceId = deviceId;
        final Set<Context> nonUIContexts = new ArraySet<>();
        // Update non UI contexts with implicit device association.
        // UI Contexts are able to derived their device Id association from the display.
        synchronized (mResourcesManager) {
            final int numApps = mAllApplications.size();
            for (int i = 0; i < numApps; i++) {
                nonUIContexts.add(mAllApplications.get(i));
            }
            final int numServices = mServices.size();
            for (int i = 0; i < numServices; i++) {
                final Service service = mServices.valueAt(i);
                // WindowProviderService is a UI Context.
                if (!service.isUiContext()) {
                    nonUIContexts.add(service);
                }
            }
        }
        synchronized (mProviderMap) {
            final int numContentProviders = mLocalProviders.size();
            for (int i = 0; i < numContentProviders; i++) {
                final Context providerContext = mLocalProviders.valueAt(i).mLocalProvider
                        .getContext();
                if (providerContext != null) {
                    nonUIContexts.add(providerContext);
                }
            }
        }

        for (Context context : nonUIContexts) {
            try {
                context.updateDeviceId(deviceId);
            } catch (IllegalArgumentException e) {
                // It can happen that the system already closed/removed a virtual device
                // and the passed deviceId is no longer valid.
                // TODO(b/263355088): check for validity of deviceId before updating
                // instead of catching this exception once VDM add an API to validate ids.
                Slog.e(TAG, "Exception updating context with deviceId: " + deviceId, e);
            }
        }
    }

    @Override
    public void handleConfigurationChanged(Configuration config, int deviceId) {
        mConfigurationController.handleConfigurationChanged(config);
        updateDeviceIdForNonUIContexts(deviceId);

        // These are only done to maintain @UnsupportedAppUsage and should be removed someday.
        mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
        mConfiguration = mConfigurationController.getConfiguration();
        mPendingConfiguration = mConfigurationController.getPendingConfiguration(
                false /* clearPending */);
    }

    @Override
    public void handleWindowContextInfoChanged(@NonNull IBinder clientToken,
            @NonNull WindowContextInfo info) {
        WindowTokenClientController.getInstance().onWindowContextInfoChanged(clientToken, info);
    }

    @Override
    public void handleWindowContextWindowRemoval(@NonNull IBinder clientToken) {
        WindowTokenClientController.getInstance().onWindowContextWindowRemoved(clientToken);
    }

    /**
     * Sends windowing mode change callbacks to {@link Activity} if applicable.
     *
     * See also {@link Activity#onMultiWindowModeChanged(boolean, Configuration)} and
     * {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)}
     */
    private void handleWindowingModeChangeIfNeeded(ActivityClientRecord r,
            Configuration newConfiguration) {
        final Activity activity = r.activity;
        final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode();
        final int oldWindowingMode = r.mLastReportedWindowingMode;
        if (oldWindowingMode == newWindowingMode) return;
        // PiP callback is sent before the MW one.
        if (newWindowingMode == WINDOWING_MODE_PINNED) {
            activity.dispatchPictureInPictureModeChanged(true, newConfiguration);
        } else if (oldWindowingMode == WINDOWING_MODE_PINNED) {
            activity.dispatchPictureInPictureModeChanged(false, newConfiguration);
        }
        final boolean wasInMultiWindowMode = WindowConfiguration.inMultiWindowMode(
                oldWindowingMode);
        final boolean nowInMultiWindowMode = WindowConfiguration.inMultiWindowMode(
                newWindowingMode);
        if (wasInMultiWindowMode != nowInMultiWindowMode) {
            activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);
        }
        r.mLastReportedWindowingMode = newWindowingMode;
    }

    private void applyPendingApplicationInfoChanges(String packageName) {
        final ApplicationInfo ai;
        synchronized (mResourcesManager) {
            ai = mPendingAppInfoUpdates.remove(packageName);
        }
        if (ai == null) {
            return;
        }
        handleApplicationInfoChanged(ai);
    }

    /**
     * Updates the application info.
     *
     * This only works in the system process. Must be called on the main thread.
     */
    public void handleSystemApplicationInfoChanged(@NonNull ApplicationInfo ai) {
        Preconditions.checkState(mSystemThread, "Must only be called in the system process");
        handleApplicationInfoChanged(ai);
    }

    @VisibleForTesting(visibility = PACKAGE)
    public void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
        // Updates triggered by package installation go through a package update
        // receiver. Here we try to capture ApplicationInfo changes that are
        // caused by other sources, such as overlays. That means we want to be as conservative
        // about code changes as possible. Take the diff of the old ApplicationInfo and the new
        // to see if anything needs to change.
        LoadedApk apk;
        LoadedApk resApk;
        // Update all affected loaded packages with new package information
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
            apk = ref != null ? ref.get() : null;
            ref = mResourcePackages.get(ai.packageName);
            resApk = ref != null ? ref.get() : null;
            for (ActivityClientRecord ar : mActivities.values()) {
                if (ar.activityInfo.applicationInfo.packageName.equals(ai.packageName)) {
                    ar.activityInfo.applicationInfo = ai;
                    if (apk != null || resApk != null) {
                        ar.packageInfo = apk != null ? apk : resApk;
                    } else {
                        apk = ar.packageInfo;
                    }
                }
            }
        }

        if (apk != null) {
            final ArrayList<String> oldPaths = new ArrayList<>();
            LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
            apk.updateApplicationInfo(ai, oldPaths);
        }
        if (resApk != null) {
            final ArrayList<String> oldPaths = new ArrayList<>();
            LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
            resApk.updateApplicationInfo(ai, oldPaths);
        }
        if (android.content.res.Flags.systemContextHandleAppInfoChanged() && mSystemThread) {
            final var systemContext = getSystemContext();
            if (systemContext.getPackageName().equals(ai.packageName)) {
                // The system package is not tracked directly, but still needs to receive updates to
                // its application info.
                final ArrayList<String> oldPaths = new ArrayList<>();
                LoadedApk.makePaths(this, systemContext.getApplicationInfo(), oldPaths);
                systemContext.mPackageInfo.updateApplicationInfo(ai, oldPaths);
            }
        }

        ResourcesImpl beforeImpl = getApplication().getResources().getImpl();

        synchronized (mResourcesManager) {
            // Update all affected Resources objects to use new ResourcesImpl
            mResourcesManager.applyAllPendingAppInfoUpdates();
        }

        ResourcesImpl afterImpl = getApplication().getResources().getImpl();

        if ((beforeImpl != afterImpl) && !Arrays.equals(beforeImpl.getAssets().getApkAssets(),
                afterImpl.getAssets().getApkAssets())) {
            List<String> beforeAssets = Arrays.asList(beforeImpl.getAssets().getApkPaths());
            List<String> afterAssets = Arrays.asList(afterImpl.getAssets().getApkPaths());

            List<String> onlyBefore = new ArrayList<>(beforeAssets);
            onlyBefore.removeAll(afterAssets);
            List<String> onlyAfter = new ArrayList<>(afterAssets);
            onlyAfter.removeAll(beforeAssets);

            Slog.i(TAG, "ApplicationInfo updating for " + ai.packageName + ", new timestamp: "
                    + ai.createTimestamp + "\nassets removed: " + onlyBefore + "\nassets added: "
                    + onlyAfter);

            if (DEBUG_APP_INFO) {
                Slog.v(TAG, "ApplicationInfo updating for " + ai.packageName
                        + ", assets before change: " + beforeAssets + "\n assets after change: "
                        + afterAssets);
            }
        }
    }

    /**
     * Sets the supplied {@code overrideConfig} as pending for the {@code token}. Calling
     * this method prevents any calls to
     * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int,
     * ActivityWindowInfo)} from processing any configurations older than {@code overrideConfig}.
     */
    @Override
    public void updatePendingActivityConfiguration(@NonNull IBinder token,
            @NonNull Configuration overrideConfig) {
        if (com.android.window.flags.Flags.improveFluidResizingPerformance()) {
            throw new UnsupportedOperationException();
        }
        synchronized (mPendingOverrideConfigs) {
            final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(token);
            if (pendingOverrideConfig != null
                    && !pendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
                if (DEBUG_CONFIGURATION) {
                    Slog.v(TAG, "Activity has newer configuration pending so this transaction will"
                            + " be dropped. overrideConfig=" + overrideConfig
                            + " pendingOverrideConfig=" + pendingOverrideConfig);
                }
                return;
            }
            mPendingOverrideConfigs.put(token, overrideConfig);
        }
    }

    /**
     * Sets the supplied {@code overrideConfig} as pending for the {@code token}. Calling
     * this method prevents any calls to
     * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int,
     * ActivityWindowInfo)} from processing any configurations older than {@code overrideConfig}.
     */
    @Override
    public void updatePendingActivityConfiguration(@NonNull IBinder token,
            @NonNull Configuration overrideConfig, @NonNull ActivityWindowInfo info,
            int displayId) {
        synchronized (mPendingConfigChanges) {
            final ConfigChange pendingChange = mPendingConfigChanges.get(token);
            if (pendingChange != null
                    && !pendingChange.mConfiguration.isOtherSeqNewer(overrideConfig)) {
                if (DEBUG_CONFIGURATION) {
                    Slog.v(TAG,
                            "Activity has newer configuration pending so this transaction will"
                                    + " be dropped. overrideConfig=" + overrideConfig
                                    + " pendingOverrideConfig=" + pendingChange.mConfiguration);
                }
                return;
            }
            mPendingConfigChanges.put(token, new ConfigChange(overrideConfig, info, displayId));
        }
    }

    @Override
    public void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
            @NonNull Configuration overrideConfig, int displayId,
            @NonNull ActivityWindowInfo activityWindowInfo) {
        if (com.android.window.flags.Flags.improveFluidResizingPerformance()) {
            throw new UnsupportedOperationException();
        }
        handleActivityConfigurationChanged(r, overrideConfig, displayId, activityWindowInfo,
                // This is the only place that uses alwaysReportChange=true. The entry point should
                // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
                // has confirmed the activity should handle the configuration instead of relaunch.
                // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
                // something wrong from server side.
                true /* alwaysReportChange */);
    }

    @Override
    public void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r) {
        final ConfigChange change;
        synchronized (mPendingConfigChanges) {
            change = mPendingConfigChanges.remove(r.token);
        }
        if (change == null) {
            return;
        }
        handleActivityConfigurationChanged(
                r, change.mConfiguration, change.mDisplayId, change.mActivityWindowInfo,
                // This is the only place that uses alwaysReportChange=true. The entry point should
                // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
                // has confirmed the activity should handle the configuration instead of relaunch.
                // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
                // something wrong from server side.
                true /* alwaysReportChange */);
    }

    /**
     * Handle new activity configuration and/or move to a different display. This method is a noop
     * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been
     * called with a newer config than {@code overrideConfig}.
     *
     * @param r Target activity record.
     * @param overrideConfig Activity override config.
     * @param displayId Id of the display where activity was moved to, -1 if there was no move and
     *                  value didn't change.
     * @param activityWindowInfo the window info of the given activity.
     */
    void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
            @NonNull Configuration overrideConfig, int displayId,
            @NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) {
        final ClientTransactionListenerController controller =
                ClientTransactionListenerController.getInstance();
        final Context contextToUpdate = r.activity;
        controller.onContextConfigurationPreChanged(contextToUpdate);
        try {
            handleActivityConfigurationChangedInner(r, overrideConfig, displayId,
                    activityWindowInfo, alwaysReportChange);
        } finally {
            controller.onContextConfigurationPostChanged(contextToUpdate);
        }
    }

    private void handleActivityConfigurationChangedInner(@NonNull ActivityClientRecord r,
            @NonNull Configuration overrideConfig, int displayId,
            @NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) {
        if (!com.android.window.flags.Flags.improveFluidResizingPerformance()) {
            synchronized (mPendingOverrideConfigs) {
                final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);
                if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {
                    if (DEBUG_CONFIGURATION) {
                        Slog.v(TAG, "Activity has newer configuration pending so drop this"
                                + " transaction. overrideConfig=" + overrideConfig
                                + " pendingOverrideConfig=" + pendingOverrideConfig);
                    }
                    return;
                }
                mPendingOverrideConfigs.remove(r.token);
            }
        }

        if (displayId == INVALID_DISPLAY) {
            // If INVALID_DISPLAY is passed assume that the activity should keep its current
            // display.
            displayId = r.activity.getDisplayId();
        }
        final boolean movedToDifferentDisplay = isDifferentDisplay(
                r.activity.getDisplayId(), displayId);
        if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
                && !movedToDifferentDisplay) {
            if (DEBUG_CONFIGURATION) {
                Slog.v(TAG, "Activity already handled newer configuration so drop this"
                        + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig="
                        + r.overrideConfig);
            }
            return;
        }

        // Perform updates.
        r.overrideConfig = overrideConfig;
        r.mActivityWindowInfo.set(activityWindowInfo);
        r.mDisplayId = displayId;

        final ViewRootImpl viewRoot = r.activity.mDecor != null
            ? r.activity.mDecor.getViewRootImpl() : null;

        if (DEBUG_CONFIGURATION) {
            Slog.v(TAG, "Handle activity config changed, activity:"
                    + r.activityInfo.name + ", displayId=" + r.activity.getDisplayId()
                    + (movedToDifferentDisplay ? (", newDisplayId=" + displayId) : "")
                    + ", config=" + overrideConfig);
        }
        final Configuration reportedConfig = performConfigurationChangedForActivity(r,
                mConfigurationController.getCompatConfiguration(),
                movedToDifferentDisplay ? displayId : r.activity.getDisplayId(),
                alwaysReportChange);
        // Notify the ViewRootImpl instance about configuration changes. It may have initiated this
        // update to make sure that resources are updated before updating itself.
        if (viewRoot != null) {
            if (movedToDifferentDisplay) {
                viewRoot.onMovedToDisplay(displayId, reportedConfig);
            }
            viewRoot.updateConfiguration(displayId);
        }
        mSomeActivitiesChanged = true;

        // Trigger ActivityWindowInfo callback if changed.
        handleActivityWindowInfoChanged(r);
    }

    private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
        if (r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
            return;
        }
        r.mLastReportedActivityWindowInfo.set(r.mActivityWindowInfo);
        ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
                r.mActivityWindowInfo);
    }

    void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
        if (start) {
            switch (profileType) {
                case ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD:
                    if (!com.android.art.flags.Flags.alwaysEnableProfileCode()) {
                        Slog.w(TAG, "Low overhead tracing feature is not enabled");
                        break;
                    }

                    if (profilerInfo.profileLongRunningMethods) {
                        long microToNano = 1000;
                        VMDebug.startLowOverheadTraceForLongRunningMethods(
                                profilerInfo.durationMicros * microToNano);
                    } else {
                        VMDebug.startLowOverheadTraceForAllMethods();
                    }
                    break;
                default:
                    try {
                        mProfiler.setProfiler(profilerInfo);
                        mProfiler.startProfiling();
                        break;
                    } catch (RuntimeException e) {
                        Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
                                + " -- can the process access this path?");
                    } finally {
                        profilerInfo.closeFd();
                    }
            }
        } else {
            switch (profileType) {
                case ProfilerInfo.PROFILE_TYPE_LOW_OVERHEAD:
                    if (!com.android.art.flags.Flags.alwaysEnableProfileCode()) {
                        if (profilerInfo != null) {
                            profilerInfo.closeFd();
                        }
                        Slog.w(TAG, "Low overhead tracing feature is not enabled");
                        break;
                    }
                    if (profilerInfo != null) {
                        FileDescriptor fd = profilerInfo.profileFd.getFileDescriptor();
                        VMDebug.TraceDestination dst =
                                VMDebug.TraceDestination.fromFileDescriptor(fd);
                        VMDebug.dumpLowOverheadTrace(dst);
                    }
                    VMDebug.stopLowOverheadTrace();
                    break;
                default:
                    mProfiler.stopProfiling();
                    break;
            }
        }
    }

    /**
     * Public entrypoint to stop profiling. This is required to end profiling when the app crashes,
     * so that profiler data won't be lost.
     *
     * @hide
     */
    public void stopProfiling() {
        if (mProfiler != null) {
            mProfiler.stopProfiling();
        }
    }

    static void handleDumpHeap(DumpHeapData dhd) {
        if (dhd.runGc) {
            System.gc();
            System.runFinalization();
            System.gc();
        }
        try (ParcelFileDescriptor fd = dhd.fd) {
            if (dhd.managed) {
                Debug.dumpHprofData(dhd.path, fd.getFileDescriptor(), dhd.dumpBitmaps);
            } else if (dhd.mallocInfo) {
                Debug.dumpNativeMallocInfo(fd.getFileDescriptor());
            } else {
                Debug.dumpNativeHeap(fd.getFileDescriptor());
            }
        } catch (IOException e) {
            if (dhd.managed) {
                Slog.w(TAG, "Managed heap dump failed on path " + dhd.path
                        + " -- can the process access this path?", e);
            } else {
                Slog.w(TAG, "Failed to dump heap", e);
            }
        } catch (RuntimeException e) {
            // This should no longer happening now that we're copying the file descriptor.
            Slog.wtf(TAG, "Heap dumper threw a runtime exception", e);
        }
        try {
            ActivityManager.getService().dumpHeapFinished(dhd.path);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        if (dhd.finishCallback != null) {
            dhd.finishCallback.sendResult(null);
        }
    }

    void handleDispatchPackageBroadcast(int cmd, String[] packages) {
        boolean hasPkgInfo = false;
        switch (cmd) {
            case ApplicationThreadConstants.PACKAGE_REMOVED:
            case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL:
            {
                final boolean killApp = cmd == ApplicationThreadConstants.PACKAGE_REMOVED;
                if (packages == null) {
                    break;
                }
                synchronized (mResourcesManager) {
                    for (int i = packages.length - 1; i >= 0; i--) {
                        if (!hasPkgInfo) {
                            WeakReference<LoadedApk> ref = mPackages.get(packages[i]);
                            if (ref != null && ref.get() != null) {
                                hasPkgInfo = true;
                            } else {
                                ref = mResourcePackages.get(packages[i]);
                                if (ref != null && ref.get() != null) {
                                    hasPkgInfo = true;
                                }
                            }
                        }
                        if (killApp) {
                            // Keep in sync with "perhaps it was removed" case below.
                            mPackages.remove(packages[i]);
                            mResourcePackages.remove(packages[i]);
                        }
                    }
                }
                break;
            }
            case ApplicationThreadConstants.PACKAGE_REPLACED:
            {
                if (packages == null) {
                    break;
                }

                List<String> packagesHandled = new ArrayList<>();

                synchronized (mResourcesManager) {
                    for (int i = packages.length - 1; i >= 0; i--) {
                        String packageName = packages[i];
                        WeakReference<LoadedApk> ref = mPackages.get(packageName);
                        LoadedApk pkgInfo = ref != null ? ref.get() : null;
                        if (pkgInfo != null) {
                            hasPkgInfo = true;
                        } else {
                            ref = mResourcePackages.get(packageName);
                            pkgInfo = ref != null ? ref.get() : null;
                            if (pkgInfo != null) {
                                hasPkgInfo = true;
                            }
                        }
                        // If the package is being replaced, yet it still has a valid
                        // LoadedApk object, the package was updated with _DONT_KILL.
                        // Adjust it's internal references to the application info and
                        // resources.
                        if (pkgInfo != null) {
                            packagesHandled.add(packageName);
                            try {
                                final ApplicationInfo aInfo =
                                        sPackageManager.getApplicationInfo(
                                                packageName,
                                                PackageManager.GET_SHARED_LIBRARY_FILES,
                                                UserHandle.myUserId());

                                if (aInfo != null) {
                                    if (mActivities.size() > 0) {
                                        for (ActivityClientRecord ar : mActivities.values()) {
                                            if (ar.activityInfo.applicationInfo.packageName
                                                    .equals(packageName)) {
                                                ar.activityInfo.applicationInfo = aInfo;
                                                ar.packageInfo = pkgInfo;
                                            }
                                        }
                                    }

                                    final String[] oldResDirs = {pkgInfo.getResDir()};

                                    final ArrayList<String> oldPaths = new ArrayList<>();
                                    LoadedApk.makePaths(
                                            this, pkgInfo.getApplicationInfo(), oldPaths);
                                    pkgInfo.updateApplicationInfo(aInfo, oldPaths);

                                    // Update affected Resources objects to use new ResourcesImpl
                                    mResourcesManager.appendPendingAppInfoUpdate(oldResDirs,
                                            aInfo);
                                    mResourcesManager.applyAllPendingAppInfoUpdates();
                                }
                            } catch (RemoteException e) {
                            }
                        } else {
                            // No package, perhaps it was removed?
                            Slog.d(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
                                    + " but missing application info. Assuming REMOVED.");
                            mPackages.remove(packages[i]);
                            mResourcePackages.remove(packages[i]);
                        }
                    }
                }

                try {
                    getPackageManager().notifyPackagesReplacedReceived(
                            packagesHandled.toArray(new String[0]));
                } catch (RemoteException ignored) {
                }

                break;
            }
        }
        ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo);
    }

    void handleLowMemory() {
        final ArrayList<ComponentCallbacks2> callbacks =
                collectComponentCallbacks(true /* includeUiContexts */);

        final int N = callbacks.size();
        for (int i=0; i<N; i++) {
            callbacks.get(i).onLowMemory();
        }

        // Ask SQLite to free up as much memory as it can, mostly from its page caches.
        if (Process.myUid() != Process.SYSTEM_UID) {
            int sqliteReleased = SQLiteDatabase.releaseMemory();
            EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
        }

        // Ask graphics to free up as much as possible (font/image caches)
        Canvas.freeCaches();

        // Ask text layout engine to free also as much as possible
        Canvas.freeTextLayoutCaches();

        BinderInternal.forceGc("mem");
    }

    private void handleTrimMemory(int level) {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory: " + level);
        }
        if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);

        try {
            if (skipBgMemTrimOnFgApp()
                    && mLastProcessState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
                    && level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
                return;
            }

            final ArrayList<ComponentCallbacks2> callbacks =
                    collectComponentCallbacks(true /* includeUiContexts */);

            final int N = callbacks.size();
            for (int i = 0; i < N; i++) {
                callbacks.get(i).onTrimMemory(level);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }

        WindowManagerGlobal.getInstance().trimMemory(level);
    }

    private void setupGraphicsSupport(Context context) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupGraphicsSupport");

        // The system package doesn't have real data directories, so don't set up cache paths.
        if (!"android".equals(context.getPackageName())) {
            // This cache location probably points at credential-encrypted
            // storage which may not be accessible yet; assign it anyway instead
            // of pointing at device-encrypted storage.
            final File cacheDir = context.getCacheDir();
            if (cacheDir != null) {
                // Provide a usable directory for temporary files
                String tmpdir = cacheDir.getAbsolutePath();
                System.setProperty("java.io.tmpdir", tmpdir);
                try {
                    android.system.Os.setenv("TMPDIR", tmpdir, true);
                } catch (ErrnoException ex) {
                    Log.w(TAG, "Unable to initialize $TMPDIR", ex);
                }
            } else {
                Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property "
                        + "due to missing cache directory");
            }

            // Setup a location to store generated/compiled graphics code.
            final Context deviceContext = context.createDeviceProtectedStorageContext();
            final File codeCacheDir = deviceContext.getCodeCacheDir();
            final File deviceCacheDir = deviceContext.getCacheDir();
            if (codeCacheDir != null && deviceCacheDir != null) {
                try {
                    int uid = Process.myUid();
                    String[] packages = getPackageManager().getPackagesForUid(uid);
                    if (packages != null) {
                        HardwareRenderer.setupDiskCache(deviceCacheDir);
                        RenderScriptCacheDir.setupDiskCache(codeCacheDir);
                    }
                } catch (RemoteException e) {
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    throw e.rethrowFromSystemServer();
                }
            } else {
                Log.w(TAG, "Unable to use shader/script cache: missing code-cache directory");
            }
        }

        // mCoreSettings is only updated from the main thread, while this function is only called
        // from main thread as well, so no need to lock here.
        GraphicsEnvironment.getInstance().setup(context, getCoreSettingsForDefaultDeviceLocked());
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

    /**
     * Returns the correct library directory for the current ABI.
     * <p>
     * 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.
     */
    private String getInstrumentationLibrary(ApplicationInfo appInfo, InstrumentationInfo insInfo) {
        if (appInfo.primaryCpuAbi != null && appInfo.secondaryCpuAbi != null
                && appInfo.secondaryCpuAbi.equals(insInfo.secondaryCpuAbi)) {
            // Get the instruction set supported by the secondary ABI. In the presence
            // of a native bridge this might be different than the one secondary ABI used.
            String secondaryIsa =
                    VMRuntime.getInstructionSet(appInfo.secondaryCpuAbi);
            final String secondaryDexCodeIsa =
                    SystemProperties.get("ro.dalvik.vm.isa." + secondaryIsa);
            secondaryIsa = secondaryDexCodeIsa.isEmpty() ? secondaryIsa : secondaryDexCodeIsa;

            final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();
            if (runtimeIsa.equals(secondaryIsa)) {
                return insInfo.secondaryNativeLibraryDir;
            }
        }
        return insInfo.nativeLibraryDir;
    }

    @UnsupportedAppUsage
    @RavenwoodThrow(comment = "See ActivityThread_ravenwood for initialization on Ravenwood")
    private void handleBindApplication(AppBindData data) {
        mDdmSyncStageUpdater.next(Stage.Bind);

        // Register the UI Thread as a sensitive thread to the runtime.
        VMRuntime.registerSensitiveThread();
        // In the case the stack depth property exists, pass it down to the runtime.
        String property = SystemProperties.get("debug.allocTracker.stackDepth");
        if (property.length() != 0) {
            VMDebug.setAllocTrackerStackDepth(Integer.parseInt(property));
        }
        if (data.trackAllocation) {
            DdmVmInternal.setRecentAllocationsTrackingEnabled(true);
        }
        // Note when this process has started.
        Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
                data.startRequestedElapsedTime, data.startRequestedUptime);

        AppCompatCallbacks.install(
                data.disabledCompatChanges,
                data.enabledCompatChanges,
                data.mLoggableCompatChanges,
                data.mLogChangeChecksToStatsD,
                data.appInfo.targetSdkVersion);
        // Let libcore handle any compat changes after installing the list of compat changes.
        AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();

        // Initialize the zip path validator callback depending on the targetSdk.
        // This has to be after AppCompatCallbacks#install() so that the Compat
        // checks work accordingly.
        initZipPathValidatorCallback();

        mBoundApplication = data;
        mConfigurationController.setConfiguration(data.config);
        mConfigurationController.setCompatConfiguration(data.config);
        mConfiguration = mConfigurationController.getConfiguration();
        mCompatibilityInfo = data.compatInfo;

        mProfiler = new Profiler();
        String agent = null;
        if (data.initProfilerInfo != null) {
            mProfiler.profileFile = data.initProfilerInfo.profileFile;
            mProfiler.profileFd = data.initProfilerInfo.profileFd;
            mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
            mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
            mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
            mProfiler.mProfilerFlags = data.initProfilerInfo.profilerFlags;
            if (data.initProfilerInfo.attachAgentDuringBind) {
                agent = data.initProfilerInfo.agent;
            }
        }

        VMDebug.setUserId(UserHandle.myUserId());
        VMDebug.addApplication(data.appInfo.packageName);
        // send up app name; do this *before* waiting for debugger
        Process.setArgV0(data.processName);
        android.ddm.DdmHandleAppName.setAppName(data.processName,
                                                data.appInfo.packageName,
                                                UserHandle.myUserId());
        VMRuntime.setProcessPackageName(data.appInfo.packageName);
        mDdmSyncStageUpdater.next(Stage.Named);

        // Pass data directory path to ART. This is used for caching information and
        // should be set before any application code is loaded.
        VMRuntime.setProcessDataDirectory(data.appInfo.dataDir);

        if (mProfiler.profileFd != null) {
            mProfiler.startProfiling();
        }

        // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
        // implementation to use the pool executor.  Normally, we use the
        // serialized executor as the default. This has to happen in the
        // main thread so the main looper is set right.
        if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
            AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }

        // Let the util.*Array classes maintain "undefined" for apps targeting Pie or earlier.
        UtilConfig.setThrowExceptionForUpperArrayOutOfBounds(
                data.appInfo.targetSdkVersion >= Build.VERSION_CODES.Q);

        // Supply the targetSdkVersion to the UI rendering module, which may
        // need it in cases where it does not have access to the appInfo.
        android.graphics.Compatibility.setTargetSdkVersion(data.appInfo.targetSdkVersion);

        /*
         * Before spawning a new process, reset the time zone to be the system time zone.
         * This needs to be done because the system time zone could have changed after the
         * the spawning of this process. Without doing this this process would have the incorrect
         * system time zone.
         */
        TimeZone.setDefault(null);

        /*
         * Set the LocaleList. This may change once we create the App Context.
         */
        LocaleList.setDefault(data.config.getLocales());

        if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
            try {
                Typeface.setSystemFontMap(data.mSerializedSystemFontMap);
            } catch (IOException | ErrnoException e) {
                Slog.e(TAG, "Failed to parse serialized system font map");
                Typeface.loadPreinstalledSystemFontMap();
            }
        }

        synchronized (mResourcesManager) {
            /*
             * Update the system configuration since its preloaded and might not
             * reflect configuration changes. The configuration object passed
             * in AppBindData can be safely assumed to be up to date
             */
            mResourcesManager.applyConfigurationToResources(data.config, data.compatInfo);
            mCurDefaultDisplayDpi = data.config.densityDpi;

            // This calls mResourcesManager so keep it within the synchronized block.
            mConfigurationController.applyCompatConfiguration();
        }

        final boolean isSdkSandbox = data.sdkSandboxClientAppPackage != null;
        data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
                false /* securityViolation */, true /* includeCode */,
                false /* registerPackage */, isSdkSandbox);
        if (isSdkSandbox) {
            data.info.setSdkSandboxStorage(data.sdkSandboxClientAppVolumeUuid,
                    data.sdkSandboxClientAppPackage);
        }

        if (agent != null) {
            handleAttachAgent(agent, data.info);
        }

        /*
         * Switch this process to density compatibility mode if needed.
         */
        if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
                == 0) {
            mDensityCompatMode = true;
            Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
        }
        mConfigurationController.updateDefaultDensity(data.config.densityDpi);

        // mCoreSettings is only updated from the main thread, while this function is only called
        // from main thread as well, so no need to lock here.
        final String use24HourSetting = getCoreSettingsForDefaultDeviceLocked().getString(
                Settings.System.TIME_12_24);
        Boolean is24Hr = null;
        if (use24HourSetting != null) {
            is24Hr = "24".equals(use24HourSetting) ? Boolean.TRUE : Boolean.FALSE;
        }
        // null : use locale default for 12/24 hour formatting,
        // false : use 12 hour format,
        // true : use 24 hour format.
        DateFormat.set24HourTimePref(is24Hr);

        updateDebugViewAttributeState();

        StrictMode.initThreadDefaults(data.appInfo);
        StrictMode.initVmDefaults(data.appInfo);

        // Allow binder tracing, and application-generated systrace messages if we're profileable.
        boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
        boolean isAppProfileable = isAppDebuggable || data.appInfo.isProfileable();
        Trace.setAppTracingAllowed(isAppProfileable);
        if ((isAppProfileable || Build.IS_DEBUGGABLE) && data.enableBinderTracking) {
            Binder.enableStackTracking();
        }

        // Initialize heap profiling.
        if (isAppProfileable || Build.IS_DEBUGGABLE) {
            nInitZygoteChildHeapProfiling();
        }

        // Allow renderer debugging features if we're debuggable.
        HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
        HardwareRenderer.setPackageName(data.appInfo.packageName);

        // Pass the current context to HardwareRenderer
        HardwareRenderer.setContextForInit(getSystemContext());
        if (data.persistent) {
            HardwareRenderer.setIsSystemOrPersistent();
        }

        // Instrumentation info affects the class loader, so load it before
        // setting up the app context.
        final InstrumentationInfo ii;
        if (data.instrumentationName != null) {
            ii = prepareInstrumentation(data);
        } else {
            ii = null;
        }

        final IActivityManager mgr = ActivityManager.getService();
        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
        mConfigurationController.updateLocaleListFromAppContext(appContext);

        // Initialize the default http proxy in this process.
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
        try {
            // In pre-boot mode (doing initial launch to collect password), not all system is up.
            // This includes the connectivity service, so trying to obtain ConnectivityManager at
            // that point would return null. Check whether the ConnectivityService is available, and
            // avoid crashing with a NullPointerException if it is not.
            final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
            if (b != null) {
                final ConnectivityManager cm =
                        appContext.getSystemService(ConnectivityManager.class);
                Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }

        if (!Process.isIsolated()) {
            final int oldMask = StrictMode.allowThreadDiskWritesMask();
            try {
                setupGraphicsSupport(appContext);
            } finally {
                StrictMode.setThreadPolicyMask(oldMask);
            }
        } else {
            HardwareRenderer.setIsolatedProcess(true);
        }

        // Install the Network Security Config Provider. This must happen before the application
        // code is loaded to prevent issues with instances of TLS objects being created before
        // the provider is installed.
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "NetworkSecurityConfigProvider.install");
        NetworkSecurityConfigProvider.install(appContext);
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        // For backward compatibility, TrafficStats needs static access to the application context.
        // But for isolated apps which cannot access network related services, service discovery
        // is restricted. Hence, calling this would result in NPE.
        if (!Process.isIsolated()) {
            TrafficStats.init(appContext);
        }

        // Continue loading instrumentation.
        if (ii != null) {
            initInstrumentation(ii, data, appContext);
        } else {
            mInstrumentation = new Instrumentation();
            mInstrumentation.basicInit(this);
        }

        if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
            dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
        } else {
            // Small heap, clamp to the current growth limit and let the heap release
            // pages after the growth limit to the non growth limit capacity. b/18387825
            dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
        }

        // Allow disk access during application and provider setup. This could
        // block processing ordered broadcasts, but later processing would
        // probably end up doing the same disk access.
        Application app;
        final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
        final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();

        if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
            mDdmSyncStageUpdater.next(Stage.Debugger);
            if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
                waitForDebugger(data);
            } else if (data.debugMode == ApplicationThreadConstants.DEBUG_SUSPEND) {
                suspendAllAndSendVmStart(data);
            }
            // Nothing special to do in case of DEBUG_ON.
        }
        mDdmSyncStageUpdater.next(Stage.Running);

        long timestampApplicationOnCreateNs = 0;
        try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            app = data.info.makeApplicationInner(data.restrictedBackupMode, null);

            // Propagate autofill compat state
            app.setAutofillOptions(data.autofillOptions);

            // Propagate Content Capture options
            app.setContentCaptureOptions(data.contentCaptureOptions);
            if (data.contentCaptureOptions != null) {
                if (data.contentCaptureOptions.enableReceiver
                        && !data.contentCaptureOptions.lite) {
                    // Warm up the background thread when:
                    // 1) app is launched with content capture enabled, and
                    // 2) the app is NOT launched with content capture lite enabled.
                    BackgroundThread.startIfNeeded();
                }
            }
            sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName);

            mInitialApplication = app;
            final boolean updateHttpProxy;
            synchronized (this) {
                updateHttpProxy = mUpdateHttpProxyOnBind;
                // This synchronized block ensures that any subsequent call to updateHttpProxy()
                // will see a non-null mInitialApplication.
            }
            if (updateHttpProxy) {
                ActivityThread.updateHttpProxy(app);
            }

            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                }
            }

            // Do this after providers, since instrumentation tests generally start their
            // test thread at this point, and we don't want that racing.
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            catch (Exception e) {
                throw new RuntimeException(
                    "Exception thrown in onCreate() of " + data.instrumentationName, e);
            }
            try {
                timestampApplicationOnCreateNs = SystemClock.uptimeNanos();
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                timestampApplicationOnCreateNs = 0;
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                      "Unable to create application " + app.getClass().getName(), e);
                }
            }
        } finally {
            // If the app targets < O-MR1, or doesn't change the thread policy
            // during startup, clobber the policy to maintain behavior of b/36951662
            if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
                    || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
                StrictMode.setThreadPolicy(savedPolicy);
            }
        }

        // Preload fonts resources
        FontsContract.setApplicationContextForResources(appContext);
        if (!Process.isIsolated()) {
            try {
                final ApplicationInfo info =
                        getPackageManager().getApplicationInfo(
                                data.appInfo.packageName,
                                PackageManager.GET_META_DATA /*flags*/,
                                UserHandle.myUserId());
                if (info != null && info.metaData != null) {
                    final int preloadedFontsResource = info.metaData.getInt(
                            ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
                    if (preloadedFontsResource != 0) {
                        data.info.getResources().preloadFonts(preloadedFontsResource);
                    }
                }
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        try {
            mgr.finishAttachApplication(mStartSeq, timestampApplicationOnCreateNs);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }

        // Set binder transaction callback after finishing bindApplication
        Binder.setTransactionCallback(
                new IBinderCallback() {
                    @Override
                    public void onTransactionError(int pid, int code, int flags, int err) {
                        final long now = SystemClock.uptimeMillis();
                        if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE) {
                            Slog.d(
                                    TAG,
                                    "Too many transaction errors, throttling transaction error"
                                        + " callback.");
                            return;
                        }
                        mBinderCallbackLast = now;
                        try {
                            mgr.frozenBinderTransactionDetected(pid, code, flags, err);
                        } catch (RemoteException ex) {
                            throw ex.rethrowFromSystemServer();
                        }
                    }
                });

        // Register callback to report native memory metrics post GC cleanup
        // Note: we do not report memory metrics of isolated processes unless
        // their native allocations become more significant. Instrumentation is
        // also excluded because the metrics from test cases are not meaningful.
        if (!Process.isIsolated() && ii == null && Flags.reportPostgcMemoryMetrics()
                && com.android.libcore.readonly.Flags.postCleanupApis()) {
            VMRuntime.addPostCleanupCallback(new Runnable() {
                @Override public void run() {
                    MetricsLoggerWrapper.logPostGcMemorySnapshot();
                }
            });
        }

        // Initialize embedding if needed.
        if (com.android.window.flags.Flags.virtualGamepadOverride()
                && CompatChanges.isChangeEnabled(OVERRIDE_ENABLE_VIRTUAL_GAMEPAD)
                && isVirtualGamepadOverrideAllowed(app)
                && !Process.isIsolated()) {
            WindowExtensionsHelper.initEmbedding(app);
        }
    }

    private static boolean isVirtualGamepadOverrideAllowed(Application app) {
        try {
            return app.getPackageManager().getProperty(
                    PROPERTY_COMPAT_ALLOW_VIRTUAL_GAMEPAD_OVERRIDE,
                    app.getPackageName()).getBoolean();
        } catch (PackageManager.NameNotFoundException e) {
            // Default to true if the property is not specified.
            return true;
        }
    }

    @UnsupportedAppUsage
    private void waitForDebugger(AppBindData data) {
        final IActivityManager mgr = ActivityManager.getService();
        Slog.w(TAG, "Application " + data.info.getPackageName()
                         + " is waiting for the debugger ...");

        try {
            mgr.showWaitingForDebugger(mAppThread, true);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }

        Debug.waitForDebugger();

        try {
            mgr.showWaitingForDebugger(mAppThread, false);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    @UnsupportedAppUsage
    private void suspendAllAndSendVmStart(AppBindData data) {
        final IActivityManager mgr = ActivityManager.getService();
        Slog.w(TAG, "Application " + data.info.getPackageName()
                         + " is suspending. Debugger needs to resume to continue.");

        try {
            mgr.showWaitingForDebugger(mAppThread, true);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }

        Debug.suspendAllAndSendVmStart();

        try {
            mgr.showWaitingForDebugger(mAppThread, false);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    /**
     * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
     * entry names.
     * Otherwise: clear the callback to the default validation.
     */
    private void initZipPathValidatorCallback() {
        if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
            ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
        } else {
            ZipPathValidator.clearCallback();
        }
    }

    private void handleSetContentCaptureOptionsCallback(String packageName) {
        if (mContentCaptureOptionsCallback != null) {
            return;
        }

        IBinder b = ServiceManager.getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
        if (b == null) {
            return;
        }

        IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(b);
        mContentCaptureOptionsCallback = new IContentCaptureOptionsCallback.Stub() {
            @Override
            public void setContentCaptureOptions(ContentCaptureOptions options)
                    throws RemoteException {
                if (mInitialApplication != null) {
                    mInitialApplication.setContentCaptureOptions(options);
                }
            }
        };
        try {
            service.registerContentCaptureOptionsCallback(packageName,
                    mContentCaptureOptionsCallback);
        } catch (RemoteException e)  {
            Slog.w(TAG, "registerContentCaptureOptionsCallback() failed: "
                    + packageName, e);
            mContentCaptureOptionsCallback = null;
        }
    }

    private void handleInstrumentWithoutRestart(AppBindData data) {
        try {
            data.compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
            data.info = getPackageInfoNoCheck(data.appInfo);
            mInstrumentingWithoutRestart = true;
            final InstrumentationInfo ii = prepareInstrumentation(data);
            final ContextImpl appContext =
                    ContextImpl.createAppContext(this, data.info);

            initInstrumentation(ii, data, appContext);

            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            } catch (Exception e) {
                throw new RuntimeException(
                        "Exception thrown in onCreate() of " + data.instrumentationName, e);
            }

        } catch (Exception e) {
            Slog.e(TAG, "Error in handleInstrumentWithoutRestart", e);
        }
    }

    private InstrumentationInfo prepareInstrumentation(AppBindData data) {
        final InstrumentationInfo ii;
        try {
            ii = getPackageManager().getInstrumentationInfoAsUser(data.instrumentationName,
                    0 /* flags */, UserHandle.myUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        if (ii == null) {
            throw new RuntimeException(
                    "Unable to find instrumentation info for: " + data.instrumentationName);
        }

        // Warn of potential ABI mismatches.
        if (!Objects.equals(data.appInfo.primaryCpuAbi, ii.primaryCpuAbi)
                || !Objects.equals(data.appInfo.secondaryCpuAbi, ii.secondaryCpuAbi)) {
            Slog.w(TAG, "Package uses different ABI(s) than its instrumentation: "
                    + "package[" + data.appInfo.packageName + "]: "
                    + data.appInfo.primaryCpuAbi + ", " + data.appInfo.secondaryCpuAbi
                    + " instrumentation[" + ii.packageName + "]: "
                    + ii.primaryCpuAbi + ", " + ii.secondaryCpuAbi);
        }

        mInstrumentationPackageName = ii.packageName;
        mInstrumentationAppDir = ii.sourceDir;
        mInstrumentationSplitAppDirs = ii.splitSourceDirs;
        mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
        mInstrumentedAppDir = data.info.getAppDir();
        mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
        mInstrumentedLibDir = data.info.getLibDir();

        return ii;
    }

    private void initInstrumentation(
            InstrumentationInfo ii, AppBindData data, ContextImpl appContext) {
        ApplicationInfo instrApp;
        try {
            instrApp = getPackageManager().getApplicationInfo(ii.packageName, 0,
                    UserHandle.myUserId());
        } catch (RemoteException e) {
            instrApp = null;
        }
        if (instrApp == null) {
            instrApp = new ApplicationInfo();
        }
        ii.copyTo(instrApp);
        instrApp.initForUser(UserHandle.myUserId());
        final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                appContext.getClassLoader(), false, true, false);

        // The test context's op package name == the target app's op package name, because
        // the app ops manager checks the op package name against the real calling UID,
        // which is what the target package name is associated with.
        // In the case of instrumenting an sdk running in the sdk sandbox, appContext refers
        // to the context of the sdk running in the sandbox. Since the sandbox does not have
        // access to data outside the sandbox, we require the instrContext to point to the
        // sdk in the sandbox as well, and not to the test context.
        final ContextImpl instrContext =
                (data.isSdkInSandbox)
                        ? appContext
                        : ContextImpl.createAppContext(this, pi, appContext.getOpPackageName());

        try {
            final ClassLoader cl = instrContext.getClassLoader();
            mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to instantiate instrumentation " + data.instrumentationName, e);
        }

        final ComponentName component = new ComponentName(ii.packageName, ii.name);
        mInstrumentation.init(this, instrContext, appContext, component,
                data.instrumentationWatcher, data.instrumentationUiAutomationConnection);

        if (mProfiler.profileFile != null && !ii.handleProfiling
                && mProfiler.profileFd == null) {
            mProfiler.handlingProfiling = true;
            final File file = new File(mProfiler.profileFile);
            file.getParentFile().mkdirs();
            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
        }

        if (ii.packageName != null) {
            VMDebug.addApplication(ii.packageName);
        }
    }

    private void handleFinishInstrumentationWithoutRestart() {
        LoadedApk loadedApk = getApplication().mLoadedApk;
        // Only remove instrumentation app if this was not a self-testing app.
        if (mInstrumentationPackageName != null && loadedApk != null && !mInstrumentationPackageName
                .equals(loadedApk.mPackageName)) {
            VMDebug.removeApplication(mInstrumentationPackageName);
        }

        mInstrumentation.onDestroy();
        mInstrumentationPackageName = null;
        mInstrumentationAppDir = null;
        mInstrumentationSplitAppDirs = null;
        mInstrumentationLibDir = null;
        mInstrumentedAppDir = null;
        mInstrumentedSplitAppDirs = null;
        mInstrumentedLibDir = null;
        mInstrumentingWithoutRestart = false;
    }

    /*package*/ void finishInstrumentation(int resultCode, Bundle results) {
        IActivityManager am = ActivityManager.getService();
        if (mProfiler.profileFile != null && mProfiler.handlingProfiling
                && mProfiler.profileFd == null) {
            Debug.stopMethodTracing();
        }
        //Slog.i(TAG, "am: " + ActivityManager.getService()
        //      + ", app thr: " + mAppThread);
        try {
            am.finishInstrumentation(mAppThread, resultCode, results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        if (mInstrumentingWithoutRestart) {
            sendMessage(H.FINISH_INSTRUMENTATION_WITHOUT_RESTART, null);
        }
    }

    @UnsupportedAppUsage
    private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            if (DEBUG_PROVIDER) {
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    @UnsupportedAppUsage
    public IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {
            return provider;
        }

        // There is a possible race here.  Another thread may try to acquire
        // the same provider at the same time.  When this happens, we want to ensure
        // that the first one wins.
        // Note that we cannot hold the lock while acquiring and installing the
        // provider since it might take a long time to run and it could also potentially
        // be re-entrant in the case where the provider is in the same process.
        ContentProviderHolder holder;
        final ProviderKey key = getGetProviderKey(auth, userId);
        try {
            synchronized (key) {
                holder = ActivityManager.getService().getContentProvider(
                        getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
                // If the returned holder is non-null but its provider is null and it's not
                // local, we'll need to wait for the publishing of the provider.
                if (holder != null && holder.provider == null && !holder.mLocal) {
                    synchronized (key.mLock) {
                        if (key.mHolder != null) {
                            if (DEBUG_PROVIDER) {
                                Slog.i(TAG, "already received provider: " + auth);
                            }
                        } else {
                            key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                        }
                        holder = key.mHolder;
                    }
                    if (holder != null && holder.provider == null) {
                        // probably timed out
                        holder = null;
                    }
                }
            }
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        } catch (InterruptedException e) {
            holder = null;
        } finally {
            // Clear the holder from the key since the key itself is never cleared.
            synchronized (key.mLock) {
                key.mHolder = null;
            }
        }
        if (holder == null) {
            if (UserManager.get(c).isUserUnlocked(userId)) {
                Slog.e(TAG, "Failed to find provider info for " + auth);
            } else {
                Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)");
            }
            return null;
        }

        // Install provider will increment the reference count for us, and break
        // any ties in the race.
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

    private ProviderKey getGetProviderKey(String auth, int userId) {
        final ProviderKey key = new ProviderKey(auth, userId);
        synchronized (mGetProviderKeys) {
            ProviderKey lock = mGetProviderKeys.computeIfAbsent(key, k -> k);
            return lock;
        }
    }

    private void incProviderRefLocked(ProviderRefCount prc, boolean stable) {
        if (stable) {
            prc.stableCount += 1;
            if (prc.stableCount == 1) {
                // We are acquiring a new stable reference on the provider.
                int unstableDelta;
                if (prc.removePending) {
                    // We have a pending remove operation, which is holding the
                    // last unstable reference.  At this point we are converting
                    // that unstable reference to our new stable reference.
                    unstableDelta = -1;
                    // Cancel the removal of the provider.
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "incProviderRef: stable "
                                + "snatched provider from the jaws of death");
                    }
                    prc.removePending = false;
                    // There is a race! It fails to remove the message, which
                    // will be handled in completeRemoveProvider().
                    mH.removeMessages(H.REMOVE_PROVIDER, prc);
                } else {
                    unstableDelta = 0;
                }
                try {
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "incProviderRef Now stable - "
                                + prc.holder.info.name + ": unstableDelta="
                                + unstableDelta);
                    }
                    ActivityManager.getService().refContentProvider(
                            prc.holder.connection, 1, unstableDelta);
                } catch (RemoteException e) {
                    //do nothing content provider object is dead any way
                }
            }
        } else {
            prc.unstableCount += 1;
            if (prc.unstableCount == 1) {
                // We are acquiring a new unstable reference on the provider.
                if (prc.removePending) {
                    // Oh look, we actually have a remove pending for the
                    // provider, which is still holding the last unstable
                    // reference.  We just need to cancel that to take new
                    // ownership of the reference.
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "incProviderRef: unstable "
                                + "snatched provider from the jaws of death");
                    }
                    prc.removePending = false;
                    mH.removeMessages(H.REMOVE_PROVIDER, prc);
                } else {
                    // First unstable ref, increment our count in the
                    // activity manager.
                    try {
                        if (DEBUG_PROVIDER) {
                            Slog.v(TAG, "incProviderRef: Now unstable - "
                                    + prc.holder.info.name);
                        }
                        // If the provider is persistent process and has unstable
                        // connection, then we don't need to increment the ref count
                        // in the activity manager.
                        if (prc != null && prc.holder != null
                                    && !prc.holder.noReleaseNeededIfUnstable) {
                            ActivityManager.getService().refContentProvider(
                                    prc.holder.connection, 0, 1);
                        }
                    } catch (RemoteException e) {
                        //do nothing content provider object is dead any way
                    }
                }
            }
        }
    }

    @UnsupportedAppUsage
    public IContentProvider acquireExistingProvider(
            Context c, String auth, int userId, boolean stable) {
        synchronized (mProviderMap) {
            final ProviderKey key = new ProviderKey(auth, userId);
            ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                if (Flags.singleUserProviderCacheFix()) {
                    // If the provider wasn't found for the current user, check if it's a
                    // single-user provider cached under |UserHandle.USER_ALL|.
                    final ProviderKey allUsersKey = new ProviderKey(auth, UserHandle.USER_ALL);
                    pr = mProviderMap.get(allUsersKey);
                    if (pr == null) {
                        // The cached single-user providers lookup also failed.
                        return null;
                    }
                } else {
                    return null;
                }
            }

            IContentProvider provider = pr.mProvider;
            IBinder jBinder = provider.asBinder();
            if (!jBinder.isBinderAlive()) {
                // The hosting process of the provider has died; we can't
                // use this one.
                Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
                        + ": existing object's process dead");
                handleUnstableProviderDiedLocked(jBinder, true);
                return null;
            }

            // Only increment the ref count if we have one.  If we don't then the
            // provider is not reference counted and never needs to be released.
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                incProviderRefLocked(prc, stable);
            }
            return provider;
        }
    }

    @UnsupportedAppUsage
    public boolean releaseProvider(IContentProvider provider, boolean stable) {
        if (provider == null) {
            return false;
        }

        IBinder jBinder = provider.asBinder();
        synchronized (mProviderMap) {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc == null) {
                // The provider has no ref count, no release is needed.
                return false;
            }

            boolean lastRef = false;
            if (stable) {
                if (prc.stableCount == 0) {
                    if (DEBUG_PROVIDER) Slog.v(TAG,
                            "releaseProvider: stable ref count already 0, how?");
                    return false;
                }
                prc.stableCount -= 1;
                if (prc.stableCount == 0) {
                    // What we do at this point depends on whether there are
                    // any unstable refs left: if there are, we just tell the
                    // activity manager to decrement its stable count; if there
                    // aren't, we need to enqueue this provider to be removed,
                    // and convert to holding a single unstable ref while
                    // doing so.
                    lastRef = prc.unstableCount == 0;
                    try {
                        if (DEBUG_PROVIDER) {
                            Slog.v(TAG, "releaseProvider: No longer stable w/lastRef="
                                    + lastRef + " - " + prc.holder.info.name);
                        }
                        ActivityManager.getService().refContentProvider(
                                prc.holder.connection, -1, lastRef ? 1 : 0);
                    } catch (RemoteException e) {
                        //do nothing content provider object is dead any way
                    }
                }
            } else {
                if (prc.unstableCount == 0) {
                    if (DEBUG_PROVIDER) Slog.v(TAG,
                            "releaseProvider: unstable ref count already 0, how?");
                    return false;
                }
                prc.unstableCount -= 1;
                if (prc.unstableCount == 0) {
                    // If this is the last reference, we need to enqueue
                    // this provider to be removed instead of telling the
                    // activity manager to remove it at this point.
                    lastRef = prc.stableCount == 0;
                    if (!lastRef) {
                        try {
                            if (DEBUG_PROVIDER) {
                                Slog.v(TAG, "releaseProvider: No longer unstable - "
                                        + prc.holder.info.name);
                            }
                            // If the provider is persistent process and has unstable
                            // connection, then we don't need to decrement the ref count
                            // in the activity manager.
                            if (prc != null && prc.holder != null
                                    && !prc.holder.noReleaseNeededIfUnstable) {
                                ActivityManager.getService().refContentProvider(
                                        prc.holder.connection, 0, -1);
                            }
                        } catch (RemoteException e) {
                            //do nothing content provider object is dead any way
                        }
                    }
                }
            }

            if (lastRef) {
                if (!prc.removePending) {
                    // Schedule the actual remove asynchronously, since we don't know the context
                    // this will be called in.
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "releaseProvider: Enqueueing pending removal - "
                                + prc.holder.info.name);
                    }
                    prc.removePending = true;
                    Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, prc);
                    mH.sendMessageDelayed(msg, CONTENT_PROVIDER_RETAIN_TIME);
                } else {
                    Slog.w(TAG, "Duplicate remove pending of provider " + prc.holder.info.name);
                }
            }
            return true;
        }
    }

    void completeRemoveProvider(ProviderRefCount prc) {
        synchronized (mProviderMap) {
            if (!prc.removePending) {
                // There was a race!  Some other client managed to acquire
                // the provider before the removal was completed.
                // Abort the removal.  We will do it later.
                if (DEBUG_PROVIDER) Slog.v(TAG, "completeRemoveProvider: lost the race, "
                        + "provider still in use");
                return;
            }

            // More complicated race!! Some client managed to acquire the
            // provider and release it before the removal was completed.
            // Continue the removal, and abort the next remove message.
            prc.removePending = false;

            final IBinder jBinder = prc.holder.provider.asBinder();
            ProviderRefCount existingPrc = mProviderRefCountMap.get(jBinder);
            if (existingPrc == prc) {
                mProviderRefCountMap.remove(jBinder);
            }

            for (int i=mProviderMap.size()-1; i>=0; i--) {
                ProviderClientRecord pr = mProviderMap.valueAt(i);
                IBinder myBinder = pr.mProvider.asBinder();
                if (myBinder == jBinder) {
                    mProviderMap.removeAt(i);
                }
            }
        }

        try {
            if (DEBUG_PROVIDER) {
                Slog.v(TAG, "removeProvider: Invoking ActivityManagerService."
                        + "removeContentProvider(" + prc.holder.info.name + ")");
            }
            ActivityManager.getService().removeContentProvider(
                    prc.holder.connection, false);
        } catch (RemoteException e) {
            //do nothing content provider object is dead any way
        }
    }

    @UnsupportedAppUsage
    void handleUnstableProviderDied(IBinder provider, boolean fromClient) {
        synchronized (mProviderMap) {
            handleUnstableProviderDiedLocked(provider, fromClient);
        }
    }

    void handleUnstableProviderDiedLocked(IBinder provider, boolean fromClient) {
        ProviderRefCount prc = mProviderRefCountMap.get(provider);
        if (prc != null) {
            if (DEBUG_PROVIDER) Slog.v(TAG, "Cleaning up dead provider "
                    + provider + " " + prc.holder.info.name);
            mProviderRefCountMap.remove(provider);
            for (int i=mProviderMap.size()-1; i>=0; i--) {
                ProviderClientRecord pr = mProviderMap.valueAt(i);
                if (pr != null && pr.mProvider.asBinder() == provider) {
                    Slog.i(TAG, "Removing dead content provider:" + pr.mProvider.toString());
                    mProviderMap.removeAt(i);
                }
            }

            if (fromClient) {
                // We found out about this due to execution in our client
                // code.  Tell the activity manager about it now, to ensure
                // that the next time we go to do anything with the provider
                // it knows it is dead (so we don't race with its death
                // notification).
                try {
                    ActivityManager.getService().unstableProviderDied(
                            prc.holder.connection);
                } catch (RemoteException e) {
                    //do nothing content provider object is dead any way
                }
            }
        }
    }

    final void appNotRespondingViaProvider(IBinder provider) {
        synchronized (mProviderMap) {
            ProviderRefCount prc = mProviderRefCountMap.get(provider);
            if (prc != null) {
                try {
                    ActivityManager.getService()
                            .appNotRespondingViaProvider(prc.holder.connection);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }
    }

    private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
            ContentProvider localProvider, ContentProviderHolder holder) {
        final String auths[] = holder.info.authority.split(";");

        if (provider != null) {
            // If this provider is hosted by the core OS and cannot be upgraded,
            // then I guess we're okay doing blocking calls to it.
            for (String auth : auths) {
                switch (auth) {
                    case ContactsContract.AUTHORITY:
                    case CallLog.AUTHORITY:
                    case CallLog.SHADOW_AUTHORITY:
                    case BlockedNumberContract.AUTHORITY:
                    case CalendarContract.AUTHORITY:
                    case Downloads.Impl.AUTHORITY:
                    case "telephony":
                        Binder.allowBlocking(provider.asBinder());
                }
            }
        }

        final ProviderClientRecord pcr = new ProviderClientRecord(
                auths, provider, localProvider, holder);

        final int userId;
        if (Flags.singleUserProviderCacheFix()) {
            // Single-user providers are cached under |UserHandle.USER_ALL|, given they
            // should be returned regardless of the user requesting the provider.
            final boolean isSingleUser =
                    (pcr.mHolder.info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
            userId = isSingleUser
                    ? UserHandle.USER_ALL : UserHandle.getUserId(holder.info.applicationInfo.uid);
        } else {
            userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
        }

        for (String auth : auths) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord existing = mProviderMap.get(key);
            if (existing != null) {
                Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
                        + " already published as " + auth);
            } else {
                mProviderMap.put(key, pcr);
            }
        }
        return pcr;
    }

    /**
     * Installs the provider.
     *
     * Providers that are local to the process or that come from the system server
     * may be installed permanently which is indicated by setting noReleaseNeeded to true.
     * Other remote providers are reference counted.  The initial reference count
     * for all reference counted providers is one.  Providers that are not reference
     * counted do not have a reference count (at all).
     *
     * This method detects when a provider has already been installed.  When this happens,
     * it increments the reference count of the existing provider (if appropriate)
     * and returns the existing provider.  This can happen due to concurrent
     * attempts to acquire the same provider.
     */
    @UnsupportedAppUsage
    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            if (DEBUG_PROVIDER || noisy) {
                Slog.d(TAG, "Loading provider " + info.authority + ": "
                        + info.name);
            }
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context != null && context.getPackageName().equals(ai.packageName)) {
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
                c = mInitialApplication;
            } else if (context != null) {
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                Slog.w(TAG, "Unable to get context for package " +
                      ai.packageName +
                      " while loading content provider " +
                      info.name);
                return null;
            }

            if (info.splitName != null) {
                try {
                    c = c.createContextForSplit(info.splitName);
                } catch (NameNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
            if (info.attributionTags != null && info.attributionTags.length > 0) {
                final String attributionTag = info.attributionTags[0];
                c = c.createAttributionContext(attributionTag);
            }

            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                if (packageInfo == null) {
                    // System startup case.
                    packageInfo = getSystemContext().mPackageInfo;
                }
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                // XXX Need to create the correct context for this provider.
                localProvider.attachInfo(c, info);
            } catch (java.lang.Exception e) {
                if (!mInstrumentation.onException(null, e)) {
                    throw new RuntimeException(
                            "Unable to get provider " + info.name, e);
                }
                return null;
            }
        } else {
            provider = holder.provider;
            if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                    + info.name);
        }

        ContentProviderHolder retHolder;

        synchronized (mProviderMap) {
            if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
                    + " / " + info.name);
            IBinder jBinder = provider.asBinder();
            if (localProvider != null) {
                ComponentName cname = new ComponentName(info.packageName, info.name);
                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                if (pr != null) {
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, "
                                + "using existing local provider");
                    }
                    provider = pr.mProvider;
                } else {
                    holder = new ContentProviderHolder(info);
                    holder.provider = provider;
                    holder.noReleaseNeeded = true;
                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                    mLocalProviders.put(jBinder, pr);
                    mLocalProvidersByName.put(cname, pr);
                }
                retHolder = pr.mHolder;
            } else {
                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                if (prc != null) {
                    if (DEBUG_PROVIDER) {
                        Slog.v(TAG, "installProvider: lost the race, updating ref count");
                    }
                    // We need to transfer our new reference to the existing
                    // ref count, releasing the old one...  but only if
                    // release is needed (that is, it is not running in the
                    // system process).
                    if (!noReleaseNeeded) {
                        incProviderRefLocked(prc, stable);
                        try {
                            ActivityManager.getService().removeContentProvider(
                                    holder.connection, stable);
                        } catch (RemoteException e) {
                            //do nothing content provider object is dead any way
                        }
                    }
                } else {
                    ProviderClientRecord client = installProviderAuthoritiesLocked(
                            provider, localProvider, holder);
                    if (noReleaseNeeded) {
                        prc = new ProviderRefCount(holder, client, 1000, 1000);
                    } else {
                        prc = stable
                                ? new ProviderRefCount(holder, client, 1, 0)
                                : new ProviderRefCount(holder, client, 0, 1);
                    }
                    mProviderRefCountMap.put(jBinder, prc);
                }
                retHolder = prc.holder;
            }
        }
        return retHolder;
    }

    private void handleRunIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
        try {
            Method main = Class.forName(entryPoint).getMethod("main", String[].class);
            main.invoke(null, new Object[]{entryPointArgs});
        } catch (ReflectiveOperationException e) {
            throw new AndroidRuntimeException("runIsolatedEntryPoint failed", e);
        }
        // The process will be empty after this method returns; exit the VM now.
        System.exit(0);
    }

    @UnsupportedAppUsage
    private void attach(boolean system, long startSeq) {
        sCurrentActivityThread = this;
        mConfigurationController = new ConfigurationController(this);
        mSystemThread = system;
        mStartSeq = startSeq;
        mDdmSyncStageUpdater.next(Stage.Attach);

        if (!system) {
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManager.getService();
            try {
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            // Watch for getting close to heap limit.
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                                + " total=" + (runtime.totalMemory()/1024)
                                + " used=" + (dalvikUsed/1024));
                        mSomeActivitiesChanged = false;
                        try {
                            ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                            throw e.rethrowFromSystemServer();
                        }
                    }
                }
            });
        } else {
            // Don't set application object here -- if the system crashes,
            // we can't display an alert, we just want to die die die.
            android.ddm.DdmHandleAppName.setAppName("system_process",
                    UserHandle.myUserId());
            initializeSystemThread(this);
        }

        ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
            synchronized (mResourcesManager) {
                // We need to apply this change to the resources immediately, because upon returning
                // the view hierarchy will be informed about it.
                if (mResourcesManager.applyConfigurationToResources(globalConfig,
                        null /* compat */)) {
                    mConfigurationController.updateLocaleListFromAppContext(
                            mInitialApplication.getApplicationContext());

                    // This actually changed the resources! Tell everyone about it.
                    final Configuration updatedConfig =
                            mConfigurationController.updatePendingConfiguration(globalConfig);
                    if (updatedConfig != null) {
                        sendMessage(H.CONFIGURATION_CHANGED, globalConfig);
                        mPendingConfiguration = updatedConfig;
                    }
                }
            }
        };
        ViewRootImpl.addConfigCallback(configChangedCallback);
    }

    /**
     * Initializes the given system activity thread, setting up its instrumentation and initial
     * application. This only has an effect if the given thread is a {@link #mSystemThread}.
     *
     * @param thread the given system activity thread to initialize.
     */
    private static void initializeSystemThread(@NonNull ActivityThread thread) {
        if (!thread.mSystemThread) {
            return;
        }
        try {
            thread.mInstrumentation = new Instrumentation();
            thread.mInstrumentation.basicInit(thread);
            ContextImpl context = ContextImpl.createAppContext(
                    thread, thread.getSystemContext().mPackageInfo);
            thread.mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);
            thread.mInitialApplication.onCreate();
        } catch (Exception e) {
            throw new RuntimeException("Unable to instantiate Application()", e);
        }
    }

    @UnsupportedAppUsage
    public static ActivityThread systemMain() {
        ThreadedRenderer.initForSystemProcess();
        ActivityThread thread = new ActivityThread();
        thread.attach(true, 0);
        return thread;
    }

    public static void updateHttpProxy(@NonNull Context context) {
        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
        Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
    }

    @UnsupportedAppUsage
    public void installSystemProviders(List<ProviderInfo> providers) {
        if (providers != null) {
            installContentProviders(mInitialApplication, providers);
        }
    }

    /**
     * Caller should NEVER mutate the Bundle returned from here
     */
    Bundle getDefaultDeviceCoreSettings() {
        synchronized (mCoreSettingsLock) {
            return getCoreSettingsForDefaultDeviceLocked();
        }
    }

    int getIntCoreSetting(String key, int defaultValue) {
        return getIntCoreSetting(key, defaultValue, mLastReportedDeviceId);
    }

    int getIntCoreSetting(String key, int defaultValue, int deviceId) {
        synchronized (mCoreSettingsLock) {
            if (mCoreSettings != null) {
                Bundle bundle = getCoreSettingsForDeviceLocked(deviceId);
                if (bundle != null) {
                    return bundle.getInt(key, defaultValue);
                }
            }
            return defaultValue;
        }
    }

    /**
     * Get the string value of the given key from core settings.
     */
    String getStringCoreSetting(String key, String defaultValue) {
        synchronized (mCoreSettingsLock) {
            if (mCoreSettings != null) {
                Bundle bundle = getCoreSettingsForDeviceLocked(mLastReportedDeviceId);
                if (bundle != null) {
                    return bundle.getString(key, defaultValue);
                }
            }
            return defaultValue;
        }
    }

    float getFloatCoreSetting(String key, float defaultValue) {
        synchronized (mCoreSettingsLock) {
            if (mCoreSettings != null) {
                Bundle bundle = getCoreSettingsForDeviceLocked(mLastReportedDeviceId);
                if (bundle != null) {
                    return bundle.getFloat(key, defaultValue);
                }
            }
            return defaultValue;
        }
    }

    /**
     * Gets the core settings for the default device.
     *
     * On HSUM devices, core settings for a non-system user might not be available immediately
     * after the user's process starts.
     * In such cases, this method returns an empty Bundle to prevent crashes.
     */
    private Bundle getCoreSettingsForDefaultDeviceLocked() {
        Bundle bundle = getCoreSettingsForDeviceLocked(Context.DEVICE_ID_DEFAULT);
        if (bundle == null) {
            Log.w(TAG, "Core settings not yet available for current user; returning empty bundle");
            return Bundle.EMPTY;
        }
        return bundle;
    }

    private Bundle getCoreSettingsForDeviceLocked(int deviceId) {
        Bundle bundle = mCoreSettings.getBundle(String.valueOf(deviceId));
        if (deviceId != Context.DEVICE_ID_DEFAULT && bundle == null) {
            // There hasn't been any overridden settings for the virtual device, so just return
            // the settings for the default device.
            bundle = mCoreSettings.getBundle(String.valueOf(Context.DEVICE_ID_DEFAULT));
        }
        return bundle;
    }

    @RavenwoodThrow(comment = "See ActivityThread_ravenwood for initialization on Ravenwood")
    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidForwardingOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null) {
                    // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command
                    // line. It will be in the format "seq=114"
                    if (args[i].startsWith(PROC_START_SEQ_IDENT)) {
                        startSeq = Long.parseLong(
                                args[i].substring(PROC_START_SEQ_IDENT.length()));
                    } else if (args[i].startsWith("--use-deliqueue=")) {
                        boolean useDeliQueue =
                                Boolean.parseBoolean(args[i].substring(args[i].indexOf('=') + 1));
                        // This must be called before Looper.prepareMainLooper(), which will
                        // instantiate a MessageQueue.
                        MessageQueue.setUseDeliQueue(useDeliQueue);
                    }
                }
            }
        }

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

    /**
     * Call various initializer APIs in mainline modules that need to be called when each process
     * starts.
     */
    public static void initializeMainlineModules() {
        TelephonyFrameworkInitializer.setTelephonyServiceManager(new TelephonyServiceManager());
        StatsFrameworkInitializer.setStatsServiceManager(new StatsServiceManager());
        MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
        MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
        BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
        BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
            BinderCallsStats.startForBluetooth(context);
        });
        NfcFrameworkInitializer.setNfcServiceManager(new NfcServiceManager());
        DeviceConfigInitializer.setDeviceConfigServiceManager(new DeviceConfigServiceManager());
        SeFrameworkInitializer.setSeServiceManager(new SeServiceManager());
        ProfilingFrameworkInitializer.setProfilingServiceManager(new ProfilingServiceManager());
    }

    private void purgePendingResources() {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "purgePendingResources");
        nPurgePendingResources();
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

    /**
     * Returns whether the provided {@link ActivityInfo} {@code ai} is a protected component.
     *
     * @see #isProtectedComponent(ComponentInfo, String)
     */
    public static boolean isProtectedComponent(@NonNull ActivityInfo ai) {
        return isProtectedComponent(ai, ai.permission);
    }

    /**
     * Returns whether the provided {@link ServiceInfo} {@code si} is a protected component.
     *
     * @see #isProtectedComponent(ComponentInfo, String)
     */
    public static boolean isProtectedComponent(@NonNull ServiceInfo si) {
        return isProtectedComponent(si, si.permission);
    }

    /**
     * Returns whether the provided {@link ComponentInfo} {@code ci} with the specified {@code
     * permission} is a protected component.
     *
     * <p>A component is protected if it is not exported, or if the specified {@code permission} is
     * a signature permission.
     */
    private static boolean isProtectedComponent(@NonNull ComponentInfo ci,
            @Nullable String permission) {
        // Bail early when this process isn't looking for violations
        if (!StrictMode.vmUnsafeIntentLaunchEnabled()) return false;

        // TODO: consider optimizing by having AMS pre-calculate this value
        if (!ci.exported) {
            return true;
        }
        if (permission != null) {
            try {
                PermissionInfo pi = getPermissionManager().getPermissionInfo(permission,
                        currentOpPackageName(), 0);
                return (pi != null) && pi.getProtection() == PermissionInfo.PROTECTION_SIGNATURE;
            } catch (RemoteException ignored) {
            }
        }
        return false;
    }

    /**
     * Returns whether the action within the provided {@code intent} is a protected broadcast.
     */
    public static boolean isProtectedBroadcast(@NonNull Intent intent) {
        // Bail early when this process isn't looking for violations
        if (!StrictMode.vmUnsafeIntentLaunchEnabled()) return false;

        // TODO: consider optimizing by having AMS pre-calculate this value
        try {
            return getPackageManager().isProtectedBroadcast(intent.getAction());
        } catch (RemoteException ignored) {
        }
        return false;
    }

    @RavenwoodIgnore(reason = "LoadedApk calls it, but we don't need to keep track of it yet")
    void addApplication(@NonNull Application app) {
        mAllApplications.add(app);
        VMDebug.addApplication(app.mLoadedApk.mPackageName);
    }

    @Override
    public boolean isInDensityCompatMode() {
        return mDensityCompatMode;
    }

    // ------------------ Regular JNI ------------------------
    private native void nPurgePendingResources();
    private native void nInitZygoteChildHeapProfiling();
}
