diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-01-03 13:39:41 -0500 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-01-03 13:39:41 -0500 |
commit | 98fe7819c6d14f4f464a5cac047f9e82dee5da58 (patch) | |
tree | a6b8b93eb21e205b27590ab5e2a1fb9efe27f892 /android/app | |
parent | 4217cf85c20565a3446a662a7f07f26137b26b7f (diff) | |
download | android-28-98fe7819c6d14f4f464a5cac047f9e82dee5da58.tar.gz |
Import Android SDK Platform P [4524038]
/google/data/ro/projects/android/fetch_artifact \
--bid 4524038 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4524038.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: Ic193bf1cf0cae78d4f2bfb4fbddfe42025c5c3c2
Diffstat (limited to 'android/app')
84 files changed, 4162 insertions, 1235 deletions
diff --git a/android/app/ActionBar.java b/android/app/ActionBar.java index 0e8326de..04ff48ce 100644 --- a/android/app/ActionBar.java +++ b/android/app/ActionBar.java @@ -95,7 +95,11 @@ import java.lang.annotation.RetentionPolicy; public abstract class ActionBar { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}) + @IntDef(prefix = { "NAVIGATION_MODE_" }, value = { + NAVIGATION_MODE_STANDARD, + NAVIGATION_MODE_LIST, + NAVIGATION_MODE_TABS + }) public @interface NavigationMode {} /** @@ -139,15 +143,14 @@ public abstract class ActionBar { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = { - DISPLAY_USE_LOGO, - DISPLAY_SHOW_HOME, - DISPLAY_HOME_AS_UP, - DISPLAY_SHOW_TITLE, - DISPLAY_SHOW_CUSTOM, - DISPLAY_TITLE_MULTIPLE_LINES - }) + @IntDef(flag = true, prefix = { "DISPLAY_" }, value = { + DISPLAY_USE_LOGO, + DISPLAY_SHOW_HOME, + DISPLAY_HOME_AS_UP, + DISPLAY_SHOW_TITLE, + DISPLAY_SHOW_CUSTOM, + DISPLAY_TITLE_MULTIPLE_LINES + }) public @interface DisplayOptions {} /** diff --git a/android/app/Activity.java b/android/app/Activity.java index 03a3631b..aa099eb1 100644 --- a/android/app/Activity.java +++ b/android/app/Activity.java @@ -7070,7 +7070,13 @@ public class Activity extends ContextThemeWrapper mActivityTransitionState.enterReady(this); } - final void performRestart() { + /** + * Restart the activity. + * @param start Indicates whether the activity should also be started after restart. + * The option to not start immediately is needed in case a transaction with + * multiple lifecycle transitions is in progress. + */ + final void performRestart(boolean start) { mCanEnterPictureInPicture = true; mFragments.noteStateNotSaved(); @@ -7108,12 +7114,14 @@ public class Activity extends ContextThemeWrapper "Activity " + mComponent.toShortString() + " did not call through to super.onRestart()"); } - performStart(); + if (start) { + performStart(); + } } } final void performResume() { - performRestart(); + performRestart(true /* start */); mFragments.execPendingActions(); diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java index 02b7f8c5..1adae7a8 100644 --- a/android/app/ActivityManager.java +++ b/android/app/ActivityManager.java @@ -175,7 +175,7 @@ public class ActivityManager { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({ + @IntDef(prefix = { "BUGREPORT_OPTION_" }, value = { BUGREPORT_OPTION_FULL, BUGREPORT_OPTION_INTERACTIVE, BUGREPORT_OPTION_REMOTE, @@ -457,6 +457,20 @@ public class ActivityManager { /** @hide User operation call: one of related users cannot be stopped. */ public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4; + /** + * @hide + * Process states, describing the kind of state a particular process is in. + * When updating these, make sure to also check all related references to the + * constant in code, and update these arrays: + * + * @see com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE + * @see com.android.server.am.ProcessList#sProcStateToProcMem + * @see com.android.server.am.ProcessList#sFirstAwakePssTimes + * @see com.android.server.am.ProcessList#sSameAwakePssTimes + * @see com.android.server.am.ProcessList#sTestFirstPssTimes + * @see com.android.server.am.ProcessList#sTestSamePssTimes + */ + /** @hide Not a real process state. */ public static final int PROCESS_STATE_UNKNOWN = -1; @@ -476,35 +490,35 @@ public class ActivityManager { /** @hide Process is hosting a foreground service. */ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; - /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */ - public static final int PROCESS_STATE_TOP_SLEEPING = 5; - /** @hide Process is important to the user, and something they are aware of. */ - public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6; + public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5; /** @hide Process is important to the user, but not something they are aware of. */ - public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7; + public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6; /** @hide Process is in the background transient so we will try to keep running. */ - public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8; + public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 7; /** @hide Process is in the background running a backup/restore operation. */ - public static final int PROCESS_STATE_BACKUP = 9; - - /** @hide Process is in the background, but it can't restore its state so we want - * to try to avoid killing it. */ - public static final int PROCESS_STATE_HEAVY_WEIGHT = 10; + public static final int PROCESS_STATE_BACKUP = 8; /** @hide Process is in the background running a service. Unlike oom_adj, this level * is used for both the normal running in background state and the executing * operations state. */ - public static final int PROCESS_STATE_SERVICE = 11; + public static final int PROCESS_STATE_SERVICE = 9; /** @hide Process is in the background running a receiver. Note that from the * perspective of oom_adj, receivers run at a higher foreground level, but for our * prioritization here that is not necessary and putting them below services means * many fewer changes in some process states as they receive broadcasts. */ - public static final int PROCESS_STATE_RECEIVER = 12; + public static final int PROCESS_STATE_RECEIVER = 10; + + /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */ + public static final int PROCESS_STATE_TOP_SLEEPING = 11; + + /** @hide Process is in the background, but it can't restore its state so we want + * to try to avoid killing it. */ + public static final int PROCESS_STATE_HEAVY_WEIGHT = 12; /** @hide Process is in the background but hosts the home activity. */ public static final int PROCESS_STATE_HOME = 13; @@ -533,10 +547,10 @@ public class ActivityManager { // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must // be updated to correctly map between them. /** - * Maps ActivityManager.PROCESS_STATE_ values to ActivityManagerProto.ProcessState enum. + * Maps ActivityManager.PROCESS_STATE_ values to ProcessState enum. * * @param amInt a process state of the form ActivityManager.PROCESS_STATE_ - * @return the value of the corresponding android.app.ActivityManagerProto's ProcessState enum. + * @return the value of the corresponding ActivityManager's ProcessState enum. * @hide */ public static final int processStateAmToProto(int amInt) { @@ -810,7 +824,7 @@ public class ActivityManager { * impose on your application to let the overall system work best. The * returned value is in megabytes; the baseline Android memory class is * 16 (which happens to be the Java heap limit of those devices); some - * device with more memory may return 24 or even higher numbers. + * devices with more memory may return 24 or even higher numbers. */ public int getMemoryClass() { return staticGetMemoryClass(); @@ -837,7 +851,7 @@ public class ActivityManager { * constrained devices, or it may be significantly larger on devices with * a large amount of available RAM. * - * <p>The is the size of the application's Dalvik heap if it has + * <p>This is the size of the application's Dalvik heap if it has * specified <code>android:largeHeap="true"</code> in its manifest. */ public int getLargeMemoryClass() { @@ -1944,15 +1958,17 @@ public class ActivityManager { * @param animate Whether we should play an animation for the moving the task * @param initialBounds If the primary stack gets created, it will use these bounds for the * docked stack. Pass {@code null} to use default bounds. + * @param showRecents If the recents activity should be shown on the other side of the task + * going into split-screen mode. * @hide */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop, - boolean animate, Rect initialBounds) throws SecurityException { + boolean animate, Rect initialBounds, boolean showRecents) throws SecurityException { try { getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate, - initialBounds); + initialBounds, showRecents); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2611,7 +2627,7 @@ public class ActivityManager { Manifest.permission.ACCESS_INSTANT_APPS}) public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { try { - return getService().clearApplicationUserData(packageName, + return getService().clearApplicationUserData(packageName, false, observer, UserHandle.myUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2882,13 +2898,13 @@ public class ActivityManager { public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; /** - * Constant for {@link #importance}: This process is running the foreground - * UI, but the device is asleep so it is not visible to the user. This means - * the user is not really aware of the process, because they can not see or - * interact with it, but it is quite important because it what they expect to - * return to once unlocking the device. + * @deprecated Pre-{@link android.os.Build.VERSION_CODES#P} version of + * {@link #IMPORTANCE_TOP_SLEEPING}. As of Android + * {@link android.os.Build.VERSION_CODES#P}, this is considered much less + * important since we want to reduce what apps can do when the screen is off. */ - public static final int IMPORTANCE_TOP_SLEEPING = 150; + @Deprecated + public static final int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150; /** * Constant for {@link #importance}: This process is running something @@ -2940,14 +2956,6 @@ public class ActivityManager { public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170; /** - * Constant for {@link #importance}: This process is running an - * application that can not save its state, and thus can't be killed - * while in the background. - * @hide - */ - public static final int IMPORTANCE_CANT_SAVE_STATE= 270; - - /** * Constant for {@link #importance}: This process is contains services * that should remain running. These are background services apps have * started, not something the user is aware of, so they may be killed by @@ -2957,6 +2965,23 @@ public class ActivityManager { public static final int IMPORTANCE_SERVICE = 300; /** + * Constant for {@link #importance}: This process is running the foreground + * UI, but the device is asleep so it is not visible to the user. Though the + * system will try hard to keep its process from being killed, in all other + * ways we consider it a kind of cached process, with the limitations that go + * along with that state: network access, running background services, etc. + */ + public static final int IMPORTANCE_TOP_SLEEPING = 325; + + /** + * Constant for {@link #importance}: This process is running an + * application that can not save its state, and thus can't be killed + * while in the background. This will be used with apps that have + * {@link android.R.attr#cantSaveState} set on their application tag. + */ + public static final int IMPORTANCE_CANT_SAVE_STATE = 350; + + /** * Constant for {@link #importance}: This process process contains * cached code that is expendable, not actively running any app components * we care about. @@ -2991,16 +3016,16 @@ public class ActivityManager { return IMPORTANCE_GONE; } else if (procState >= PROCESS_STATE_HOME) { return IMPORTANCE_CACHED; + } else if (procState == PROCESS_STATE_HEAVY_WEIGHT) { + return IMPORTANCE_CANT_SAVE_STATE; + } else if (procState >= PROCESS_STATE_TOP_SLEEPING) { + return IMPORTANCE_TOP_SLEEPING; } else if (procState >= PROCESS_STATE_SERVICE) { return IMPORTANCE_SERVICE; - } else if (procState > PROCESS_STATE_HEAVY_WEIGHT) { - return IMPORTANCE_CANT_SAVE_STATE; } else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) { return IMPORTANCE_PERCEPTIBLE; } else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) { return IMPORTANCE_VISIBLE; - } else if (procState >= PROCESS_STATE_TOP_SLEEPING) { - return IMPORTANCE_TOP_SLEEPING; } else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) { return IMPORTANCE_FOREGROUND_SERVICE; } else { @@ -3034,6 +3059,8 @@ public class ActivityManager { switch (importance) { case IMPORTANCE_PERCEPTIBLE: return IMPORTANCE_PERCEPTIBLE_PRE_26; + case IMPORTANCE_TOP_SLEEPING: + return IMPORTANCE_TOP_SLEEPING_PRE_28; case IMPORTANCE_CANT_SAVE_STATE: return IMPORTANCE_CANT_SAVE_STATE_PRE_26; } @@ -3047,16 +3074,18 @@ public class ActivityManager { return PROCESS_STATE_NONEXISTENT; } else if (importance >= IMPORTANCE_CACHED) { return PROCESS_STATE_HOME; + } else if (importance >= IMPORTANCE_CANT_SAVE_STATE) { + return PROCESS_STATE_HEAVY_WEIGHT; + } else if (importance >= IMPORTANCE_TOP_SLEEPING) { + return PROCESS_STATE_TOP_SLEEPING; } else if (importance >= IMPORTANCE_SERVICE) { return PROCESS_STATE_SERVICE; - } else if (importance > IMPORTANCE_CANT_SAVE_STATE) { - return PROCESS_STATE_HEAVY_WEIGHT; } else if (importance >= IMPORTANCE_PERCEPTIBLE) { return PROCESS_STATE_TRANSIENT_BACKGROUND; } else if (importance >= IMPORTANCE_VISIBLE) { return PROCESS_STATE_IMPORTANT_FOREGROUND; - } else if (importance >= IMPORTANCE_TOP_SLEEPING) { - return PROCESS_STATE_TOP_SLEEPING; + } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) { + return PROCESS_STATE_FOREGROUND_SERVICE; } else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) { return PROCESS_STATE_FOREGROUND_SERVICE; } else { @@ -3835,7 +3864,7 @@ public class ActivityManager { pw.println(); dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName }); pw.println(); - dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName }); + dumpService(pw, fd, "usagestats", new String[] { packageName }); pw.println(); dumpService(pw, fd, BatteryStats.SERVICE_NAME, new String[] { packageName }); pw.flush(); diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java index d7efa91f..60a5a110 100644 --- a/android/app/ActivityManagerInternal.java +++ b/android/app/ActivityManagerInternal.java @@ -99,7 +99,10 @@ public abstract class ActivityManagerInternal { // Called by the power manager. public abstract void onWakefulnessChanged(int wakefulness); - public abstract int startIsolatedProcess(String entryPoint, String[] mainArgs, + /** + * @return {@code true} if process start is successful, {@code false} otherwise. + */ + public abstract boolean startIsolatedProcess(String entryPoint, String[] mainArgs, String processName, String abiOverride, int uid, Runnable crashHandler); /** @@ -261,6 +264,11 @@ public abstract class ActivityManagerInternal { public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq); /** + * Called after the voice interaction service has changed. + */ + public abstract void notifyActiveVoiceInteractionServiceChanged(ComponentName component); + + /** * Called after virtual display Id is updated by * {@link com.android.server.vr.Vr2dDisplay} with a specific * {@param vr2dDisplayId}. @@ -299,4 +307,16 @@ public abstract class ActivityManagerInternal { * @return true if runtime was restarted, false if it's normal boot */ public abstract boolean isRuntimeRestarted(); + + /** + * Returns {@code true} if {@code uid} is running an activity from {@code packageName}. + */ + public abstract boolean hasRunningActivity(int uid, @Nullable String packageName); + + public interface ScreenObserver { + public void onAwakeStateChanged(boolean isAwake); + public void onKeyguardStateChanged(boolean isShowing); + } + + public abstract void registerScreenObserver(ScreenObserver observer); } diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java index 4a21f5c4..e61c5b7c 100644 --- a/android/app/ActivityOptions.java +++ b/android/app/ActivityOptions.java @@ -36,6 +36,7 @@ import android.os.IRemoteCallback; import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.UserHandle; import android.transition.Transition; import android.transition.TransitionListenerAdapter; import android.transition.TransitionManager; @@ -265,6 +266,8 @@ public class ActivityOptions { public static final int ANIM_CUSTOM_IN_PLACE = 10; /** @hide */ public static final int ANIM_CLIP_REVEAL = 11; + /** @hide */ + public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12; private String mPackageName; private Rect mLaunchBounds; @@ -486,6 +489,19 @@ public class ActivityOptions { } /** + * Creates an {@link ActivityOptions} object specifying an animation where the new activity + * is started in another user profile by calling {@link + * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle) + * }. + * @hide + */ + public static ActivityOptions makeOpenCrossProfileAppsAnimation() { + ActivityOptions options = new ActivityOptions(); + options.mAnimationType = ANIM_OPEN_CROSS_PROFILE_APPS; + return options; + } + + /** * Create an ActivityOptions specifying an animation where a thumbnail * is scaled from a given position to the new activity window that is * being started. diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java index ffd012d9..aaa6bf03 100644 --- a/android/app/ActivityThread.java +++ b/android/app/ActivityThread.java @@ -16,6 +16,13 @@ package android.app; +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.view.Display.INVALID_DISPLAY; import android.annotation.NonNull; @@ -23,8 +30,12 @@ import android.annotation.Nullable; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.backup.BackupAgent; +import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.PendingTransactionActions; +import android.app.servertransaction.PendingTransactionActions.StopInfo; +import android.app.servertransaction.TransactionExecutor; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; @@ -69,6 +80,7 @@ import android.os.DropBoxManager; import android.os.Environment; import android.os.GraphicsEnvironment; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.LocaleList; import android.os.Looper; @@ -84,7 +96,6 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; -import android.os.TransactionTooLargeException; import android.os.UserHandle; import android.provider.BlockedNumberContract; import android.provider.CalendarContract; @@ -102,12 +113,12 @@ import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.LogPrinter; -import android.util.LogWriter; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseIntArray; import android.util.SuperNotCalledException; +import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.Display; import android.view.ThreadedRenderer; @@ -121,6 +132,7 @@ import android.view.WindowManagerGlobal; import android.webkit.WebView; 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.BinderInternal; @@ -128,9 +140,9 @@ import com.android.internal.os.RuntimeInit; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; -import com.android.internal.util.IndentingPrintWriter; import com.android.org.conscrypt.OpenSSLSocketImpl; import com.android.org.conscrypt.TrustedCertificateStore; +import com.android.server.am.proto.MemInfoProto; import dalvik.system.BaseDexClassLoader; import dalvik.system.CloseGuard; @@ -161,6 +173,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TimeZone; +import java.util.concurrent.Executor; final class RemoteServiceException extends AndroidRuntimeException { public RemoteServiceException(String msg) { @@ -188,7 +201,7 @@ public final class ActivityThread extends ClientTransactionHandler { private static final boolean DEBUG_BACKUP = false; public static final boolean DEBUG_CONFIGURATION = false; private static final boolean DEBUG_SERVICE = false; - private static final boolean DEBUG_MEMORY_TRIM = false; + public static final boolean DEBUG_MEMORY_TRIM = false; private static final boolean DEBUG_PROVIDER = false; private static final boolean DEBUG_ORDER = false; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; @@ -204,10 +217,6 @@ public final class ActivityThread extends ClientTransactionHandler { /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */ public static final int SERVICE_DONE_EXECUTING_STOP = 2; - // Details for pausing activity. - private static final int USER_LEAVING = 1; - private static final int DONT_REPORT = 2; - // Whether to invoke an activity callback after delivering new configuration. private static final boolean REPORT_TO_ACTIVITY = true; @@ -216,6 +225,12 @@ public final class ActivityThread extends ClientTransactionHandler { */ 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(); /** @@ -235,6 +250,7 @@ public final class ActivityThread extends ClientTransactionHandler { final ApplicationThread mAppThread = new ApplicationThread(); final Looper mLooper = Looper.myLooper(); final H mH = new H(); + final Executor mExecutor = new HandlerExecutor(mH); final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. @@ -287,12 +303,8 @@ public final class ActivityThread extends ClientTransactionHandler { final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<>(); @GuardedBy("mResourcesManager") Configuration mPendingConfiguration = null; - // Because we merge activity relaunch operations we can't depend on the ordering provided by - // the handler messages. We need to introduce secondary ordering mechanism, which will allow - // us to drop certain events, if we know that they happened before relaunch we already executed. - // This represents the order of receiving the request from AM. - @GuardedBy("mResourcesManager") - int mLifecycleSeq = 0; + // An executor that performs multi-step transactions. + private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this); private final ResourcesManager mResourcesManager; @@ -340,8 +352,9 @@ public final class ActivityThread extends ClientTransactionHandler { Bundle mCoreSettings = null; - static final class ActivityClientRecord { - IBinder token; + /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */ + public static final class ActivityClientRecord { + public IBinder token; int ident; Intent intent; String referrer; @@ -353,6 +366,7 @@ public final class ActivityThread extends ClientTransactionHandler { Activity parent; String embeddedID; Activity.NonConfigurationInstances lastNonConfigurationInstances; + // TODO(lifecycler): Use mLifecycleState instead. boolean paused; boolean stopped; boolean hideForNow; @@ -369,13 +383,13 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityInfo activityInfo; CompatibilityInfo compatInfo; - LoadedApk packageInfo; + public LoadedApk loadedApk; List<ResultInfo> pendingResults; List<ReferrerIntent> pendingIntents; boolean startsNotResumed; - boolean isForward; + public final boolean isForward; int pendingConfigChanges; boolean onlyLocalRequest; @@ -383,15 +397,42 @@ public final class ActivityThread extends ClientTransactionHandler { WindowManager mPendingRemoveWindowManager; boolean mPreserveWindow; - // Set for relaunch requests, indicates the order number of the relaunch operation, so it - // can be compared with other lifecycle operations. - int relaunchSeq = 0; + @LifecycleState + private int mLifecycleState = PRE_ON_CREATE; - // Can only be accessed from the UI thread. This represents the latest processed message - // that is related to lifecycle events/ - int lastProcessedSeq = 0; + @VisibleForTesting + public ActivityClientRecord() { + this.isForward = false; + init(); + } - ActivityClientRecord() { + public ActivityClientRecord(IBinder token, Intent intent, int ident, + ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo, + String referrer, IVoiceInteractor voiceInteractor, Bundle state, + PersistableBundle persistentState, List<ResultInfo> pendingResults, + List<ReferrerIntent> pendingNewIntents, boolean isForward, + ProfilerInfo profilerInfo, ClientTransactionHandler client) { + this.token = token; + this.ident = ident; + this.intent = intent; + this.referrer = referrer; + this.voiceInteractor = voiceInteractor; + this.activityInfo = info; + this.compatInfo = compatInfo; + this.state = state; + this.persistentState = persistentState; + this.pendingResults = pendingResults; + this.pendingIntents = pendingNewIntents; + this.isForward = isForward; + this.profilerInfo = profilerInfo; + this.overrideConfig = overrideConfig; + this.loadedApk = client.getLoadedApkNoCheck(activityInfo.applicationInfo, + compatInfo); + init(); + } + + /** Common initializer for all constructors. */ + private void init() { parent = null; embeddedID = null; paused = false; @@ -408,6 +449,38 @@ public final class ActivityThread extends ClientTransactionHandler { }; } + /** 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; + } + } + public boolean isPreHoneycomb() { if (activity != null) { return activity.getApplicationInfo().targetSdkVersion @@ -535,7 +608,7 @@ public final class ActivityThread extends ClientTransactionHandler { } static final class AppBindData { - LoadedApk info; + LoadedApk loadedApk; String processName; ApplicationInfo appInfo; List<ProviderInfo> providers; @@ -1079,7 +1152,7 @@ public final class ActivityThread extends ClientTransactionHandler { int N = stats.dbStats.size(); if (N > 0) { pw.println(" DATABASES"); - printRow(pw, " %8s %8s %14s %14s %s", "pgsz", "dbsz", "Lookaside(b)", "cache", + printRow(pw, DB_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache", "Dbname"); for (int i = 0; i < N; i++) { DbStats dbStats = stats.dbStats.get(i); @@ -1111,6 +1184,124 @@ public final class ActivityThread extends ClientTransactionHandler { } @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); + } + } + + 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, + OpenSSLSocketImpl.class + }; + long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true); + long appContextInstanceCount = instanceCounts[0]; + long activityInstanceCount = instanceCounts[1]; + long webviewInstanceCount = instanceCounts[2]; + long openSslSocketCount = instanceCounts[3]; + + long viewInstanceCount = ViewDebug.getViewInstanceCount(); + long viewRootInstanceCount = ViewDebug.getViewRootImplCount(); + 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(MemInfoProto.AppData.PROCESS_MEMORY); + proto.write(MemInfoProto.ProcessMemory.PID, Process.myPid()); + proto.write(MemInfoProto.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(MemInfoProto.AppData.OBJECTS); + proto.write(MemInfoProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT, viewInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT, + viewRootInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT, + appContextInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT, + activityInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT, globalAssetCount); + proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT, + globalAssetManagerCount); + proto.write(MemInfoProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT, + binderLocalObjectCount); + proto.write(MemInfoProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT, + binderProxyObjectCount); + proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_MEMORY_KB, parcelSize / 1024); + proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount); + proto.write(MemInfoProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT, + binderDeathObjectCount); + proto.write(MemInfoProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, openSslSocketCount); + proto.write(MemInfoProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT, + webviewInstanceCount); + proto.end(oToken); + + // SQLite mem info + final long sToken = proto.start(MemInfoProto.AppData.SQL); + proto.write(MemInfoProto.AppData.SqlStats.MEMORY_USED_KB, stats.memoryUsed / 1024); + proto.write(MemInfoProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB, + stats.pageCacheOverflow / 1024); + proto.write(MemInfoProto.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(MemInfoProto.AppData.SqlStats.DATABASES); + proto.write(MemInfoProto.AppData.SqlStats.Database.NAME, dbStats.dbName); + proto.write(MemInfoProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize); + proto.write(MemInfoProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize); + proto.write(MemInfoProto.AppData.SqlStats.Database.LOOKASIDE_B, dbStats.lookaside); + proto.write(MemInfoProto.AppData.SqlStats.Database.CACHE, dbStats.cache); + proto.end(dToken); + } + proto.end(sToken); + + // Asset details. + String assetAlloc = AssetManager.getAssetAllocations(); + if (assetAlloc != null) { + proto.write(MemInfoProto.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(MemInfoProto.AppData.UNREACHABLE_MEMORY, + Debug.getUnreachableMemory(100, showContents)); + } + } + + @Override public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) { nDumpGraphicsInfo(pfd.getFileDescriptor()); WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), args); @@ -1313,13 +1504,6 @@ public final class ActivityThread extends ClientTransactionHandler { mAppThread.updateProcessState(processState, fromIpc); } - @Override - public int getLifecycleSeq() { - synchronized (mResourcesManager) { - return mLifecycleSeq++; - } - } - class H extends Handler { public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; @@ -1584,7 +1768,9 @@ public final class ActivityThread extends ClientTransactionHandler { (String[]) ((SomeArgs) msg.obj).arg2); break; case EXECUTE_TRANSACTION: - ((ClientTransaction) msg.obj).execute(ActivityThread.this); + final ClientTransaction transaction = (ClientTransaction) msg.obj; + mTransactionExecutor.execute(transaction); + transaction.recycle(); break; } Object obj = msg.obj; @@ -1714,13 +1900,13 @@ public final class ActivityThread extends ClientTransactionHandler { return mH; } - public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, - int flags) { - return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId()); + public final LoadedApk getLoadedApkForPackageName(String packageName, + CompatibilityInfo compatInfo, int flags) { + return getLoadedApkForPackageName(packageName, compatInfo, flags, UserHandle.myUserId()); } - public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, - int flags, int userId) { + public final LoadedApk getLoadedApkForPackageName(String packageName, + CompatibilityInfo compatInfo, int flags, int userId) { final boolean differentUser = (UserHandle.myUserId() != userId); synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; @@ -1733,13 +1919,13 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(packageName); } - LoadedApk packageInfo = ref != null ? ref.get() : null; - //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); - //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir - // + ": " + packageInfo.mResources.getAssets().isUpToDate()); - if (packageInfo != null && (packageInfo.mResources == null - || packageInfo.mResources.getAssets().isUpToDate())) { - if (packageInfo.isSecurityViolation() + LoadedApk loadedApk = ref != null ? ref.get() : null; + //Slog.i(TAG, "getLoadedApkForPackageName " + packageName + ": " + loadedApk); + //if (loadedApk != null) Slog.i(TAG, "isUptoDate " + loadedApk.mResDir + // + ": " + loadedApk.mResources.getAssets().isUpToDate()); + if (loadedApk != null && (loadedApk.mResources == null + || loadedApk.mResources.getAssets().isUpToDate())) { + if (loadedApk.isSecurityViolation() && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { throw new SecurityException( "Requesting code from " + packageName @@ -1747,7 +1933,7 @@ public final class ActivityThread extends ClientTransactionHandler { + mBoundApplication.processName + "/" + mBoundApplication.appInfo.uid); } - return packageInfo; + return loadedApk; } } @@ -1762,13 +1948,13 @@ public final class ActivityThread extends ClientTransactionHandler { } if (ai != null) { - return getPackageInfo(ai, compatInfo, flags); + return getLoadedApk(ai, compatInfo, flags); } return null; } - public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo, + public final LoadedApk getLoadedApk(ApplicationInfo ai, CompatibilityInfo compatInfo, int flags) { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 @@ -1790,16 +1976,17 @@ public final class ActivityThread extends ClientTransactionHandler { throw new SecurityException(msg); } } - return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode, + return getLoadedApk(ai, compatInfo, null, securityViolation, includeCode, registerPackage); } - public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, + @Override + public final LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { - return getPackageInfo(ai, compatInfo, null, false, true, false); + return getLoadedApk(ai, compatInfo, null, false, true, false); } - public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { + public final LoadedApk peekLoadedApk(String packageName, boolean includeCode) { synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; if (includeCode) { @@ -1811,7 +1998,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, + private LoadedApk getLoadedApk(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); @@ -1826,35 +2013,35 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(aInfo.packageName); } - LoadedApk packageInfo = ref != null ? ref.get() : null; - if (packageInfo == null || (packageInfo.mResources != null - && !packageInfo.mResources.getAssets().isUpToDate())) { + LoadedApk loadedApk = ref != null ? ref.get() : null; + if (loadedApk == null || (loadedApk.mResources != null + && !loadedApk.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); - packageInfo = + loadedApk = 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()); + loadedApk.installSystemApplicationInfo(aInfo, + getSystemContext().mLoadedApk.getClassLoader()); } if (differentUser) { // Caching not supported across users } else if (includeCode) { mPackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(packageInfo)); + new WeakReference<LoadedApk>(loadedApk)); } else { mResourcePackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(packageInfo)); + new WeakReference<LoadedApk>(loadedApk)); } } - return packageInfo; + return loadedApk; } } @@ -1885,6 +2072,10 @@ public final class ActivityThread extends ClientTransactionHandler { return mLooper; } + public Executor getExecutor() { + return mExecutor; + } + public Application getApplication() { return mInitialApplication; } @@ -2259,6 +2450,167 @@ public final class ActivityThread extends ClientTransactionHandler { } } + /** + * 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) { + final long token = proto.start(fieldId); + + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.NAME, name); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean); + if (hasSwappedOutPss) { + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss); + } else { + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap); + } + + proto.end(token); + } + + /** + * Dump mem info data to proto. + */ + 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(MemInfoProto.ProcessMemory.NATIVE_HEAP); + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap", + memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty, + memInfo.nativePrivateDirty, memInfo.nativeSharedClean, + memInfo.nativePrivateClean, memInfo.hasSwappedOutPss, + memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree); + proto.end(nhToken); + + final long dvToken = proto.start(MemInfoProto.ProcessMemory.DALVIK_HEAP); + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap", + memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty, + memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean, + memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss, + memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated); + proto.write(MemInfoProto.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; + + 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); + if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0 + || mySharedClean != 0 || myPrivateClean != 0 + || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) { + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.OTHER_HEAPS, + Debug.MemoryInfo.getOtherLabel(i), + myPss, mySwappablePss, mySharedDirty, myPrivateDirty, + mySharedClean, myPrivateClean, + memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss); + + otherPss -= myPss; + otherSwappablePss -= mySwappablePss; + otherSharedDirty -= mySharedDirty; + otherPrivateDirty -= myPrivateDirty; + otherSharedClean -= mySharedClean; + otherPrivateClean -= myPrivateClean; + otherSwappedOut -= mySwappedOut; + otherSwappedOutPss -= mySwappedOutPss; + } + } + + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.UNKNOWN_HEAP, "Unknown", + otherPss, otherSwappablePss, + otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean, + memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss); + final long tToken = proto.start(MemInfoProto.ProcessMemory.TOTAL_HEAP); + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL", + memInfo.getTotalPss(), memInfo.getTotalSwappablePss(), + memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(), + memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(), + memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(), + memInfo.getTotalSwappedOutPss()); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, + nativeAllocated + dalvikAllocated); + proto.write(MemInfoProto.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); + if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0 + || mySharedClean != 0 || myPrivateClean != 0 + || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) { + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.DALVIK_DETAILS, + Debug.MemoryInfo.getOtherLabel(i), + myPss, mySwappablePss, mySharedDirty, myPrivateDirty, + mySharedClean, myPrivateClean, + memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss); + } + } + } + } + + final long asToken = proto.start(MemInfoProto.ProcessMemory.APP_SUMMARY); + proto.write(MemInfoProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB, + memInfo.getSummaryJavaHeap()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB, + memInfo.getSummaryNativeHeap()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB, + memInfo.getSummaryGraphics()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB, + memInfo.getSummaryPrivateOther()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB, + memInfo.getSummarySystem()); + if (memInfo.hasSwappedOutPss) { + proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, + memInfo.getSummaryTotalSwapPss()); + } else { + proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, + memInfo.getSummaryTotalSwap()); + } + proto.end(asToken); + } + public void registerOnActivityPausedListener(Activity activity, OnActivityPausedListener listener) { synchronized (mOnPauseListeners) { @@ -2316,13 +2668,21 @@ public final class ActivityThread extends ClientTransactionHandler { + ", comp=" + name + ", token=" + token); } - return performLaunchActivity(r, null); + // 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); } public final Activity getActivity(IBinder token) { return mActivities.get(token).activity; } + @Override + public ActivityClientRecord getActivityClient(IBinder token) { + return mActivities.get(token); + } + public final void sendActivityResult( IBinder token, String id, int requestCode, int resultCode, Intent data) { @@ -2330,8 +2690,8 @@ public final class ActivityThread extends ClientTransactionHandler { + " req=" + requestCode + " res=" + resultCode + " data=" + data); ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); list.add(new ResultInfo(id, requestCode, resultCode, data)); - final ClientTransaction clientTransaction = new ClientTransaction(mAppThread, token); - clientTransaction.addCallback(new ActivityResultItem(list)); + final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token); + clientTransaction.addCallback(ActivityResultItem.obtain(list)); try { mAppThread.scheduleTransaction(clientTransaction); } catch (RemoteException e) { @@ -2390,12 +2750,11 @@ public final class ActivityThread extends ClientTransactionHandler { sendMessage(H.CLEAN_UP_CONTEXT, cci); } - private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { - // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); - + /** Core implementation of activity launch. */ + private Activity performLaunchActivity(ActivityClientRecord r) { ActivityInfo aInfo = r.activityInfo; - if (r.packageInfo == null) { - r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, + if (r.loadedApk == null) { + r.loadedApk = getLoadedApk(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } @@ -2432,15 +2791,15 @@ public final class ActivityThread extends ClientTransactionHandler { } try { - Application app = r.packageInfo.makeApplication(false, mInstrumentation); + Application app = r.loadedApk.makeApplication(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() + + ", pkg=" + r.loadedApk.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() - + ", dir=" + r.packageInfo.getAppDir()); + + ", dir=" + r.loadedApk.getAppDir()); if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); @@ -2462,9 +2821,6 @@ public final class ActivityThread extends ClientTransactionHandler { r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback); - if (customIntent != null) { - activity.mIntent = customIntent; - } r.lastNonConfigurationInstances = null; checkAndBlockForNetworkAccess(); activity.mStartedActivity = false; @@ -2485,37 +2841,8 @@ public final class ActivityThread extends ClientTransactionHandler { " did not call through to super.onCreate()"); } r.activity = activity; - r.stopped = true; - if (!r.activity.mFinished) { - activity.performStart(); - r.stopped = false; - } - if (!r.activity.mFinished) { - 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); - } - } - if (!r.activity.mFinished) { - activity.mCalled = false; - if (r.isPersistable()) { - mInstrumentation.callActivityOnPostCreate(activity, r.state, - r.persistentState); - } else { - mInstrumentation.callActivityOnPostCreate(activity, r.state); - } - if (!activity.mCalled) { - throw new SuperNotCalledException( - "Activity " + r.intent.getComponent().toShortString() + - " did not call through to super.onPostCreate()"); - } - } } - r.paused = true; + r.setState(ON_CREATE); mActivities.put(r.token, r); @@ -2533,6 +2860,60 @@ public final class ActivityThread extends ClientTransactionHandler { return activity; } + @Override + public void handleStartActivity(ActivityClientRecord r, + PendingTransactionActions pendingActions) { + final Activity activity = r.activity; + if (r.activity == null) { + // TODO(lifecycler): What do we do in this case? + return; + } + 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; + } + + // Start + activity.performStart(); + 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; + if (r.isPersistable()) { + mInstrumentation.callActivityOnPostCreate(activity, r.state, + r.persistentState); + } else { + mInstrumentation.callActivityOnPostCreate(activity, r.state); + } + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPostCreate()"); + } + } + } + /** * 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 @@ -2558,7 +2939,7 @@ public final class ActivityThread extends ClientTransactionHandler { } ContextImpl appContext = ContextImpl.createActivityContext( - this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); + this, r.loadedApk, 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 @@ -2566,7 +2947,7 @@ public final class ActivityThread extends ClientTransactionHandler { // 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)) { + && r.loadedApk.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { if (id != Display.DEFAULT_DISPLAY) { Display display = @@ -2579,38 +2960,12 @@ public final class ActivityThread extends ClientTransactionHandler { return appContext; } + /** + * Extended implementation of activity launch. Used when server requests a launch or relaunch. + */ @Override - public void handleLaunchActivity(IBinder token, Intent intent, int ident, ActivityInfo info, - Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, - IVoiceInteractor voiceInteractor, Bundle state, PersistableBundle persistentState, - List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, - boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) { - ActivityClientRecord r = new ActivityClientRecord(); - - r.token = token; - r.ident = ident; - r.intent = intent; - r.referrer = referrer; - r.voiceInteractor = voiceInteractor; - r.activityInfo = info; - r.compatInfo = compatInfo; - r.state = state; - r.persistentState = persistentState; - - r.pendingResults = pendingResults; - r.pendingIntents = pendingNewIntents; - - r.startsNotResumed = notResumed; - r.isForward = isForward; - - r.profilerInfo = profilerInfo; - - r.overrideConfig = overrideConfig; - r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo); - handleLaunchActivity(r, null /* customIntent */, "LAUNCH_ACTIVITY"); - } - - private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { + public Activity handleLaunchActivity(ActivityClientRecord r, + PendingTransactionActions pendingActions) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); @@ -2633,44 +2988,28 @@ public final class ActivityThread extends ClientTransactionHandler { } WindowManagerGlobal.initialize(); - Activity a = performLaunchActivity(r, customIntent); + final Activity a = performLaunchActivity(r); if (a != null) { r.createdConfig = new Configuration(mConfiguration); reportSizeConfigurations(r); - Bundle oldState = r.state; - handleResumeActivity(r.token, false, r.isForward, - !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason); - - if (!r.activity.mFinished && r.startsNotResumed) { - // The activity manager actually wants this one to start out paused, because it - // needs to be visible but isn't in the foreground. We accomplish this by going - // through the normal startup (because activities expect to go through onResume() - // the first time they run, before their window is displayed), and then pausing it. - // However, in this case we do -not- need to do the full pause cycle (of freezing - // and such) because the activity manager assumes it can just retain the current - // state it has. - performPauseActivityIfNeeded(r, reason); - - // 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; - } + if (!r.activity.mFinished && pendingActions != null) { + pendingActions.setOldState(r.state); + pendingActions.setRestoreInstanceState(true); + pendingActions.setCallOnPostCreate(true); } } else { // If there was an error, for any reason, tell the activity manager to stop us. try { ActivityManager.getService() - .finishActivity(r.token, Activity.RESULT_CANCELED, null, - Activity.DONT_FINISH_TASK_WITH_ACTIVITY); + .finishActivity(r.token, Activity.RESULT_CANCELED, null, + Activity.DONT_FINISH_TASK_WITH_ACTIVITY); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } + + return a; } private void reportSizeConfigurations(ActivityClientRecord r) { @@ -2928,7 +3267,7 @@ public final class ActivityThread extends ClientTransactionHandler { String component = data.intent.getComponent().getClassName(); - LoadedApk packageInfo = getPackageInfoNoCheck( + LoadedApk loadedApk = getLoadedApkNoCheck( data.info.applicationInfo, data.compatInfo); IActivityManager mgr = ActivityManager.getService(); @@ -2937,7 +3276,7 @@ public final class ActivityThread extends ClientTransactionHandler { BroadcastReceiver receiver; ContextImpl context; try { - app = packageInfo.makeApplication(false, mInstrumentation); + app = loadedApk.makeApplication(false, mInstrumentation); context = (ContextImpl) app.getBaseContext(); if (data.info.splitName != null) { context = (ContextImpl) context.createContextForSplit(data.info.splitName); @@ -2946,7 +3285,8 @@ public final class ActivityThread extends ClientTransactionHandler { data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); - receiver = (BroadcastReceiver)cl.loadClass(component).newInstance(); + receiver = loadedApk.getAppFactory() + .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { if (DEBUG_BROADCAST) Slog.i(TAG, "Finishing failed broadcast to " + data.intent.getComponent()); @@ -2961,9 +3301,9 @@ public final class ActivityThread extends ClientTransactionHandler { TAG, "Performing receive of " + data.intent + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + packageInfo.getPackageName() + + ", pkg=" + loadedApk.getPackageName() + ", comp=" + data.intent.getComponent().toShortString() - + ", dir=" + packageInfo.getAppDir()); + + ", dir=" + loadedApk.getAppDir()); sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); @@ -3008,8 +3348,8 @@ public final class ActivityThread extends ClientTransactionHandler { unscheduleGcIdler(); // instantiate the BackupAgent class named in the manifest - LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); - String packageName = packageInfo.mPackageName; + LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + String packageName = loadedApk.mPackageName; if (packageName == null) { Slog.d(TAG, "Asked to create backup agent for nonexistent package"); return; @@ -3035,11 +3375,11 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); - java.lang.ClassLoader cl = packageInfo.getClassLoader(); + java.lang.ClassLoader cl = loadedApk.getClassLoader(); agent = (BackupAgent) cl.loadClass(classname).newInstance(); // set up the agent's context - ContextImpl context = ContextImpl.createAppContext(this, packageInfo); + ContextImpl context = ContextImpl.createAppContext(this, loadedApk); context.setOuterContext(agent); agent.attach(context); @@ -3075,8 +3415,8 @@ public final class ActivityThread extends ClientTransactionHandler { private void handleDestroyBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); - LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); - String packageName = packageInfo.mPackageName; + LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + String packageName = loadedApk.mPackageName; BackupAgent agent = mBackupAgents.get(packageName); if (agent != null) { try { @@ -3096,12 +3436,13 @@ public final class ActivityThread extends ClientTransactionHandler { // we are back active so skip it. unscheduleGcIdler(); - LoadedApk packageInfo = getPackageInfoNoCheck( + LoadedApk loadedApk = getLoadedApkNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { - java.lang.ClassLoader cl = packageInfo.getClassLoader(); - service = (Service) cl.loadClass(data.info.name).newInstance(); + java.lang.ClassLoader cl = loadedApk.getClassLoader(); + service = loadedApk.getAppFactory() + .instantiateService(cl, data.info.name, data.intent); } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { throw new RuntimeException( @@ -3113,10 +3454,10 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); - ContextImpl context = ContextImpl.createAppContext(this, packageInfo); + ContextImpl context = ContextImpl.createAppContext(this, loadedApk); context.setOuterContext(service); - Application app = packageInfo.makeApplication(false, mInstrumentation); + Application app = loadedApk.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); @@ -3314,8 +3655,7 @@ public final class ActivityThread extends ClientTransactionHandler { //Slog.i(TAG, "Running services: " + mServices); } - public final ActivityClientRecord performResumeActivity(IBinder token, - boolean clearHide, String reason) { + ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide, String reason) { ActivityClientRecord r = mActivities.get(token); if (localLOGV) Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); @@ -3355,10 +3695,9 @@ public final class ActivityThread extends ClientTransactionHandler { EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName(), reason); - r.paused = false; - r.stopped = false; r.state = null; r.persistentState = null; + r.setState(ON_RESUME); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( @@ -3390,19 +3729,14 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, - boolean reallyResume, int seq, String reason) { - ActivityClientRecord r = mActivities.get(token); - if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) { - return; - } - + 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 - r = performResumeActivity(token, clearHide, reason); + final ActivityClientRecord r = performResumeActivity(token, clearHide, reason); if (r != null) { final Activity a = r.activity; @@ -3514,16 +3848,6 @@ public final class ActivityThread extends ClientTransactionHandler { Looper.myQueue().addIdleHandler(new Idler()); } r.onlyLocalRequest = false; - - // Tell the activity manager we have resumed. - if (reallyResume) { - try { - ActivityManager.getService().activityResumed(token); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - } else { // If an exception was thrown when trying to resume, then // just end this activity. @@ -3594,21 +3918,17 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override - public void handlePauseActivity(IBinder token, boolean finished, - boolean userLeaving, int configChanges, boolean dontReport, int seq) { + public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, + int configChanges, boolean dontReport, PendingTransactionActions pendingActions) { ActivityClientRecord r = mActivities.get(token); - if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq); - if (!checkAndUpdateLifecycleSeq(seq, r, "pauseActivity")) { - return; - } if (r != null) { - //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r); if (userLeaving) { performUserLeavingActivity(r); } r.activity.mConfigChangeFlags |= configChanges; - performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity"); + performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity", + pendingActions); // Make sure any pending writes are now committed. if (r.isPreHoneycomb()) { @@ -3632,13 +3952,15 @@ public final class ActivityThread extends ClientTransactionHandler { } final Bundle performPauseActivity(IBinder token, boolean finished, - boolean saveState, String reason) { + boolean saveState, String reason, PendingTransactionActions pendingActions) { ActivityClientRecord r = mActivities.get(token); - return r != null ? performPauseActivity(r, finished, saveState, reason) : null; + return r != null + ? performPauseActivity(r, finished, saveState, reason, pendingActions) + : null; } - final Bundle performPauseActivity(ActivityClientRecord r, boolean finished, - boolean saveState, String reason) { + private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, boolean saveState, + String reason, PendingTransactionActions pendingActions) { if (r.paused) { if (r.activity.mFinished) { // If we are finishing, we won't call onResume() in certain cases. @@ -3672,6 +3994,18 @@ public final class ActivityThread extends ClientTransactionHandler { 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 !r.activity.mFinished && saveState ? r.state : null; } @@ -3698,7 +4032,7 @@ public final class ActivityThread extends ClientTransactionHandler { + safeToComponentShortString(r.intent) + ": " + e.toString(), e); } } - r.paused = true; + r.setState(ON_PAUSE); } final void performStopActivity(IBinder token, boolean saveState, String reason) { @@ -3706,37 +4040,6 @@ public final class ActivityThread extends ClientTransactionHandler { performStopActivityInner(r, null, false, saveState, reason); } - private static class StopInfo implements Runnable { - ActivityClientRecord activity; - Bundle state; - PersistableBundle persistentState; - CharSequence description; - - @Override public void run() { - // Tell activity manager we have been stopped. - try { - if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity); - ActivityManager.getService().activityStopped( - activity.token, state, persistentState, description); - } catch (RemoteException ex) { - // Dump statistics about bundle to help developers debug - final LogWriter writer = new LogWriter(Log.WARN, TAG); - final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println("Bundle stats:"); - Bundle.dumpStats(pw, state); - pw.println("PersistableBundle stats:"); - Bundle.dumpStats(pw, persistentState); - - if (ex instanceof TransactionTooLargeException - && activity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { - Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); - return; - } - throw ex.rethrowFromSystemServer(); - } - } - } - private static final class ProviderRefCount { public final ContentProviderHolder holder; public final ProviderClientRecord client; @@ -3767,8 +4070,8 @@ public final class ActivityThread extends ClientTransactionHandler { * For the client, we want to call onStop()/onStart() to indicate when * the activity's UI visibility changes. */ - private void performStopActivityInner(ActivityClientRecord r, - StopInfo info, boolean keepShown, boolean saveState, String reason) { + private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown, + boolean saveState, String reason) { if (localLOGV) Slog.v(TAG, "Performing stop of " + r); if (r != null) { if (!keepShown && r.stopped) { @@ -3793,7 +4096,7 @@ public final class ActivityThread extends ClientTransactionHandler { // 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.description = r.activity.onCreateDescription(); + info.setDescription(r.activity.onCreateDescription()); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( @@ -3823,7 +4126,7 @@ public final class ActivityThread extends ClientTransactionHandler { + ": " + e.toString(), e); } } - r.stopped = true; + r.setState(ON_STOP); EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName(), reason); } @@ -3859,15 +4162,13 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override - public void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) { - ActivityClientRecord r = mActivities.get(token); - if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) { - return; - } + public void handleStopActivity(IBinder token, boolean show, int configChanges, + PendingTransactionActions pendingActions) { + final ActivityClientRecord r = mActivities.get(token); r.activity.mConfigChangeFlags |= configChanges; - StopInfo info = new StopInfo(); - performStopActivityInner(r, info, show, true, "handleStopActivity"); + final StopInfo stopInfo = new StopInfo(); + performStopActivityInner(r, stopInfo, show, true, "handleStopActivity"); if (localLOGV) Slog.v( TAG, "Finishing stop of " + r + ": show=" + show @@ -3880,37 +4181,32 @@ public final class ActivityThread extends ClientTransactionHandler { QueuedWork.waitToFinish(); } - // 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. - info.activity = r; - info.state = r.state; - info.persistentState = r.persistentState; - mH.post(info); + stopInfo.setActivity(r); + stopInfo.setState(r.state); + stopInfo.setPersistentState(r.persistentState); + pendingActions.setStopInfo(stopInfo); mSomeActivitiesChanged = true; } - private static boolean checkAndUpdateLifecycleSeq(int seq, ActivityClientRecord r, - String action) { - if (r == null) { - return true; - } - if (seq < r.lastProcessedSeq) { - if (DEBUG_ORDER) Slog.d(TAG, action + " for " + r + " ignored, because seq=" + seq - + " < mCurrentLifecycleSeq=" + r.lastProcessedSeq); - return false; - } - r.lastProcessedSeq = seq; - return 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()); } - final void performRestartActivity(IBinder token) { + @Override + public void performRestartActivity(IBinder token, boolean start) { ActivityClientRecord r = mActivities.get(token); if (r.stopped) { - r.activity.performRestart(); - r.stopped = false; + r.activity.performRestart(start); + if (start) { + r.setState(ON_START); + } } } @@ -3930,8 +4226,8 @@ public final class ActivityThread extends ClientTransactionHandler { // we are back active so skip it. unscheduleGcIdler(); - r.activity.performRestart(); - r.stopped = false; + r.activity.performRestart(true /* start */); + r.setState(ON_START); } if (r.activity.mDecor != null) { if (false) Slog.v( @@ -3969,7 +4265,7 @@ public final class ActivityThread extends ClientTransactionHandler { + ": " + e.toString(), e); } } - r.stopped = true; + r.setState(ON_STOP); EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName(), "sleeping"); } @@ -3987,8 +4283,8 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { if (r.stopped && r.activity.mVisibleFromServer) { - r.activity.performRestart(); - r.stopped = false; + r.activity.performRestart(true /* start */); + r.setState(ON_START); } } } @@ -4025,11 +4321,11 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { - LoadedApk apk = peekPackageInfo(data.pkg, false); + LoadedApk apk = peekLoadedApk(data.pkg, false); if (apk != null) { apk.setCompatibilityInfo(data.info); } - apk = peekPackageInfo(data.pkg, true); + apk = peekLoadedApk(data.pkg, true); if (apk != null) { apk.setCompatibilityInfo(data.info); } @@ -4105,11 +4401,8 @@ public final class ActivityThread extends ClientTransactionHandler { } } - public final ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing) { - return performDestroyActivity(token, finishing, 0, false); - } - - private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, + /** Core implementation of activity destroy call. */ + ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = mActivities.get(token); Class<? extends Activity> activityClass = null; @@ -4136,7 +4429,7 @@ public final class ActivityThread extends ClientTransactionHandler { + ": " + e.toString(), e); } } - r.stopped = true; + r.setState(ON_STOP); EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName(), "destroy"); } @@ -4173,6 +4466,7 @@ public final class ActivityThread extends ClientTransactionHandler { + ": " + e.toString(), e); } } + r.setState(ON_DESTROY); } mActivities.remove(token); StrictMode.decrementExpectedActivityCount(activityClass); @@ -4333,10 +4627,7 @@ public final class ActivityThread extends ClientTransactionHandler { target.overrideConfig = overrideConfig; } target.pendingConfigChanges |= configChanges; - target.relaunchSeq = getLifecycleSeq(); } - if (DEBUG_ORDER) Slog.d(TAG, "relaunchActivity " + ActivityThread.this + ", target " - + target + " operation received seq: " + target.relaunchSeq); } private void handleRelaunchActivity(ActivityClientRecord tmp) { @@ -4381,12 +4672,6 @@ public final class ActivityThread extends ClientTransactionHandler { } } - if (tmp.lastProcessedSeq > tmp.relaunchSeq) { - Slog.wtf(TAG, "For some reason target: " + tmp + " has lower sequence: " - + tmp.relaunchSeq + " than current sequence: " + tmp.lastProcessedSeq); - } else { - tmp.lastProcessedSeq = tmp.relaunchSeq; - } 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 @@ -4427,9 +4712,6 @@ public final class ActivityThread extends ClientTransactionHandler { r.activity.mConfigChangeFlags |= configChanges; r.onlyLocalRequest = tmp.onlyLocalRequest; r.mPreserveWindow = tmp.mPreserveWindow; - r.lastProcessedSeq = tmp.lastProcessedSeq; - r.relaunchSeq = tmp.relaunchSeq; - Intent currentIntent = r.activity.mIntent; r.activity.mChangingConfigurations = true; @@ -4455,7 +4737,8 @@ public final class ActivityThread extends ClientTransactionHandler { // Need to ensure state is saved. if (!r.paused) { - performPauseActivity(r.token, false, r.isPreHoneycomb(), "handleRelaunchActivity"); + performPauseActivity(r.token, false, r.isPreHoneycomb(), "handleRelaunchActivity", + null /* pendingActions */); } if (r.state == null && !r.stopped && !r.isPreHoneycomb()) { callCallActivityOnSaveInstanceState(r); @@ -4485,7 +4768,15 @@ public final class ActivityThread extends ClientTransactionHandler { r.startsNotResumed = tmp.startsNotResumed; r.overrideConfig = tmp.overrideConfig; - handleLaunchActivity(r, currentIntent, "handleRelaunchActivity"); + // TODO(lifecycler): Move relaunch to lifecycler. + PendingTransactionActions pendingActions = new PendingTransactionActions(); + handleLaunchActivity(r, pendingActions); + handleStartActivity(r, pendingActions); + handleResumeActivity(r.token, false /* clearHide */, r.isForward, "relaunch"); + if (r.startsNotResumed) { + performPauseActivity(r, false /* finished */, r.isPreHoneycomb(), "relaunch", + pendingActions); + } if (!tmp.onlyLocalRequest) { try { @@ -4528,7 +4819,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (a != null) { Configuration thisConfig = applyConfigCompatMainThread( mCurDefaultDisplayDpi, newConfig, - ar.packageInfo.getCompatibilityInfo()); + ar.loadedApk.getCompatibilityInfo()); if (!ar.activity.mFinished && (allActivities || !ar.paused)) { // If the activity is currently resumed, its configuration // needs to change right now. @@ -5014,7 +5305,7 @@ public final class ActivityThread extends ClientTransactionHandler { } final void handleDispatchPackageBroadcast(int cmd, String[] packages) { - boolean hasPkgInfo = false; + boolean hasLoadedApk = false; switch (cmd) { case ApplicationThreadConstants.PACKAGE_REMOVED: case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL: @@ -5025,14 +5316,14 @@ public final class ActivityThread extends ClientTransactionHandler { } synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { - if (!hasPkgInfo) { + if (!hasLoadedApk) { WeakReference<LoadedApk> ref = mPackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasPkgInfo = true; + hasLoadedApk = true; } else { ref = mResourcePackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasPkgInfo = true; + hasLoadedApk = true; } } } @@ -5052,21 +5343,21 @@ public final class ActivityThread extends ClientTransactionHandler { synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { WeakReference<LoadedApk> ref = mPackages.get(packages[i]); - LoadedApk pkgInfo = ref != null ? ref.get() : null; - if (pkgInfo != null) { - hasPkgInfo = true; + LoadedApk loadedApk = ref != null ? ref.get() : null; + if (loadedApk != null) { + hasLoadedApk = true; } else { ref = mResourcePackages.get(packages[i]); - pkgInfo = ref != null ? ref.get() : null; - if (pkgInfo != null) { - hasPkgInfo = true; + loadedApk = ref != null ? ref.get() : null; + if (loadedApk != null) { + hasLoadedApk = 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) { + if (loadedApk != null) { try { final String packageName = packages[i]; final ApplicationInfo aInfo = @@ -5080,13 +5371,13 @@ public final class ActivityThread extends ClientTransactionHandler { if (ar.activityInfo.applicationInfo.packageName .equals(packageName)) { ar.activityInfo.applicationInfo = aInfo; - ar.packageInfo = pkgInfo; + ar.loadedApk = loadedApk; } } } final List<String> oldPaths = sPackageManager.getPreviousCodePaths(packageName); - pkgInfo.updateApplicationInfo(aInfo, oldPaths); + loadedApk.updateApplicationInfo(aInfo, oldPaths); } catch (RemoteException e) { } } @@ -5095,7 +5386,7 @@ public final class ActivityThread extends ClientTransactionHandler { break; } } - ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); + ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasLoadedApk); } final void handleLowMemory() { @@ -5299,7 +5590,7 @@ public final class ActivityThread extends ClientTransactionHandler { applyCompatConfiguration(mCurDefaultDisplayDpi); } - data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); /** * Switch this process to density compatibility mode if needed. @@ -5343,7 +5634,7 @@ public final class ActivityThread extends ClientTransactionHandler { // XXX should have option to change the port. Debug.changeDebugPort(8100); if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) { - Slog.w(TAG, "Application " + data.info.getPackageName() + Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + " is waiting for the debugger on port 8100..."); IActivityManager mgr = ActivityManager.getService(); @@ -5362,7 +5653,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { - Slog.w(TAG, "Application " + data.info.getPackageName() + Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + " can be debugged on port 8100..."); } } @@ -5410,14 +5701,14 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentationAppDir = ii.sourceDir; mInstrumentationSplitAppDirs = ii.splitSourceDirs; mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii); - mInstrumentedAppDir = data.info.getAppDir(); - mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); - mInstrumentedLibDir = data.info.getLibDir(); + mInstrumentedAppDir = data.loadedApk.getAppDir(); + mInstrumentedSplitAppDirs = data.loadedApk.getSplitAppDirs(); + mInstrumentedLibDir = data.loadedApk.getLibDir(); } else { ii = null; } - final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); + final ContextImpl appContext = ContextImpl.createAppContext(this, data.loadedApk); updateLocaleListFromAppContext(appContext, mResourcesManager.getConfiguration().getLocales()); @@ -5449,12 +5740,21 @@ public final class ActivityThread extends ClientTransactionHandler { // Continue loading instrumentation. if (ii != null) { - final ApplicationInfo instrApp = new ApplicationInfo(); + 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, + final LoadedApk loadedApk = getLoadedApk(instrApp, data.compatInfo, appContext.getClassLoader(), false, true, false); - final ContextImpl instrContext = ContextImpl.createAppContext(this, pi); + final ContextImpl instrContext = ContextImpl.createAppContext(this, loadedApk); try { final ClassLoader cl = instrContext.getClassLoader(); @@ -5479,6 +5779,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { mInstrumentation = new Instrumentation(); + mInstrumentation.basicInit(this); } if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) { @@ -5498,7 +5799,7 @@ public final class ActivityThread extends ClientTransactionHandler { 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.makeApplication(data.restrictedBackupMode, null); + app = data.loadedApk.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; // don't bring up providers in restricted mode; they may depend on the @@ -5552,7 +5853,7 @@ public final class ActivityThread extends ClientTransactionHandler { final int preloadedFontsResource = info.metaData.getInt( ApplicationInfo.METADATA_PRELOADED_FONTS, 0); if (preloadedFontsResource != 0) { - data.info.getResources().preloadFonts(preloadedFontsResource); + data.loadedApk.getResources().preloadFonts(preloadedFontsResource); } } } catch (RemoteException e) { @@ -6010,8 +6311,13 @@ public final class ActivityThread extends ClientTransactionHandler { try { final java.lang.ClassLoader cl = c.getClassLoader(); - localProvider = (ContentProvider)cl. - loadClass(info.name).newInstance(); + LoadedApk loadedApk = peekLoadedApk(ai.packageName, true); + if (loadedApk == null) { + // System startup case. + loadedApk = getSystemContext().mLoadedApk; + } + localProvider = loadedApk.getAppFactory() + .instantiateProvider(cl, info.name); provider = localProvider.getIContentProvider(); if (provider == null) { Slog.e(TAG, "Failed to instantiate class " + @@ -6109,7 +6415,7 @@ public final class ActivityThread extends ClientTransactionHandler { System.exit(0); } - private void attach(boolean system) { + private void attach(boolean system, long startSeq) { sCurrentActivityThread = this; mSystemThread = system; if (!system) { @@ -6124,7 +6430,7 @@ public final class ActivityThread extends ClientTransactionHandler { RuntimeInit.setApplicationObject(mAppThread.asBinder()); final IActivityManager mgr = ActivityManager.getService(); try { - mgr.attachApplication(mAppThread); + mgr.attachApplication(mAppThread, startSeq); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -6157,9 +6463,10 @@ public final class ActivityThread extends ClientTransactionHandler { UserHandle.myUserId()); try { mInstrumentation = new Instrumentation(); + mInstrumentation.basicInit(this); ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mPackageInfo); - mInitialApplication = context.mPackageInfo.makeApplication(true, null); + this, getSystemContext().mLoadedApk); + mInitialApplication = context.mLoadedApk.makeApplication(true, null); mInitialApplication.onCreate(); } catch (Exception e) { throw new RuntimeException( @@ -6202,7 +6509,7 @@ public final class ActivityThread extends ClientTransactionHandler { ThreadedRenderer.enableForegroundTrimming(); } ActivityThread thread = new ActivityThread(); - thread.attach(true); + thread.attach(true, 0); return thread; } @@ -6274,8 +6581,19 @@ public final class ActivityThread extends ClientTransactionHandler { Looper.prepareMainLooper(); + // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line. + // It will be in the format "seq=114" + long startSeq = 0; + if (args != null) { + for (int i = args.length - 1; i >= 0; --i) { + if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) { + startSeq = Long.parseLong( + args[i].substring(PROC_START_SEQ_IDENT.length())); + } + } + } ActivityThread thread = new ActivityThread(); - thread.attach(false); + thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java index 55f9e289..382719b4 100644 --- a/android/app/AlarmManager.java +++ b/android/app/AlarmManager.java @@ -568,9 +568,21 @@ public class AlarmManager { } /** - * Schedule an alarm that represents an alarm clock. + * Schedule an alarm that represents an alarm clock, which will be used to notify the user + * when it goes off. The expectation is that when this alarm triggers, the application will + * further wake up the device to tell the user about the alarm -- turning on the screen, + * playing a sound, vibrating, etc. As such, the system will typically also use the + * information supplied here to tell the user about this upcoming alarm if appropriate. * - * The system may choose to display information about this alarm to the user. + * <p>Due to the nature of this kind of alarm, similar to {@link #setExactAndAllowWhileIdle}, + * these alarms will be allowed to trigger even if the system is in a low-power idle + * (a.k.a. doze) mode. The system may also do some prep-work when it sees that such an + * alarm coming up, to reduce the amount of background work that could happen if this + * causes the device to fully wake up -- this is to avoid situations such as a large number + * of devices having an alarm set at the same time in the morning, all waking up at that + * time and suddenly swamping the network with pending background work. As such, these + * types of alarms can be extremely expensive on battery use and should only be used for + * their intended purpose.</p> * * <p> * This method is like {@link #setExact(int, long, PendingIntent)}, but implies @@ -783,9 +795,9 @@ public class AlarmManager { /** * Like {@link #set(int, long, PendingIntent)}, but this alarm will be allowed to execute - * even when the system is in low-power idle modes. This type of alarm must <b>only</b> - * be used for situations where it is actually required that the alarm go off while in - * idle -- a reasonable example would be for a calendar notification that should make a + * even when the system is in low-power idle (a.k.a. doze) modes. This type of alarm must + * <b>only</b> be used for situations where it is actually required that the alarm go off while + * in idle -- a reasonable example would be for a calendar notification that should make a * sound so the user is aware of it. When the alarm is dispatched, the app will also be * added to the system's temporary whitelist for approximately 10 seconds to allow that * application to acquire further wake locks in which to complete its work.</p> diff --git a/android/app/AppComponentFactory.java b/android/app/AppComponentFactory.java new file mode 100644 index 00000000..4df73799 --- /dev/null +++ b/android/app/AppComponentFactory.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.ContentProvider; +import android.content.Intent; + +/** + * Interface used to control the instantiation of manifest elements. + * + * @see #instantiateApplication + * @see #instantiateActivity + * @see #instantiateService + * @see #instantiateReceiver + * @see #instantiateProvider + */ +public class AppComponentFactory { + + /** + * Allows application to override the creation of the application object. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + */ + public @NonNull Application instantiateApplication(@NonNull ClassLoader cl, + @NonNull String className) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Application) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of activities. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + */ + public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, + @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Activity) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of receivers. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + */ + public @NonNull BroadcastReceiver instantiateReceiver(@NonNull ClassLoader cl, + @NonNull String className, @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (BroadcastReceiver) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of services. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + * @param intent Intent creating the class. + */ + public @NonNull Service instantiateService(@NonNull ClassLoader cl, + @NonNull String className, @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Service) cl.loadClass(className).newInstance(); + } + + /** + * Allows application to override the creation of providers. This can be used to + * perform things such as dependency injection or class loader changes to these + * classes. + * + * @param cl The default classloader to use for instantiation. + * @param className The class to be instantiated. + */ + public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl, + @NonNull String className) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (ContentProvider) cl.loadClass(className).newInstance(); + } + + /** + * @hide + */ + public static AppComponentFactory DEFAULT = new AppComponentFactory(); +} diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java index b6fb1201..ea22d332 100644 --- a/android/app/AppOpsManager.java +++ b/android/app/AppOpsManager.java @@ -160,7 +160,7 @@ public class AppOpsManager { public static final int OP_WRITE_ICC_SMS = 22; /** @hide */ public static final int OP_WRITE_SETTINGS = 23; - /** @hide */ + /** @hide Required to draw on top of other apps. */ public static final int OP_SYSTEM_ALERT_WINDOW = 24; /** @hide */ public static final int OP_ACCESS_NOTIFICATIONS = 25; @@ -256,8 +256,12 @@ public class AppOpsManager { public static final int OP_RUN_ANY_IN_BACKGROUND = 70; /** @hide Change Wi-Fi connectivity state */ public static final int OP_CHANGE_WIFI_STATE = 71; + /** @hide Request package deletion through package installer */ + public static final int OP_REQUEST_DELETE_PACKAGES = 72; + /** @hide Bind an accessibility service. */ + public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73; /** @hide */ - public static final int _NUM_OP = 72; + public static final int _NUM_OP = 74; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -410,6 +414,7 @@ public class AppOpsManager { OP_CAMERA, // Body sensors OP_BODY_SENSORS, + OP_REQUEST_DELETE_PACKAGES, // APPOP PERMISSIONS OP_ACCESS_NOTIFICATIONS, @@ -499,6 +504,8 @@ public class AppOpsManager { OP_ANSWER_PHONE_CALLS, OP_RUN_ANY_IN_BACKGROUND, OP_CHANGE_WIFI_STATE, + OP_REQUEST_DELETE_PACKAGES, + OP_BIND_ACCESSIBILITY_SERVICE, }; /** @@ -578,6 +585,8 @@ public class AppOpsManager { OPSTR_ANSWER_PHONE_CALLS, null, // OP_RUN_ANY_IN_BACKGROUND null, // OP_CHANGE_WIFI_STATE + null, // OP_REQUEST_DELETE_PACKAGES + null, // OP_BIND_ACCESSIBILITY_SERVICE }; /** @@ -657,6 +666,8 @@ public class AppOpsManager { "ANSWER_PHONE_CALLS", "RUN_ANY_IN_BACKGROUND", "CHANGE_WIFI_STATE", + "REQUEST_DELETE_PACKAGES", + "BIND_ACCESSIBILITY_SERVICE", }; /** @@ -736,6 +747,8 @@ public class AppOpsManager { Manifest.permission.ANSWER_PHONE_CALLS, null, // no permission for OP_RUN_ANY_IN_BACKGROUND Manifest.permission.CHANGE_WIFI_STATE, + Manifest.permission.REQUEST_DELETE_PACKAGES, + Manifest.permission.BIND_ACCESSIBILITY_SERVICE, }; /** @@ -816,6 +829,8 @@ public class AppOpsManager { null, // ANSWER_PHONE_CALLS null, // OP_RUN_ANY_IN_BACKGROUND null, // OP_CHANGE_WIFI_STATE + null, // REQUEST_DELETE_PACKAGES + null, // OP_BIND_ACCESSIBILITY_SERVICE }; /** @@ -895,6 +910,8 @@ public class AppOpsManager { false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND false, // OP_CHANGE_WIFI_STATE + false, // OP_REQUEST_DELETE_PACKAGES + false, // OP_BIND_ACCESSIBILITY_SERVICE }; /** @@ -973,6 +990,8 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE + AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES + AppOpsManager.MODE_ALLOWED, // OP_BIND_ACCESSIBILITY_SERVICE }; /** @@ -1055,6 +1074,8 @@ public class AppOpsManager { false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND false, // OP_CHANGE_WIFI_STATE + false, // OP_REQUEST_DELETE_PACKAGES + false, // OP_BIND_ACCESSIBILITY_SERVICE }; /** diff --git a/android/app/Application.java b/android/app/Application.java index 156df36a..5822f5c8 100644 --- a/android/app/Application.java +++ b/android/app/Application.java @@ -187,7 +187,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { */ /* package */ final void attach(Context context) { attachBaseContext(context); - mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; + mLoadedApk = ContextImpl.getImpl(context).mLoadedApk; } /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) { diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java index 005b7c38..8641a21a 100644 --- a/android/app/ApplicationPackageManager.java +++ b/android/app/ApplicationPackageManager.java @@ -55,6 +55,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; +import android.content.pm.dex.ArtManager; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Bitmap; @@ -121,6 +122,8 @@ public class ApplicationPackageManager extends PackageManager { private UserManager mUserManager; @GuardedBy("mLock") private PackageInstaller mInstaller; + @GuardedBy("mLock") + private ArtManager mArtManager; @GuardedBy("mDelegates") private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>(); @@ -1378,7 +1381,7 @@ public class ApplicationPackageManager extends PackageManager { sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - mContext.mPackageInfo); + mContext.mLoadedApk); if (r != null) { return r; } @@ -2750,4 +2753,18 @@ public class ApplicationPackageManager extends PackageManager { throw e.rethrowAsRuntimeException(); } } + + @Override + public ArtManager getArtManager() { + synchronized (mLock) { + if (mArtManager == null) { + try { + mArtManager = new ArtManager(mPM.getArtManager()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mArtManager; + } + } } diff --git a/android/app/ClientTransactionHandler.java b/android/app/ClientTransactionHandler.java index f7f4c716..45c0e0cd 100644 --- a/android/app/ClientTransactionHandler.java +++ b/android/app/ClientTransactionHandler.java @@ -16,15 +16,12 @@ package android.app; import android.app.servertransaction.ClientTransaction; -import android.content.Intent; -import android.content.pm.ActivityInfo; +import android.app.servertransaction.PendingTransactionActions; +import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.os.Bundle; import android.os.IBinder; -import android.os.PersistableBundle; -import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; import java.util.List; @@ -40,7 +37,7 @@ public abstract class ClientTransactionHandler { /** Prepare and schedule transaction for execution. */ void scheduleTransaction(ClientTransaction transaction) { - transaction.prepare(this); + transaction.preExecute(this); sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction); } @@ -50,9 +47,6 @@ public abstract class ClientTransactionHandler { // Prepare phase related logic and handlers. Methods that inform about about pending changes or // do other internal bookkeeping. - /** Get current lifecycle request number to maintain correct ordering. */ - public abstract int getLifecycleSeq(); - /** Set pending config in case it will be updated by other transaction item. */ public abstract void updatePendingConfiguration(Configuration config); @@ -69,15 +63,21 @@ public abstract class ClientTransactionHandler { /** Pause the activity. */ public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, - int configChanges, boolean dontReport, int seq); + int configChanges, boolean dontReport, PendingTransactionActions pendingActions); /** Resume the activity. */ public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, - boolean reallyResume, int seq, String reason); + String reason); /** Stop the activity. */ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges, - int seq); + PendingTransactionActions pendingActions); + + /** Report that activity was stopped to server. */ + public abstract void reportStop(PendingTransactionActions pendingActions); + + /** Restart the activity after it was stopped. */ + public abstract void performRestartActivity(IBinder token, boolean start); /** Deliver activity (override) configuration change. */ public abstract void handleActivityConfigurationChanged(IBinder activityToken, @@ -102,13 +102,23 @@ public abstract class ClientTransactionHandler { public abstract void handleWindowVisibility(IBinder token, boolean show); /** Perform activity launch. */ - public abstract void handleLaunchActivity(IBinder token, Intent intent, int ident, - ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo, - String referrer, IVoiceInteractor voiceInteractor, Bundle state, - PersistableBundle persistentState, List<ResultInfo> pendingResults, - List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, - ProfilerInfo profilerInfo); + public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r, + PendingTransactionActions pendingActions); + + /** Perform activity start. */ + public abstract void handleStartActivity(ActivityThread.ActivityClientRecord r, + PendingTransactionActions pendingActions); + + /** Get package info. */ + public abstract LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + CompatibilityInfo compatInfo); /** Deliver app configuration change notification. */ public abstract void handleConfigurationChanged(Configuration config); + + /** + * Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the + * provided token. + */ + public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token); } diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java index b0d020a7..16534305 100644 --- a/android/app/ContextImpl.java +++ b/android/app/ContextImpl.java @@ -90,6 +90,7 @@ import java.io.InputStream; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Objects; +import java.util.concurrent.Executor; class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { @@ -158,7 +159,7 @@ class ContextImpl extends Context { private ArrayMap<String, File> mSharedPrefsPaths; final @NonNull ActivityThread mMainThread; - final @NonNull LoadedApk mPackageInfo; + final @NonNull LoadedApk mLoadedApk; private @Nullable ClassLoader mClassLoader; private final @Nullable IBinder mActivityToken; @@ -250,9 +251,14 @@ class ContextImpl extends Context { } @Override + public Executor getMainExecutor() { + return mMainThread.getExecutor(); + } + + @Override public Context getApplicationContext() { - return (mPackageInfo != null) ? - mPackageInfo.getApplication() : mMainThread.getApplication(); + return (mLoadedApk != null) ? + mLoadedApk.getApplication() : mMainThread.getApplication(); } @Override @@ -296,15 +302,15 @@ class ContextImpl extends Context { @Override public ClassLoader getClassLoader() { - return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader()); + return mClassLoader != null ? mClassLoader : (mLoadedApk != null ? mLoadedApk.getClassLoader() : ClassLoader.getSystemClassLoader()); } @Override public String getPackageName() { - if (mPackageInfo != null) { - return mPackageInfo.getPackageName(); + if (mLoadedApk != null) { + return mLoadedApk.getPackageName(); } - // No mPackageInfo means this is a Context for the system itself, + // No mLoadedApk means this is a Context for the system itself, // and this here is its name. return "android"; } @@ -323,24 +329,24 @@ class ContextImpl extends Context { @Override public ApplicationInfo getApplicationInfo() { - if (mPackageInfo != null) { - return mPackageInfo.getApplicationInfo(); + if (mLoadedApk != null) { + return mLoadedApk.getApplicationInfo(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageResourcePath() { - if (mPackageInfo != null) { - return mPackageInfo.getResDir(); + if (mLoadedApk != null) { + return mLoadedApk.getResDir(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageCodePath() { - if (mPackageInfo != null) { - return mPackageInfo.getAppDir(); + if (mLoadedApk != null) { + return mLoadedApk.getAppDir(); } throw new RuntimeException("Not supported in system context"); } @@ -350,7 +356,7 @@ class ContextImpl extends Context { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. - if (mPackageInfo.getApplicationInfo().targetSdkVersion < + if (mLoadedApk.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; @@ -1092,11 +1098,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mPackageInfo != null) { + if (mLoadedApk != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mPackageInfo.getReceiverDispatcher( + rd = mLoadedApk.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1196,11 +1202,11 @@ class ContextImpl extends Context { Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mPackageInfo != null) { + if (mLoadedApk != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mPackageInfo.getReceiverDispatcher( + rd = mLoadedApk.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1250,11 +1256,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mPackageInfo != null) { + if (mLoadedApk != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mPackageInfo.getReceiverDispatcher( + rd = mLoadedApk.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1332,11 +1338,11 @@ class ContextImpl extends Context { Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mPackageInfo != null) { + if (mLoadedApk != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mPackageInfo.getReceiverDispatcher( + rd = mLoadedApk.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1413,11 +1419,11 @@ class ContextImpl extends Context { Handler scheduler, Context context, int flags) { IIntentReceiver rd = null; if (receiver != null) { - if (mPackageInfo != null && context != null) { + if (mLoadedApk != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mPackageInfo.getReceiverDispatcher( + rd = mLoadedApk.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { @@ -1444,8 +1450,8 @@ class ContextImpl extends Context { @Override public void unregisterReceiver(BroadcastReceiver receiver) { - if (mPackageInfo != null) { - IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher( + if (mLoadedApk != null) { + IIntentReceiver rd = mLoadedApk.forgetReceiverDispatcher( getOuterContext(), receiver); try { ActivityManager.getService().unregisterReceiver(rd); @@ -1578,7 +1584,7 @@ class ContextImpl extends Context { @Override public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, int flags) { - return mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); + return mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); } /** @hide */ @@ -1600,16 +1606,16 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mPackageInfo != null) { - sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); + if (mLoadedApk != null) { + sd = mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); } else { throw new RuntimeException("Not supported in system context"); } validateServiceIntent(service); try { IBinder token = getActivityToken(); - if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null - && mPackageInfo.getApplicationInfo().targetSdkVersion + if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mLoadedApk != null + && mLoadedApk.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } @@ -1633,8 +1639,8 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mPackageInfo != null) { - IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( + if (mLoadedApk != null) { + IServiceConnection sd = mLoadedApk.forgetServiceDispatcher( getOuterContext(), conn); try { ActivityManager.getService().unbindService(sd); @@ -1979,40 +1985,20 @@ class ContextImpl extends Context { } } - private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { - final String[] splitResDirs; - final ClassLoader classLoader; - try { - splitResDirs = pi.getSplitPaths(splitName); - classLoader = pi.getSplitClassLoader(splitName); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - return ResourcesManager.getInstance().getResources(activityToken, - pi.getResDir(), - splitResDirs, - pi.getOverlayDirs(), - pi.getApplicationInfo().sharedLibraryFiles, - displayId, - overrideConfig, - compatInfo, - classLoader); - } - @Override public Context createApplicationContext(ApplicationInfo application, int flags) throws NameNotFoundException { - LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), + LoadedApk loadedApk = mMainThread.getLoadedApk(application, + mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); - if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, + if (loadedApk != null) { + ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2036,20 +2022,21 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. - return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user, + return new ContextImpl(this, mMainThread, mLoadedApk, null, mActivityToken, user, flags, null); } - LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), + LoadedApk loadedApk = mMainThread.getLoadedApkForPackageName(packageName, + mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); - if (pi != null) { - ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user, + if (loadedApk != null) { + ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, user, flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(createResources(mActivityToken, pi, null, displayId, null, + c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2063,30 +2050,21 @@ class ContextImpl extends Context { @Override public Context createContextForSplit(String splitName) throws NameNotFoundException { - if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (!mLoadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { // All Splits are always loaded. return this; } - final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName); - final String[] paths = mPackageInfo.getSplitPaths(splitName); + final ClassLoader classLoader = mLoadedApk.getSplitClassLoader(splitName); - final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName, + final ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, splitName, mActivityToken, mUser, mFlags, classLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(ResourcesManager.getInstance().getResources( - mActivityToken, - mPackageInfo.getResDir(), - paths, - mPackageInfo.getOverlayDirs(), - mPackageInfo.getApplicationInfo().sharedLibraryFiles, - displayId, - null, - mPackageInfo.getCompatibilityInfo(), - classLoader)); + context.setResources(mLoadedApk.getOrCreateResourcesForSplit(splitName, + mActivityToken, displayId)); return context; } @@ -2096,11 +2074,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("overrideConfiguration must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2111,11 +2089,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("display must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = display.getDisplayId(); - context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, + context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; @@ -2125,7 +2103,7 @@ class ContextImpl extends Context { public Context createDeviceProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2133,7 +2111,7 @@ class ContextImpl extends Context { public Context createCredentialProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2182,14 +2160,14 @@ class ContextImpl extends Context { @Override public File getDataDir() { - if (mPackageInfo != null) { + if (mLoadedApk != null) { File res = null; if (isCredentialProtectedStorage()) { - res = mPackageInfo.getCredentialProtectedDataDirFile(); + res = mLoadedApk.getCredentialProtectedDataDirFile(); } else if (isDeviceProtectedStorage()) { - res = mPackageInfo.getDeviceProtectedDataDirFile(); + res = mLoadedApk.getDeviceProtectedDataDirFile(); } else { - res = mPackageInfo.getDataDirFile(); + res = mLoadedApk.getDataDirFile(); } if (res != null) { @@ -2240,10 +2218,10 @@ class ContextImpl extends Context { } static ContextImpl createSystemContext(ActivityThread mainThread) { - LoadedApk packageInfo = new LoadedApk(mainThread); - ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, + LoadedApk loadedApk = new LoadedApk(mainThread); + ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, null); - context.setResources(packageInfo.getResources()); + context.setResources(loadedApk.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); return context; @@ -2254,35 +2232,35 @@ class ContextImpl extends Context { * Make sure that the created system UI context shares the same LoadedApk as the system context. */ static ContextImpl createSystemUiContext(ContextImpl systemContext) { - final LoadedApk packageInfo = systemContext.mPackageInfo; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, + final LoadedApk loadedApk = systemContext.mLoadedApk; + ContextImpl context = new ContextImpl(null, systemContext.mMainThread, loadedApk, null, null, null, 0, null); - context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null, - packageInfo.getCompatibilityInfo())); + context.setResources(loadedApk.createResources(null, null, Display.DEFAULT_DISPLAY, null, + loadedApk.getCompatibilityInfo())); return context; } - static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { - if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); - ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, + static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk loadedApk) { + if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); + ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, null); - context.setResources(packageInfo.getResources()); + context.setResources(loadedApk.getResources()); return context; } static ContextImpl createActivityContext(ActivityThread mainThread, - LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, + LoadedApk loadedApk, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { - if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); - String[] splitDirs = packageInfo.getSplitResDirs(); - ClassLoader classLoader = packageInfo.getClassLoader(); + String[] splitDirs = loadedApk.getSplitResDirs(); + ClassLoader classLoader = loadedApk.getClassLoader(); - if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (loadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies"); try { - classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName); - splitDirs = packageInfo.getSplitPaths(activityInfo.splitName); + classLoader = loadedApk.getSplitClassLoader(activityInfo.splitName); + splitDirs = loadedApk.getSplitPaths(activityInfo.splitName); } catch (NameNotFoundException e) { // Nothing above us can handle a NameNotFoundException, better crash. throw new RuntimeException(e); @@ -2291,14 +2269,14 @@ class ContextImpl extends Context { } } - ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, + ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName, activityToken, null, 0, classLoader); // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) - ? packageInfo.getCompatibilityInfo() + ? loadedApk.getCompatibilityInfo() : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; final ResourcesManager resourcesManager = ResourcesManager.getInstance(); @@ -2306,10 +2284,10 @@ class ContextImpl extends Context { // Create the base resources for which all configuration contexts for this Activity // will be rebased upon. context.setResources(resourcesManager.createBaseActivityResources(activityToken, - packageInfo.getResDir(), + loadedApk.getResDir(), splitDirs, - packageInfo.getOverlayDirs(), - packageInfo.getApplicationInfo().sharedLibraryFiles, + loadedApk.getOverlayDirs(), + loadedApk.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, @@ -2320,7 +2298,7 @@ class ContextImpl extends Context { } private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, - @NonNull LoadedApk packageInfo, @Nullable String splitName, + @NonNull LoadedApk loadedApk, @Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader) { mOuterContext = this; @@ -2329,10 +2307,10 @@ class ContextImpl extends Context { // location for application. if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE | Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) { - final File dataDir = packageInfo.getDataDirFile(); - if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) { + final File dataDir = loadedApk.getDataDirFile(); + if (Objects.equals(dataDir, loadedApk.getCredentialProtectedDataDirFile())) { flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) { + } else if (Objects.equals(dataDir, loadedApk.getDeviceProtectedDataDirFile())) { flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE; } } @@ -2346,7 +2324,7 @@ class ContextImpl extends Context { } mUser = user; - mPackageInfo = packageInfo; + mLoadedApk = loadedApk; mSplitName = splitName; mClassLoader = classLoader; mResourcesManager = ResourcesManager.getInstance(); @@ -2357,8 +2335,8 @@ class ContextImpl extends Context { setResources(container.mResources); mDisplay = container.mDisplay; } else { - mBasePackageName = packageInfo.mPackageName; - ApplicationInfo ainfo = packageInfo.getApplicationInfo(); + mBasePackageName = loadedApk.mPackageName; + ApplicationInfo ainfo = loadedApk.getApplicationInfo(); if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) { // Special case: system components allow themselves to be loaded in to other // processes. For purposes of app ops, we must then consider the context as @@ -2381,7 +2359,7 @@ class ContextImpl extends Context { } void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { - mPackageInfo.installSystemApplicationInfo(info, classLoader); + mLoadedApk.installSystemApplicationInfo(info, classLoader); } final void scheduleFinalCleanup(String who, String what) { @@ -2390,7 +2368,7 @@ class ContextImpl extends Context { final void performFinalCleanup(String who, String what) { //Log.i(TAG, "Cleanup up context: " + this); - mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); + mLoadedApk.removeContextRegistrations(getOuterContext(), who, what); } final Context getReceiverRestrictedContext() { diff --git a/android/app/DialogFragment.java b/android/app/DialogFragment.java index a0fb6eeb..3a355d97 100644 --- a/android/app/DialogFragment.java +++ b/android/app/DialogFragment.java @@ -137,7 +137,9 @@ import java.io.PrintWriter; * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java * embed} * - * @deprecated Use {@link android.support.v4.app.DialogFragment} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices + * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. */ @Deprecated public class DialogFragment extends Fragment diff --git a/android/app/Fragment.java b/android/app/Fragment.java index a92684b5..4ff07f2e 100644 --- a/android/app/Fragment.java +++ b/android/app/Fragment.java @@ -257,7 +257,9 @@ import java.lang.reflect.InvocationTargetException; * pressing back will pop it to return the user to whatever previous state * the activity UI was in. * - * @deprecated Use {@link android.support.v4.app.Fragment} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.Fragment} for consistent behavior across all devices + * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. */ @Deprecated public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener { diff --git a/android/app/FragmentContainer.java b/android/app/FragmentContainer.java index a1dd32ff..536c8660 100644 --- a/android/app/FragmentContainer.java +++ b/android/app/FragmentContainer.java @@ -25,7 +25,8 @@ import android.view.View; /** * Callbacks to a {@link Fragment}'s container. * - * @deprecated Use {@link android.support.v4.app.FragmentContainer} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.FragmentContainer}. */ @Deprecated public abstract class FragmentContainer { diff --git a/android/app/FragmentController.java b/android/app/FragmentController.java index cbb58d40..40bc2483 100644 --- a/android/app/FragmentController.java +++ b/android/app/FragmentController.java @@ -38,7 +38,8 @@ import java.util.List; * It is the responsibility of the host to take care of the Fragment's lifecycle. * The methods provided by {@link FragmentController} are for that purpose. * - * @deprecated Use {@link android.support.v4.app.FragmentController} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.FragmentController} */ @Deprecated public class FragmentController { diff --git a/android/app/FragmentHostCallback.java b/android/app/FragmentHostCallback.java index 1edc68ed..b48817b1 100644 --- a/android/app/FragmentHostCallback.java +++ b/android/app/FragmentHostCallback.java @@ -38,7 +38,8 @@ import java.io.PrintWriter; * host fragments, implement {@link FragmentHostCallback}, overriding the methods * applicable to the host. * - * @deprecated Use {@link android.support.v4.app.FragmentHostCallback} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.FragmentHostCallback} */ @Deprecated public abstract class FragmentHostCallback<E> extends FragmentContainer { diff --git a/android/app/FragmentManager.java b/android/app/FragmentManager.java index 12e60b87..708450f6 100644 --- a/android/app/FragmentManager.java +++ b/android/app/FragmentManager.java @@ -75,7 +75,9 @@ import java.util.concurrent.CopyOnWriteArrayList; * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html"> * Fragments For All</a> for more details. * - * @deprecated Use {@link android.support.v4.app.FragmentManager} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices + * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. */ @Deprecated public abstract class FragmentManager { @@ -90,7 +92,8 @@ public abstract class FragmentManager { * the identifier as returned by {@link #getId} is the only thing that * will be persisted across activity instances. * - * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> + * Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry} */ @Deprecated public interface BackStackEntry { @@ -136,7 +139,9 @@ public abstract class FragmentManager { /** * Interface to watch for changes to the back stack. * - * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> + * Support Library</a> + * {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener} */ @Deprecated public interface OnBackStackChangedListener { @@ -438,7 +443,9 @@ public abstract class FragmentManager { * Callback interface for listening to fragment state changes that happen * within a given FragmentManager. * - * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> + * Support Library</a> + * {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks} */ @Deprecated public abstract static class FragmentLifecycleCallbacks { diff --git a/android/app/FragmentManagerNonConfig.java b/android/app/FragmentManagerNonConfig.java index beb1a15a..326438af 100644 --- a/android/app/FragmentManagerNonConfig.java +++ b/android/app/FragmentManagerNonConfig.java @@ -28,7 +28,8 @@ import java.util.List; * {@link FragmentController#retainNonConfig()} and * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p> * - * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.FragmentManagerNonConfig} */ @Deprecated public class FragmentManagerNonConfig { diff --git a/android/app/FragmentTransaction.java b/android/app/FragmentTransaction.java index 0f4a7fb5..713a559a 100644 --- a/android/app/FragmentTransaction.java +++ b/android/app/FragmentTransaction.java @@ -22,7 +22,8 @@ import java.lang.annotation.RetentionPolicy; * guide.</p> * </div> * - * @deprecated Use {@link android.support.v4.app.FragmentTransaction} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.FragmentTransaction} */ @Deprecated public abstract class FragmentTransaction { @@ -182,7 +183,12 @@ public abstract class FragmentTransaction { public static final int TRANSIT_FRAGMENT_FADE = 3 | TRANSIT_ENTER_MASK; /** @hide */ - @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE}) + @IntDef(prefix = { "TRANSIT_" }, value = { + TRANSIT_NONE, + TRANSIT_FRAGMENT_OPEN, + TRANSIT_FRAGMENT_CLOSE, + TRANSIT_FRAGMENT_FADE + }) @Retention(RetentionPolicy.SOURCE) public @interface Transit {} diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java index d49e11f4..b469de56 100644 --- a/android/app/Instrumentation.java +++ b/android/app/Instrumentation.java @@ -1114,7 +1114,10 @@ public class Instrumentation { public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { - return newApplication(cl.loadClass(className), context); + Application app = getFactory(context.getPackageName()) + .instantiateApplication(cl, className); + app.attach(context); + return app; } /** @@ -1201,7 +1204,20 @@ public class Instrumentation { Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { - return (Activity)cl.loadClass(className).newInstance(); + String pkg = intent.getComponent().getPackageName(); + return getFactory(pkg).instantiateActivity(cl, className, intent); + } + + private AppComponentFactory getFactory(String pkg) { + if (mThread == null) { + Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation," + + " disabling AppComponentFactory", new Throwable()); + return AppComponentFactory.DEFAULT; + } + LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true); + // This is in the case of starting up "android". + if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk; + return loadedApk.getAppFactory(); } private void prePerformCreate(Activity activity) { @@ -1950,6 +1966,14 @@ public class Instrumentation { mUiAutomationConnection = uiAutomationConnection; } + /** + * Only sets the ActivityThread up, keeps everything else null because app is not being + * instrumented. + */ + final void basicInit(ActivityThread thread) { + mThread = thread; + } + /** @hide */ public static void checkStartActivityResult(int res, Object intent) { if (!ActivityManager.isStartResultFatalError(res)) { diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java index 1fe29004..d0f84c8e 100644 --- a/android/app/KeyguardManager.java +++ b/android/app/KeyguardManager.java @@ -382,6 +382,8 @@ public class KeyguardManager { } /** + * @deprecated Use {@link #isKeyguardLocked()} instead. + * * If keyguard screen is showing or in restricted key input mode (i.e. in * keyguard password emergency screen). When in such mode, certain keys, * such as the Home key and the right soft keys, don't work. @@ -389,11 +391,7 @@ public class KeyguardManager { * @return true if in keyguard restricted input mode. */ public boolean inKeyguardRestrictedInputMode() { - try { - return mWM.inKeyguardRestrictedInputMode(); - } catch (RemoteException ex) { - return false; - } + return isKeyguardLocked(); } /** diff --git a/android/app/LauncherActivity.java b/android/app/LauncherActivity.java index 9ec7f413..88e23566 100644 --- a/android/app/LauncherActivity.java +++ b/android/app/LauncherActivity.java @@ -166,7 +166,7 @@ public abstract class LauncherActivity extends ListActivity { if (item.icon == null) { item.icon = mIconResizer.createIconThumbnail(item.resolveInfo.loadIcon(getPackageManager())); } - text.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null); + text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, null, null, null); } } diff --git a/android/app/ListFragment.java b/android/app/ListFragment.java index 90b77b39..7790f70b 100644 --- a/android/app/ListFragment.java +++ b/android/app/ListFragment.java @@ -145,7 +145,9 @@ import android.widget.TextView; * @see #setListAdapter * @see android.widget.ListView * - * @deprecated Use {@link android.support.v4.app.ListFragment} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.ListFragment} for consistent behavior across all devices + * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. */ @Deprecated public class ListFragment extends Fragment { diff --git a/android/app/LoadedApk.java b/android/app/LoadedApk.java index f6d9710d..26f49808 100644 --- a/android/app/LoadedApk.java +++ b/android/app/LoadedApk.java @@ -31,6 +31,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.split.SplitDependencyLoader; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; @@ -48,15 +49,13 @@ import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.Log; +import android.util.LogPrinter; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; - import com.android.internal.util.ArrayUtils; - import dalvik.system.VMRuntime; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -125,6 +124,7 @@ public final class LoadedApk { = new ArrayMap<>(); private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices = new ArrayMap<>(); + private AppComponentFactory mAppComponentFactory; Application getApplication() { return mApplication; @@ -148,6 +148,7 @@ public final class LoadedApk { mIncludeCode = includeCode; mRegisterPackage = registerPackage; mDisplayAdjustments.setCompatibilityInfo(compatInfo); + mAppComponentFactory = createAppFactory(mApplicationInfo, mBaseClassLoader); } private static ApplicationInfo adjustNativeLibraryPaths(ApplicationInfo info) { @@ -203,6 +204,7 @@ public final class LoadedApk { mRegisterPackage = false; mClassLoader = ClassLoader.getSystemClassLoader(); mResources = Resources.getSystem(); + mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader); } /** @@ -212,6 +214,23 @@ public final class LoadedApk { assert info.packageName.equals("android"); mApplicationInfo = info; mClassLoader = classLoader; + mAppComponentFactory = createAppFactory(info, classLoader); + } + + private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) { + if (appInfo.appComponentFactory != null) { + try { + return (AppComponentFactory) cl.loadClass(appInfo.appComponentFactory) + .newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + Slog.e(TAG, "Unable to instantiate appComponentFactory", e); + } + } + return AppComponentFactory.DEFAULT; + } + + public AppComponentFactory getAppFactory() { + return mAppComponentFactory; } public String getPackageName() { @@ -313,6 +332,7 @@ public final class LoadedApk { getClassLoader()); } } + mAppComponentFactory = createAppFactory(aInfo, mClassLoader); } private void setApplicationInfo(ApplicationInfo aInfo) { @@ -638,8 +658,7 @@ public final class LoadedApk { final String defaultSearchPaths = System.getProperty("java.library.path"); final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib"); if (mApplicationInfo.getCodePath() != null - && mApplicationInfo.getCodePath().startsWith("/vendor/") - && treatVendorApkAsUnbundled) { + && mApplicationInfo.isVendor() && treatVendorApkAsUnbundled) { isBundledApp = false; } @@ -948,14 +967,78 @@ public final class LoadedApk { throw new AssertionError("null split not found"); } - mResources = ResourcesManager.getInstance().getResources(null, mResDir, - splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), + mResources = ResourcesManager.getInstance().getResources( + null, + mResDir, + splitPaths, + mOverlayDirs, + mApplicationInfo.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, + null, + getCompatibilityInfo(), getClassLoader()); } return mResources; } + public Resources getOrCreateResourcesForSplit(@NonNull String splitName, + @Nullable IBinder activityToken, int displayId) throws NameNotFoundException { + return ResourcesManager.getInstance().getResources( + activityToken, + mResDir, + getSplitPaths(splitName), + mOverlayDirs, + mApplicationInfo.sharedLibraryFiles, + displayId, + null, + getCompatibilityInfo(), + getSplitClassLoader(splitName)); + } + + /** + * Creates the top level resources for the given package. Will return an existing + * Resources if one has already been created. + */ + public Resources getOrCreateTopLevelResources(@NonNull ApplicationInfo appInfo) { + // Request for this app, short circuit + if (appInfo.uid == Process.myUid()) { + return getResources(); + } + + // Get resources for a different package + return ResourcesManager.getInstance().getResources( + null, + appInfo.publicSourceDir, + appInfo.splitPublicSourceDirs, + appInfo.resourceDirs, + appInfo.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, + null, + getCompatibilityInfo(), + getClassLoader()); + } + + public Resources createResources(IBinder activityToken, String splitName, + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { + final String[] splitResDirs; + final ClassLoader classLoader; + try { + splitResDirs = getSplitPaths(splitName); + classLoader = getSplitClassLoader(splitName); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + return ResourcesManager.getInstance().getResources(activityToken, + mResDir, + splitResDirs, + mOverlayDirs, + mApplicationInfo.sharedLibraryFiles, + displayId, + overrideConfig, + compatInfo, + classLoader); + } + public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { @@ -1647,9 +1730,12 @@ public final class LoadedApk { if (dead) { mConnection.onBindingDied(name); } - // If there is a new service, it is now connected. + // If there is a new viable service, it is now connected. if (service != null) { mConnection.onServiceConnected(name, service); + } else { + // The binding machinery worked, but the remote returned null from onBind(). + mConnection.onNullBinding(name); } } diff --git a/android/app/LoaderManager.java b/android/app/LoaderManager.java index 7969684a..86d0fd62 100644 --- a/android/app/LoaderManager.java +++ b/android/app/LoaderManager.java @@ -55,14 +55,16 @@ import java.lang.reflect.Modifier; * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p> * </div> * - * @deprecated Use {@link android.support.v4.app.LoaderManager} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> + * {@link android.support.v4.app.LoaderManager} */ @Deprecated public abstract class LoaderManager { /** * Callback interface for a client to interact with the manager. * - * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks} + * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html"> + * Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks} */ @Deprecated public interface LoaderCallbacks<D> { diff --git a/android/app/LocalActivityManager.java b/android/app/LocalActivityManager.java index 3b273bc1..998ac5f2 100644 --- a/android/app/LocalActivityManager.java +++ b/android/app/LocalActivityManager.java @@ -22,6 +22,7 @@ import android.os.Binder; import android.os.Bundle; import android.util.Log; import android.view.Window; + import com.android.internal.content.ReferrerIntent; import java.util.ArrayList; @@ -161,12 +162,12 @@ public class LocalActivityManager { case CREATED: if (desiredState == STARTED) { if (localLOGV) Log.v(TAG, r.id + ": restarting"); - mActivityThread.performRestartActivity(r); + mActivityThread.performRestartActivity(r, true /* start */); r.curState = STARTED; } if (desiredState == RESUMED) { if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming"); - mActivityThread.performRestartActivity(r); + mActivityThread.performRestartActivity(r, true /* start */); mActivityThread.performResumeActivity(r, true, "moveToState-CREATED"); r.curState = RESUMED; } @@ -207,7 +208,7 @@ public class LocalActivityManager { private void performPause(LocalActivityRecord r, boolean finishing) { final boolean needState = r.instanceState == null; final Bundle instanceState = mActivityThread.performPauseActivity( - r, finishing, needState, "performPause"); + r, finishing, needState, "performPause", null /* pendingActions */); if (needState) { r.instanceState = instanceState; } @@ -361,7 +362,8 @@ public class LocalActivityManager { performPause(r, finish); } if (localLOGV) Log.v(TAG, r.id + ": destroying"); - mActivityThread.performDestroyActivity(r, finish); + mActivityThread.performDestroyActivity(r, finish, 0 /* configChanges */, + false /* getNonConfigInstance */); r.activity = null; r.window = null; if (finish) { @@ -625,7 +627,8 @@ public class LocalActivityManager { for (int i=0; i<N; i++) { LocalActivityRecord r = mActivityArray.get(i); if (localLOGV) Log.v(TAG, r.id + ": destroying"); - mActivityThread.performDestroyActivity(r, finishing); + mActivityThread.performDestroyActivity(r, finishing, 0 /* configChanges */, + false /* getNonConfigInstance */); } mActivities.clear(); mActivityArray.clear(); diff --git a/android/app/Notification.java b/android/app/Notification.java index 42c1347e..85c3be82 100644 --- a/android/app/Notification.java +++ b/android/app/Notification.java @@ -577,7 +577,13 @@ public class Notification implements Parcelable public int flags; /** @hide */ - @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX}) + @IntDef(prefix = { "PRIORITY_" }, value = { + PRIORITY_DEFAULT, + PRIORITY_LOW, + PRIORITY_MIN, + PRIORITY_HIGH, + PRIORITY_MAX + }) @Retention(RetentionPolicy.SOURCE) public @interface Priority {} @@ -1084,6 +1090,12 @@ public class Notification implements Parcelable public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; /** + * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification + * represents a group conversation. + */ + public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; + + /** * {@link #extras} key: whether the notification should be colorized as * supplied to {@link Builder#setColorized(boolean)}}. */ @@ -1941,6 +1953,7 @@ public class Notification implements Parcelable mSortKey = parcel.readString(); extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null + fixDuplicateExtras(); actions = parcel.createTypedArray(Action.CREATOR); // may be null @@ -2389,6 +2402,33 @@ public class Notification implements Parcelable }; /** + * Parcelling creates multiple copies of objects in {@code extras}. Fix them. + * <p> + * For backwards compatibility {@code extras} holds some references to "real" member data such + * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly + * fine as long as the object stays in one process. + * <p> + * However, once the notification goes into a parcel each reference gets marshalled separately, + * wasting memory. Especially with large images on Auto and TV, this is worth fixing. + */ + private void fixDuplicateExtras() { + if (extras != null) { + fixDuplicateExtra(mSmallIcon, EXTRA_SMALL_ICON); + fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); + } + } + + /** + * If we find an extra that's exactly the same as one of the "real" fields but refers to a + * separate object, replace it with the field's version to avoid holding duplicate copies. + */ + private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { + if (original != null && extras.getParcelable(extraName) != null) { + extras.putParcelable(extraName, original); + } + } + + /** * Sets the {@link #contentView} field to be a view with the standard "Latest Event" * layout. * @@ -5926,9 +5966,10 @@ public class Notification implements Parcelable public static final int MAXIMUM_RETAINED_MESSAGES = 25; CharSequence mUserDisplayName; - CharSequence mConversationTitle; + @Nullable CharSequence mConversationTitle; List<Message> mMessages = new ArrayList<>(); List<Message> mHistoricMessages = new ArrayList<>(); + boolean mIsGroupConversation; MessagingStyle() { } @@ -5951,20 +5992,20 @@ public class Notification implements Parcelable } /** - * Sets the title to be displayed on this conversation. This should only be used for - * group messaging and left unset for one-on-one conversations. - * @param conversationTitle + * Sets the title to be displayed on this conversation. May be set to {@code null}. + * + * @param conversationTitle A name for the conversation, or {@code null} * @return this object for method chaining. */ - public MessagingStyle setConversationTitle(CharSequence conversationTitle) { + public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { mConversationTitle = conversationTitle; return this; } /** - * Return the title to be displayed on this conversation. Can be <code>null</code> and - * should be for one-on-one conversations + * Return the title to be displayed on this conversation. May return {@code null}. */ + @Nullable public CharSequence getConversationTitle() { return mConversationTitle; } @@ -6041,6 +6082,24 @@ public class Notification implements Parcelable } /** + * Sets whether this conversation notification represents a group. + * @param isGroupConversation {@code true} if the conversation represents a group, + * {@code false} otherwise. + * @return this object for method chaining + */ + public MessagingStyle setGroupConversation(boolean isGroupConversation) { + mIsGroupConversation = isGroupConversation; + return this; + } + + /** + * Returns {@code true} if this notification represents a group conversation. + */ + public boolean isGroupConversation() { + return mIsGroupConversation; + } + + /** * @hide */ @Override @@ -6060,6 +6119,7 @@ public class Notification implements Parcelable } fixTitleAndTextExtras(extras); + extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); } private void fixTitleAndTextExtras(Bundle extras) { @@ -6102,6 +6162,7 @@ public class Notification implements Parcelable mMessages = Message.getMessagesFromBundleArray(messages); Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); + mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); } /** diff --git a/android/app/SharedPreferencesImpl.java b/android/app/SharedPreferencesImpl.java index 8c47598f..6dca4004 100644 --- a/android/app/SharedPreferencesImpl.java +++ b/android/app/SharedPreferencesImpl.java @@ -50,6 +50,11 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; final class SharedPreferencesImpl implements SharedPreferences { private static final String TAG = "SharedPreferencesImpl"; @@ -69,16 +74,12 @@ final class SharedPreferencesImpl implements SharedPreferences { private final Object mLock = new Object(); private final Object mWritingToDiskLock = new Object(); - @GuardedBy("mLock") - private Map<String, Object> mMap; + private Future<Map<String, Object>> mMap; @GuardedBy("mLock") private int mDiskWritesInFlight = 0; @GuardedBy("mLock") - private boolean mLoaded = false; - - @GuardedBy("mLock") private StructTimespec mStatTimestamp; @GuardedBy("mLock") @@ -105,27 +106,18 @@ final class SharedPreferencesImpl implements SharedPreferences { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; - mLoaded = false; mMap = null; startLoadFromDisk(); } private void startLoadFromDisk() { - synchronized (mLock) { - mLoaded = false; - } - new Thread("SharedPreferencesImpl-load") { - public void run() { - loadFromDisk(); - } - }.start(); + FutureTask<Map<String, Object>> futureTask = new FutureTask<>(() -> loadFromDisk()); + mMap = futureTask; + new Thread(futureTask, "SharedPreferencesImpl-load").start(); } - private void loadFromDisk() { + private Map<String, Object> loadFromDisk() { synchronized (mLock) { - if (mLoaded) { - return; - } if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); @@ -158,16 +150,14 @@ final class SharedPreferencesImpl implements SharedPreferences { } synchronized (mLock) { - mLoaded = true; if (map != null) { - mMap = map; mStatTimestamp = stat.st_mtim; mStatSize = stat.st_size; } else { - mMap = new HashMap<>(); + map = new HashMap<>(); } - mLock.notifyAll(); } + return map; } static File makeBackupFile(File prefsFile) { @@ -226,36 +216,42 @@ final class SharedPreferencesImpl implements SharedPreferences { } } - private void awaitLoadedLocked() { - if (!mLoaded) { + private @GuardedBy("mLock") Map<String, Object> getLoaded() { + // For backwards compatibility, we need to ignore any interrupts. b/70122540. + for (;;) { + try { + return mMap.get(); + } catch (ExecutionException e) { + throw new IllegalStateException(e); + } catch (InterruptedException e) { + // Ignore and try again. + } + } + } + private @GuardedBy("mLock") Map<String, Object> getLoadedWithBlockGuard() { + if (!mMap.isDone()) { // Raise an explicit StrictMode onReadFromDisk for this // thread, since the real read will be in a different // thread and otherwise ignored by StrictMode. BlockGuard.getThreadPolicy().onReadFromDisk(); } - while (!mLoaded) { - try { - mLock.wait(); - } catch (InterruptedException unused) { - } - } + return getLoaded(); } @Override public Map<String, ?> getAll() { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - //noinspection unchecked - return new HashMap<String, Object>(mMap); + return new HashMap<String, Object>(map); } } @Override @Nullable public String getString(String key, @Nullable String defValue) { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - String v = (String)mMap.get(key); + String v = (String) map.get(key); return v != null ? v : defValue; } } @@ -263,66 +259,65 @@ final class SharedPreferencesImpl implements SharedPreferences { @Override @Nullable public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - Set<String> v = (Set<String>) mMap.get(key); + @SuppressWarnings("unchecked") + Set<String> v = (Set<String>) map.get(key); return v != null ? v : defValues; } } @Override public int getInt(String key, int defValue) { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - Integer v = (Integer)mMap.get(key); + Integer v = (Integer) map.get(key); return v != null ? v : defValue; } } @Override public long getLong(String key, long defValue) { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - Long v = (Long)mMap.get(key); + Long v = (Long) map.get(key); return v != null ? v : defValue; } } @Override public float getFloat(String key, float defValue) { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - Float v = (Float)mMap.get(key); + Float v = (Float) map.get(key); return v != null ? v : defValue; } } @Override public boolean getBoolean(String key, boolean defValue) { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - Boolean v = (Boolean)mMap.get(key); + Boolean v = (Boolean) map.get(key); return v != null ? v : defValue; } } @Override public boolean contains(String key) { + Map<String, Object> map = getLoadedWithBlockGuard(); synchronized (mLock) { - awaitLoadedLocked(); - return mMap.containsKey(key); + return map.containsKey(key); } } @Override public Editor edit() { - // TODO: remove the need to call awaitLoadedLocked() when + // TODO: remove the need to call getLoaded() when // requesting an editor. will require some work on the // Editor, but then we should be able to do: // // context.getSharedPreferences(..).edit().putString(..).apply() // // ... all without blocking. - synchronized (mLock) { - awaitLoadedLocked(); - } + getLoadedWithBlockGuard(); return new EditorImpl(); } @@ -476,13 +471,43 @@ final class SharedPreferencesImpl implements SharedPreferences { // a memory commit comes in when we're already // writing to disk. if (mDiskWritesInFlight > 0) { - // We can't modify our mMap as a currently + // We can't modify our map as a currently // in-flight write owns it. Clone it before // modifying it. // noinspection unchecked - mMap = new HashMap<String, Object>(mMap); + mMap = new Future<Map<String, Object>>() { + private Map<String, Object> mCopiedMap = + new HashMap<String, Object>(getLoaded()); + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public Map<String, Object> get() + throws InterruptedException, ExecutionException { + return mCopiedMap; + } + + @Override + public Map<String, Object> get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return mCopiedMap; + } + }; } - mapToWriteToDisk = mMap; + mapToWriteToDisk = getLoaded(); mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java index 23c4166d..85a9be35 100644 --- a/android/app/StatusBarManager.java +++ b/android/app/StatusBarManager.java @@ -80,9 +80,14 @@ public class StatusBarManager { public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS; - @IntDef(flag = true, - value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS, - DISABLE2_NOTIFICATION_SHADE, DISABLE2_GLOBAL_ACTIONS}) + @IntDef(flag = true, prefix = { "DISABLE2_" }, value = { + DISABLE2_NONE, + DISABLE2_MASK, + DISABLE2_QUICK_SETTINGS, + DISABLE2_SYSTEM_ICONS, + DISABLE2_NOTIFICATION_SHADE, + DISABLE2_GLOBAL_ACTIONS + }) @Retention(RetentionPolicy.SOURCE) public @interface Disable2Flags {} diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java index e48946f2..66cf9915 100644 --- a/android/app/SystemServiceRegistry.java +++ b/android/app/SystemServiceRegistry.java @@ -22,6 +22,7 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.job.IJobScheduler; import android.app.job.JobScheduler; +import android.app.slice.SliceManager; import android.app.timezone.RulesManager; import android.app.trust.TrustManager; import android.app.usage.IStorageStatsManager; @@ -551,8 +552,16 @@ final class SystemServiceRegistry { registerService(Context.WALLPAPER_SERVICE, WallpaperManager.class, new CachedServiceFetcher<WallpaperManager>() { @Override - public WallpaperManager createService(ContextImpl ctx) { - return new WallpaperManager(ctx.getOuterContext(), + public WallpaperManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + final IBinder b; + if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) { + b = ServiceManager.getServiceOrThrow(Context.WALLPAPER_SERVICE); + } else { + b = ServiceManager.getService(Context.WALLPAPER_SERVICE); + } + IWallpaperManager service = IWallpaperManager.Stub.asInterface(b); + return new WallpaperManager(service, ctx.getOuterContext(), ctx.mMainThread.getHandler()); }}); @@ -617,12 +626,13 @@ final class SystemServiceRegistry { ConnectivityThread.getInstanceLooper()); }}); - registerService(Context.WIFI_RTT2_SERVICE, WifiRttManager.class, + registerService(Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class, new CachedServiceFetcher<WifiRttManager>() { @Override public WifiRttManager createService(ContextImpl ctx) throws ServiceNotFoundException { - IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT2_SERVICE); + IBinder b = ServiceManager.getServiceOrThrow( + Context.WIFI_RTT_RANGING_SERVICE); IWifiRttManager service = IWifiRttManager.Stub.asInterface(b); return new WifiRttManager(ctx.getOuterContext(), service); }}); @@ -944,6 +954,16 @@ final class SystemServiceRegistry { ICrossProfileApps.Stub.asInterface(b)); } }); + + registerService(Context.SLICE_SERVICE, SliceManager.class, + new CachedServiceFetcher<SliceManager>() { + @Override + public SliceManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + return new SliceManager(ctx.getOuterContext(), + ctx.mMainThread.getHandler()); + } + }); } /** diff --git a/android/app/UiAutomation.java b/android/app/UiAutomation.java index c99de5dd..8f016853 100644 --- a/android/app/UiAutomation.java +++ b/android/app/UiAutomation.java @@ -26,6 +26,7 @@ import android.annotation.TestApi; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; +import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; @@ -690,42 +691,15 @@ public final class UiAutomation { .getRealDisplay(Display.DEFAULT_DISPLAY); Point displaySize = new Point(); display.getRealSize(displaySize); - final int displayWidth = displaySize.x; - final int displayHeight = displaySize.y; - final float screenshotWidth; - final float screenshotHeight; - - final int rotation = display.getRotation(); - switch (rotation) { - case ROTATION_FREEZE_0: { - screenshotWidth = displayWidth; - screenshotHeight = displayHeight; - } break; - case ROTATION_FREEZE_90: { - screenshotWidth = displayHeight; - screenshotHeight = displayWidth; - } break; - case ROTATION_FREEZE_180: { - screenshotWidth = displayWidth; - screenshotHeight = displayHeight; - } break; - case ROTATION_FREEZE_270: { - screenshotWidth = displayHeight; - screenshotHeight = displayWidth; - } break; - default: { - throw new IllegalArgumentException("Invalid rotation: " - + rotation); - } - } + int rotation = display.getRotation(); // Take the screenshot Bitmap screenShot = null; try { // Calling out without a lock held. - screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth, - (int) screenshotHeight); + screenShot = mUiAutomationConnection.takeScreenshot( + new Rect(0, 0, displaySize.x, displaySize.y), rotation); if (screenShot == null) { return null; } @@ -734,21 +708,6 @@ public final class UiAutomation { return null; } - // Rotate the screenshot to the current orientation - if (rotation != ROTATION_FREEZE_0) { - Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight, - Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(unrotatedScreenShot); - canvas.translate(unrotatedScreenShot.getWidth() / 2, - unrotatedScreenShot.getHeight() / 2); - canvas.rotate(getDegreesForRotation(rotation)); - canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2); - canvas.drawBitmap(screenShot, 0, 0, null); - canvas.setBitmap(null); - screenShot.recycle(); - screenShot = unrotatedScreenShot; - } - // Optimization screenShot.setHasAlpha(false); diff --git a/android/app/UiAutomationConnection.java b/android/app/UiAutomationConnection.java index 5e414b83..d3828ab4 100644 --- a/android/app/UiAutomationConnection.java +++ b/android/app/UiAutomationConnection.java @@ -21,6 +21,7 @@ import android.accessibilityservice.IAccessibilityServiceClient; import android.content.Context; import android.content.pm.IPackageManager; import android.graphics.Bitmap; +import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Binder; import android.os.IBinder; @@ -153,7 +154,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public Bitmap takeScreenshot(int width, int height) { + public Bitmap takeScreenshot(Rect crop, int rotation) { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); @@ -161,7 +162,9 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } final long identity = Binder.clearCallingIdentity(); try { - return SurfaceControl.screenshot(width, height); + int width = crop.width(); + int height = crop.height(); + return SurfaceControl.screenshot(crop, width, height, rotation); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/android/app/UiModeManager.java b/android/app/UiModeManager.java index bc616686..0da5e249 100644 --- a/android/app/UiModeManager.java +++ b/android/app/UiModeManager.java @@ -98,7 +98,11 @@ public class UiModeManager { public static String ACTION_EXIT_DESK_MODE = "android.app.action.EXIT_DESK_MODE"; /** @hide */ - @IntDef({MODE_NIGHT_AUTO, MODE_NIGHT_NO, MODE_NIGHT_YES}) + @IntDef(prefix = { "MODE_" }, value = { + MODE_NIGHT_AUTO, + MODE_NIGHT_NO, + MODE_NIGHT_YES + }) @Retention(RetentionPolicy.SOURCE) public @interface NightMode {} diff --git a/android/app/VrManager.java b/android/app/VrManager.java index 392387a9..61b90e17 100644 --- a/android/app/VrManager.java +++ b/android/app/VrManager.java @@ -4,6 +4,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.os.Handler; @@ -214,4 +215,22 @@ public class VrManager { e.rethrowFromSystemServer(); } } + + /** + * Start VR Input method for the packageName in {@link ComponentName}. + * This method notifies InputMethodManagerService to use VR IME instead of + * regular phone IME. + * @param componentName ComponentName of a VR InputMethod that should be set as selected + * input by InputMethodManagerService. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) + public void setVrInputMethod(ComponentName componentName) { + try { + mService.setVrInputMethod(componentName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } } diff --git a/android/app/WallpaperInfo.java b/android/app/WallpaperInfo.java index 9d40381f..35a17892 100644 --- a/android/app/WallpaperInfo.java +++ b/android/app/WallpaperInfo.java @@ -16,18 +16,15 @@ package android.app; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources.NotFoundException; import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; @@ -39,6 +36,9 @@ import android.util.AttributeSet; import android.util.Printer; import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** @@ -76,6 +76,7 @@ public final class WallpaperInfo implements Parcelable { final int mContextUriResource; final int mContextDescriptionResource; final boolean mShowMetadataInPreview; + final boolean mSupportsAmbientMode; /** * Constructor. @@ -89,15 +90,7 @@ public final class WallpaperInfo implements Parcelable { mService = service; ServiceInfo si = service.serviceInfo; - PackageManager pm = context.getPackageManager(); - String settingsActivityComponent = null; - int thumbnailRes = -1; - int authorRes = -1; - int descriptionRes = -1; - int contextUriRes = -1; - int contextDescriptionRes = -1; - boolean showMetadataInPreview = false; - + final PackageManager pm = context.getPackageManager(); XmlResourceParser parser = null; try { parser = si.loadXmlMetaData(pm, WallpaperService.SERVICE_META_DATA); @@ -123,27 +116,30 @@ public final class WallpaperInfo implements Parcelable { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Wallpaper); - settingsActivityComponent = sa.getString( + mSettingsActivityName = sa.getString( com.android.internal.R.styleable.Wallpaper_settingsActivity); - - thumbnailRes = sa.getResourceId( + + mThumbnailResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_thumbnail, -1); - authorRes = sa.getResourceId( + mAuthorResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_author, -1); - descriptionRes = sa.getResourceId( + mDescriptionResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_description, -1); - contextUriRes = sa.getResourceId( + mContextUriResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_contextUri, -1); - contextDescriptionRes = sa.getResourceId( + mContextDescriptionResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_contextDescription, -1); - showMetadataInPreview = sa.getBoolean( + mShowMetadataInPreview = sa.getBoolean( com.android.internal.R.styleable.Wallpaper_showMetadataInPreview, false); + mSupportsAmbientMode = sa.getBoolean( + com.android.internal.R.styleable.Wallpaper_supportsAmbientMode, + false); sa.recycle(); } catch (NameNotFoundException e) { @@ -152,14 +148,6 @@ public final class WallpaperInfo implements Parcelable { } finally { if (parser != null) parser.close(); } - - mSettingsActivityName = settingsActivityComponent; - mThumbnailResource = thumbnailRes; - mAuthorResource = authorRes; - mDescriptionResource = descriptionRes; - mContextUriResource = contextUriRes; - mContextDescriptionResource = contextDescriptionRes; - mShowMetadataInPreview = showMetadataInPreview; } WallpaperInfo(Parcel source) { @@ -170,6 +158,7 @@ public final class WallpaperInfo implements Parcelable { mContextUriResource = source.readInt(); mContextDescriptionResource = source.readInt(); mShowMetadataInPreview = source.readInt() != 0; + mSupportsAmbientMode = source.readInt() != 0; mService = ResolveInfo.CREATOR.createFromParcel(source); } @@ -326,6 +315,16 @@ public final class WallpaperInfo implements Parcelable { } /** + * Returns whether a wallpaper was optimized or not for ambient mode. + * + * @return {@code true} if wallpaper can draw in ambient mode. + * @hide + */ + public boolean getSupportsAmbientMode() { + return mSupportsAmbientMode; + } + + /** * Return the class name of an activity that provides a settings UI for * the wallpaper. You can launch this activity be starting it with * an {@link android.content.Intent} whose action is MAIN and with an @@ -366,6 +365,7 @@ public final class WallpaperInfo implements Parcelable { dest.writeInt(mContextUriResource); dest.writeInt(mContextDescriptionResource); dest.writeInt(mShowMetadataInPreview ? 1 : 0); + dest.writeInt(mSupportsAmbientMode ? 1 : 0); mService.writeToParcel(dest, flags); } diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java index 081bd814..f21746cd 100644 --- a/android/app/WallpaperManager.java +++ b/android/app/WallpaperManager.java @@ -176,7 +176,7 @@ public class WallpaperManager { // flags for which kind of wallpaper to act on /** @hide */ - @IntDef(flag = true, value = { + @IntDef(flag = true, prefix = { "FLAG_" }, value = { FLAG_SYSTEM, FLAG_LOCK }) @@ -286,9 +286,8 @@ public class WallpaperManager { private Bitmap mDefaultWallpaper; private Handler mMainLooperHandler; - Globals(Looper looper) { - IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); - mService = IWallpaperManager.Stub.asInterface(b); + Globals(IWallpaperManager service, Looper looper) { + mService = service; mMainLooperHandler = new Handler(looper); forgetLoadedWallpaper(); } @@ -497,17 +496,17 @@ public class WallpaperManager { private static final Object sSync = new Object[0]; private static Globals sGlobals; - static void initGlobals(Looper looper) { + static void initGlobals(IWallpaperManager service, Looper looper) { synchronized (sSync) { if (sGlobals == null) { - sGlobals = new Globals(looper); + sGlobals = new Globals(service, looper); } } } - /*package*/ WallpaperManager(Context context, Handler handler) { + /*package*/ WallpaperManager(IWallpaperManager service, Context context, Handler handler) { mContext = context; - initGlobals(context.getMainLooper()); + initGlobals(service, context.getMainLooper()); } /** diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java index 80399ae6..085fc79f 100644 --- a/android/app/WindowConfiguration.java +++ b/android/app/WindowConfiguration.java @@ -89,7 +89,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public static final int WINDOWING_MODE_FREEFORM = 5; /** @hide */ - @IntDef({ + @IntDef(prefix = { "WINDOWING_MODE_" }, value = { WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_PINNED, @@ -115,7 +115,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public static final int ACTIVITY_TYPE_ASSISTANT = 4; /** @hide */ - @IntDef({ + @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = { ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_HOME, @@ -138,13 +138,12 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3; /** @hide */ - @IntDef(flag = true, - value = { - WINDOW_CONFIG_BOUNDS, - WINDOW_CONFIG_APP_BOUNDS, - WINDOW_CONFIG_WINDOWING_MODE, - WINDOW_CONFIG_ACTIVITY_TYPE - }) + @IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = { + WINDOW_CONFIG_BOUNDS, + WINDOW_CONFIG_APP_BOUNDS, + WINDOW_CONFIG_WINDOWING_MODE, + WINDOW_CONFIG_ACTIVITY_TYPE + }) public @interface WindowConfig {} public WindowConfiguration() { diff --git a/android/app/admin/DeviceAdminReceiver.java b/android/app/admin/DeviceAdminReceiver.java index d0d98c9f..2e697ac0 100644 --- a/android/app/admin/DeviceAdminReceiver.java +++ b/android/app/admin/DeviceAdminReceiver.java @@ -368,9 +368,9 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({ - BUGREPORT_FAILURE_FAILED_COMPLETING, - BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE + @IntDef(prefix = { "BUGREPORT_FAILURE_" }, value = { + BUGREPORT_FAILURE_FAILED_COMPLETING, + BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE }) public @interface BugreportFailureCode {} diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java index 0bca9690..7e80ac7b 100644 --- a/android/app/admin/DevicePolicyManager.java +++ b/android/app/admin/DevicePolicyManager.java @@ -16,7 +16,9 @@ package android.app.admin; +import android.annotation.CallbackExecutor; import android.annotation.ColorInt; +import android.annotation.Condemned; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -49,6 +51,7 @@ import android.net.ProxyInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.Process; @@ -57,7 +60,15 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.ContactsContract.Directory; +import android.security.AttestedKeyPair; import android.security.Credentials; +import android.security.KeyChain; +import android.security.KeyChainException; +import android.security.keymaster.KeymasterCertificateChain; +import android.security.keystore.AttestationUtils; +import android.security.keystore.KeyAttestationException; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.ParcelableKeyGenParameterSpec; import android.service.restrictions.RestrictionsReceiver; import android.telephony.TelephonyManager; import android.util.ArraySet; @@ -75,6 +86,7 @@ import java.lang.annotation.RetentionPolicy; import java.net.InetSocketAddress; import java.net.Proxy; import java.security.KeyFactory; +import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; @@ -88,6 +100,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; /** * Public interface for managing policies enforced on a device. Most clients of this class must be @@ -1315,9 +1328,15 @@ public class DevicePolicyManager { public static final String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app"; /** + * Delegation for installing existing packages. This scope grants access to the + * {@link #installExistingPackage} API. + */ + public static final String DELEGATION_INSTALL_EXISTING_PACKAGE = + "delegation-install-existing-package"; + + /** * Delegation of management of uninstalled packages. This scope grants access to the * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs. - * @hide */ public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES = "delegation-keep-uninstalled-packages"; @@ -1360,8 +1379,13 @@ public class DevicePolicyManager { /** * @hide */ - @IntDef({STATE_USER_UNMANAGED, STATE_USER_SETUP_INCOMPLETE, STATE_USER_SETUP_COMPLETE, - STATE_USER_SETUP_FINALIZED, STATE_USER_PROFILE_COMPLETE}) + @IntDef(prefix = { "STATE_USER_" }, value = { + STATE_USER_UNMANAGED, + STATE_USER_SETUP_INCOMPLETE, + STATE_USER_SETUP_COMPLETE, + STATE_USER_SETUP_FINALIZED, + STATE_USER_PROFILE_COMPLETE + }) @Retention(RetentionPolicy.SOURCE) public @interface UserProvisioningState {} @@ -1534,11 +1558,13 @@ public class DevicePolicyManager { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER, CODE_USER_NOT_RUNNING, + @IntDef(prefix = { "CODE_" }, value = { + CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER, CODE_USER_NOT_RUNNING, CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED, CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE, CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED, - CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED}) + CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED + }) public @interface ProvisioningPreCondition {} /** @@ -1620,11 +1646,15 @@ public class DevicePolicyManager { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = {LOCK_TASK_FEATURE_NONE, LOCK_TASK_FEATURE_SYSTEM_INFO, - LOCK_TASK_FEATURE_NOTIFICATIONS, LOCK_TASK_FEATURE_HOME, - LOCK_TASK_FEATURE_RECENTS, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, - LOCK_TASK_FEATURE_KEYGUARD}) + @IntDef(flag = true, prefix = { "LOCK_TASK_FEATURE_" }, value = { + LOCK_TASK_FEATURE_NONE, + LOCK_TASK_FEATURE_SYSTEM_INFO, + LOCK_TASK_FEATURE_NOTIFICATIONS, + LOCK_TASK_FEATURE_HOME, + LOCK_TASK_FEATURE_RECENTS, + LOCK_TASK_FEATURE_GLOBAL_ACTIONS, + LOCK_TASK_FEATURE_KEYGUARD + }) public @interface LockTaskFeature {} /** @@ -2605,10 +2635,115 @@ public class DevicePolicyManager { } /** + * The maximum number of characters allowed in the password blacklist. + */ + private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000; + + /** + * Throws an exception if the password blacklist is too large. + * + * @hide + */ + public static void enforcePasswordBlacklistSize(List<String> blacklist) { + if (blacklist == null) { + return; + } + long characterCount = 0; + for (final String item : blacklist) { + characterCount += item.length(); + } + if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) { + throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by " + + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters"); + } + } + + /** + * Called by an application that is administering the device to blacklist passwords. + * <p> + * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin. + * Note that the match against the blacklist is case insensitive. The blacklist applies for all + * password qualities requested by {@link #setPasswordQuality} however it is not taken into + * consideration by {@link #isActivePasswordSufficient}. + * <p> + * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is + * given a name that is used to track which blacklist is currently set by calling {@link + * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link + * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when + * the blacklist is being cleared. + * <p> + * The blacklist is limited to a total of 128 thousand characters rather than limiting to a + * number of entries. + * <p> + * This method can be called on the {@link DevicePolicyManager} instance returned by + * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent + * profile. + * + * @param admin the {@link DeviceAdminReceiver} this request is associated with + * @param name name to associate with the blacklist + * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist + * @return whether the new blacklist was successfully installed + * @throws SecurityException if {@code admin} is not a device or profile owner + * @throws IllegalArgumentException if the blacklist surpasses the character limit + * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list + * + * @see #getPasswordBlacklistName + * @see #isActivePasswordSufficient + * @see #resetPasswordWithToken + */ + public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name, + @Nullable List<String> blacklist) { + enforcePasswordBlacklistSize(blacklist); + + try { + return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Get the name of the password blacklist set by the given admin. + * + * @param admin the {@link DeviceAdminReceiver} this request is associated with + * @return the name of the blacklist or {@code null} if no blacklist is set + * + * @see #setPasswordBlacklist + */ + public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) { + try { + return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Test if a given password is blacklisted. + * + * @param userId the user to valiate for + * @param password the password to check against the blacklist + * @return whether the password is blacklisted + * + * @see #setPasswordBlacklist + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD) + public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) { + try { + return mService.isPasswordBlacklisted(userId, password); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Determine whether the current password the user has set is sufficient to meet the policy * requirements (e.g. quality, minimum length) that have been requested by the admins of this * user and its participating profiles. Restrictions on profiles that have a separate challenge - * are not taken into account. The user must be unlocked in order to perform the check. + * are not taken into account. The user must be unlocked in order to perform the check. The + * password blacklist is not considered when checking sufficiency. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has @@ -2635,6 +2770,29 @@ public class DevicePolicyManager { } /** + * When called by a profile owner of a managed profile returns true if the profile uses unified + * challenge with its parent user. + * + * <strong>Note: This method is not concerned with password quality and will return false if + * the profile has empty password as a separate challenge. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a profile owner of a managed profile. + * @see UserManager#DISALLOW_UNIFIED_PASSWORD + */ + public boolean isUsingUnifiedPassword(@NonNull ComponentName admin) { + throwIfParentInstance("isUsingUnifiedPassword"); + if (mService != null) { + try { + return mService.isUsingUnifiedPassword(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return true; + } + + /** * Determine whether the current profile password the user has set is sufficient * to meet the policy requirements (e.g. quality, minimum length) that have been * requested by the admins of the parent user and its profiles. @@ -3049,23 +3207,6 @@ public class DevicePolicyManager { } /** - * Returns maximum time to lock that applied by all profiles in this user. We do this because we - * do not have a separate timeout to lock for work challenge only. - * - * @hide - */ - public long getMaximumTimeToLockForUserAndProfiles(int userHandle) { - if (mService != null) { - try { - return mService.getMaximumTimeToLockForUserAndProfiles(userHandle); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - return 0; - } - - /** * Called by a device/profile owner to set the timeout after which unlocking with secondary, non * strong auth (e.g. fingerprint, trust agents) times out, i.e. the user has to use a strong * authentication method like password, pin or pattern. @@ -3153,7 +3294,9 @@ public class DevicePolicyManager { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag=true, value={FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}) + @IntDef(flag = true, prefix = { "FLAG_EVICT_" }, value = { + FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY + }) public @interface LockNowFlag {} /** @@ -3468,6 +3611,16 @@ public class DevicePolicyManager { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION"; + + /** + * Broadcast action: notify managed provisioning that new managed user is created. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MANAGED_USER_CREATED = + "android.app.action.MANAGED_USER_CREATED"; + /** * Widgets are enabled in keyguard */ @@ -3943,6 +4096,108 @@ public class DevicePolicyManager { } /** + * Called by a device or profile owner, or delegated certificate installer, to generate a + * new private/public key pair. If the device supports key generation via secure hardware, + * this method is useful for creating a key in KeyChain that never left the secure hardware. + * + * Access to the key is controlled the same way as in {@link #installKeyPair}. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if calling from a delegated certificate installer. + * @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}. + * @param keySpec Specification of the key to generate, see + * {@link java.security.KeyPairGenerator}. + * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise. + * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile + * owner. + * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, or if the + * algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec} + * or {@code ECGenParameterSpec}. + */ + public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin, + @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec) { + throwIfParentInstance("generateKeyPair"); + try { + final ParcelableKeyGenParameterSpec parcelableSpec = + new ParcelableKeyGenParameterSpec(keySpec); + KeymasterCertificateChain attestationChain = new KeymasterCertificateChain(); + final boolean success = mService.generateKeyPair( + admin, mContext.getPackageName(), algorithm, parcelableSpec, attestationChain); + if (!success) { + Log.e(TAG, "Error generating key via DevicePolicyManagerService."); + return null; + } + + final String alias = keySpec.getKeystoreAlias(); + final KeyPair keyPair = KeyChain.getKeyPair(mContext, alias); + Certificate[] outputChain = null; + try { + if (AttestationUtils.isChainValid(attestationChain)) { + outputChain = AttestationUtils.parseCertificateChain(attestationChain); + } + } catch (KeyAttestationException e) { + Log.e(TAG, "Error parsing attestation chain for alias " + alias, e); + mService.removeKeyPair(admin, mContext.getPackageName(), alias); + return null; + } + return new AttestedKeyPair(keyPair, outputChain); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (KeyChainException e) { + Log.w(TAG, "Failed to generate key", e); + } catch (InterruptedException e) { + Log.w(TAG, "Interrupted while generating key", e); + Thread.currentThread().interrupt(); + } + return null; + } + + + /** + * Called by a device or profile owner, or delegated certificate installer, to associate + * certificates with a key pair that was generated using {@link #generateKeyPair}, and + * set whether the key is available for the user to choose in the certificate selection + * prompt. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if calling from a delegated certificate installer. + * @param alias The private key alias under which to install the certificate. The {@code alias} + * should denote an existing private key. If a certificate with that alias already + * exists, it will be overwritten. + * @param certs The certificate chain to install. The chain should start with the leaf + * certificate and include the chain of trust in order. This will be returned by + * {@link android.security.KeyChain#getCertificateChain}. + * @param isUserSelectable {@code true} to indicate that a user can select this key via the + * certificate selection prompt, {@code false} to indicate that this key can only be + * granted access by implementing + * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}. + * @return {@code true} if the provided {@code alias} exists and the certificates has been + * successfully associated with it, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile + * owner, or {@code admin} is null but the calling application is not a delegated + * certificate installer. + */ + public boolean setKeyPairCertificate(@Nullable ComponentName admin, + @NonNull String alias, @NonNull List<Certificate> certs, boolean isUserSelectable) { + throwIfParentInstance("setKeyPairCertificate"); + try { + final byte[] pemCert = Credentials.convertToPem(certs.get(0)); + byte[] pemChain = null; + if (certs.size() > 1) { + pemChain = Credentials.convertToPem( + certs.subList(1, certs.size()).toArray(new Certificate[0])); + } + return mService.setKeyPairCertificate(admin, mContext.getPackageName(), alias, pemCert, + pemChain, isUserSelectable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (CertificateException | IOException e) { + Log.w(TAG, "Could not pem-encode certificate", e); + } + return false; + } + + + /** * @return the alias of a given CA certificate in the certificate store, or {@code null} if it * doesn't exist. */ @@ -4212,16 +4467,16 @@ public class DevicePolicyManager { /** * Called by a device owner to request a bugreport. * <p> - * If the device contains secondary users or profiles, they must be affiliated with the device - * owner user. Otherwise a {@link SecurityException} will be thrown. See - * {@link #setAffiliationIds}. + * If the device contains secondary users or profiles, they must be affiliated with the device. + * Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @return {@code true} if the bugreport collection started successfully, or {@code false} if it * wasn't triggered because a previous bugreport operation is still active (either the * bugreport is still running or waiting for the user to share or decline) * @throws SecurityException if {@code admin} is not a device owner, or there is at least one - * profile or secondary user that is not affiliated with the device owner user. + * profile or secondary user that is not affiliated with the device. + * @see #isAffiliatedUser */ public boolean requestBugreport(@NonNull ComponentName admin) { throwIfParentInstance("requestBugreport"); @@ -6035,7 +6290,6 @@ public class DevicePolicyManager { * @return List of package names to keep cached. * @see #setDelegatedScopes * @see #DELEGATION_KEEP_UNINSTALLED_PACKAGES - * @hide */ public @Nullable List<String> getKeepUninstalledPackages(@Nullable ComponentName admin) { throwIfParentInstance("getKeepUninstalledPackages"); @@ -6063,7 +6317,6 @@ public class DevicePolicyManager { * @throws SecurityException if {@code admin} is not a device owner. * @see #setDelegatedScopes * @see #DELEGATION_KEEP_UNINSTALLED_PACKAGES - * @hide */ public void setKeepUninstalledPackages(@Nullable ComponentName admin, @NonNull List<String> packageNames) { @@ -6151,21 +6404,27 @@ public class DevicePolicyManager { public static final int MAKE_USER_DEMO = 0x0004; /** - * Flag used by {@link #createAndManageUser} to specificy that the newly created user should be + * Flag used by {@link #createAndManageUser} to specify that the newly created user should be * started in the background as part of the user creation. */ - // TODO: Investigate solutions for the case where reboot happens before setup is completed. public static final int START_USER_IN_BACKGROUND = 0x0008; /** + * Flag used by {@link #createAndManageUser} to specify that the newly created user should skip + * the disabling of system apps during provisioning. + */ + public static final int LEAVE_ALL_SYSTEM_APPS_ENABLED = 0x0010; + + /** * @hide */ - @IntDef( - flag = true, - prefix = {"SKIP_", "MAKE_USER_", "START_"}, - value = {SKIP_SETUP_WIZARD, MAKE_USER_EPHEMERAL, MAKE_USER_DEMO, - START_USER_IN_BACKGROUND} - ) + @IntDef(flag = true, prefix = { "SKIP_", "MAKE_USER_", "START_", "LEAVE_" }, value = { + SKIP_SETUP_WIZARD, + MAKE_USER_EPHEMERAL, + MAKE_USER_DEMO, + START_USER_IN_BACKGROUND, + LEAVE_ALL_SYSTEM_APPS_ENABLED + }) @Retention(RetentionPolicy.SOURCE) public @interface CreateAndManageUserFlags {} @@ -6219,7 +6478,7 @@ public class DevicePolicyManager { * @return {@code true} if the user was removed, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. */ - public boolean removeUser(@NonNull ComponentName admin, UserHandle userHandle) { + public boolean removeUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("removeUser"); try { return mService.removeUser(admin, userHandle); @@ -6230,6 +6489,7 @@ public class DevicePolicyManager { /** * Called by a device owner to switch the specified user to the foreground. + * <p> This cannot be used to switch to a managed profile. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to switch to; null will switch to primary. @@ -6247,6 +6507,80 @@ public class DevicePolicyManager { } /** + * Called by a device owner to stop the specified secondary user. + * <p> This cannot be used to stop the primary user or a managed profile. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param userHandle the user to be stopped. + * @return {@code true} if the user can be stopped, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { + throwIfParentInstance("stopUser"); + try { + return mService.stopUser(admin, userHandle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Called by a profile owner that is affiliated with the device to stop the calling user + * and switch back to primary. + * <p> This has no effect when called on a managed profile. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return {@code true} if the exit was successful, {@code false} otherwise. + * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device. + * @see #isAffiliatedUser + */ + public boolean logoutUser(@NonNull ComponentName admin) { + throwIfParentInstance("logoutUser"); + try { + return mService.logoutUser(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Called by a device owner to list all secondary users on the device, excluding managed + * profiles. + * <p> Used for various user management APIs, including {@link #switchUser}, {@link #removeUser} + * and {@link #stopUser}. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return list of other {@link UserHandle}s on the device. + * @throws SecurityException if {@code admin} is not a device owner. + * @see #switchUser + * @see #removeUser + * @see #stopUser + */ + public List<UserHandle> getSecondaryUsers(@NonNull ComponentName admin) { + throwIfParentInstance("getSecondaryUsers"); + try { + return mService.getSecondaryUsers(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Checks if the profile owner is running in an ephemeral user. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return whether the profile owner is running in an ephemeral user. + */ + public boolean isEphemeralUser(@NonNull ComponentName admin) { + throwIfParentInstance("isEphemeralUser"); + try { + return mService.isEphemeralUser(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Retrieves the application restrictions for a given target application running in the calling * user. * <p> @@ -6481,6 +6815,37 @@ public class DevicePolicyManager { } /** + * Install an existing package that has been installed in another user, or has been kept after + * removal via {@link #setKeepUninstalledPackages}. + * This function can be called by a device owner, profile owner or a delegate given + * the {@link #DELEGATION_INSTALL_EXISTING_PACKAGE} scope via {@link #setDelegatedScopes}. + * When called in a secondary user or managed profile, the user/profile must be affiliated with + * the device. See {@link #isAffiliatedUser}. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The package to be installed in the calling profile. + * @return {@code true} if the app is installed; {@code false} otherwise. + * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of + * an affiliated user or profile. + * @see #setKeepUninstalledPackages + * @see #setDelegatedScopes + * @see #isAffiliatedUser + * @see #DELEGATION_PACKAGE_ACCESS + */ + public boolean installExistingPackage(@NonNull ComponentName admin, String packageName) { + throwIfParentInstance("installExistingPackage"); + if (mService != null) { + try { + return mService.installExistingPackage(admin, mContext.getPackageName(), + packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Called by a device owner or profile owner to disable account management for a specific type * of account. * <p> @@ -6551,13 +6916,14 @@ public class DevicePolicyManager { * package list results in locked tasks belonging to those packages to be finished. * <p> * This function can only be called by the device owner or by a profile owner of a user/profile - * that is affiliated with the device owner user. See {@link #setAffiliationIds}. Any packages + * that is affiliated with the device. See {@link #isAffiliatedUser}. Any packages * set via this method will be cleared if the user becomes unaffiliated. * * @param packages The list of packages allowed to enter lock task mode * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of * an affiliated user or profile. + * @see #isAffiliatedUser * @see Activity#startLockTask() * @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String) * @see DeviceAdminReceiver#onLockTaskModeExiting(Context, Intent) @@ -6580,6 +6946,7 @@ public class DevicePolicyManager { * * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of * an affiliated user or profile. + * @see #isAffiliatedUser * @see #setLockTaskPackages */ public @NonNull String[] getLockTaskPackages(@NonNull ComponentName admin) { @@ -6619,7 +6986,7 @@ public class DevicePolicyManager { * enabled. * <p> * This function can only be called by the device owner or by a profile owner of a user/profile - * that is affiliated with the device owner user. See {@link #setAffiliationIds}. Any features + * that is affiliated with the device. See {@link #isAffiliatedUser}. Any features * set via this method will be cleared if the user becomes unaffiliated. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -6633,6 +7000,7 @@ public class DevicePolicyManager { * {@link #LOCK_TASK_FEATURE_KEYGUARD} * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of * an affiliated user or profile. + * @see #isAffiliatedUser */ public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) { throwIfParentInstance("setLockTaskFeatures"); @@ -6652,7 +7020,8 @@ public class DevicePolicyManager { * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list. * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of * an affiliated user or profile. - * @see #setLockTaskFeatures(ComponentName, int) + * @see #isAffiliatedUser + * @see #setLockTaskFeatures */ public @LockTaskFeature int getLockTaskFeatures(@NonNull ComponentName admin) { throwIfParentInstance("getLockTaskFeatures"); @@ -6667,7 +7036,7 @@ public class DevicePolicyManager { } /** - * Called by device owners to update {@link android.provider.Settings.Global} settings. + * Called by device owner to update {@link android.provider.Settings.Global} settings. * Validation that the value of the setting is in the correct form for the setting type should * be performed by the caller. * <p> @@ -6716,6 +7085,37 @@ public class DevicePolicyManager { } /** + * Called by device owner to update {@link android.provider.Settings.System} settings. + * Validation that the value of the setting is in the correct form for the setting type should + * be performed by the caller. + * <p> + * The settings that can be updated with this method are: + * <ul> + * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS}</li> + * <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS_MODE}</li> + * <li>{@link android.provider.Settings.System#SCREEN_OFF_TIMEOUT}</li> + * </ul> + * <p> + * + * @see android.provider.Settings.System#SCREEN_OFF_TIMEOUT + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param setting The name of the setting to update. + * @param value The value to update the setting to. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setSystemSetting(@NonNull ComponentName admin, @NonNull String setting, + String value) { + throwIfParentInstance("setSystemSetting"); + if (mService != null) { + try { + mService.setSystemSetting(admin, setting, value); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** * Called by device owner to set the system wall clock time. This only takes effect if called * when {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be * returned. @@ -7619,6 +8019,7 @@ public class DevicePolicyManager { * @param admin Which device owner this request is associated with. * @param enabled whether security logging should be enabled or not. * @throws SecurityException if {@code admin} is not a device owner. + * @see #setAffiliationIds * @see #retrieveSecurityLogs */ public void setSecurityLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { @@ -7657,14 +8058,14 @@ public class DevicePolicyManager { * owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}. * * <p>If there is any other user or profile on the device, it must be affiliated with the - * device owner. Otherwise a {@link SecurityException} will be thrown. See - * {@link #setAffiliationIds} + * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}. * * @param admin Which device owner this request is associated with. * @return the new batch of security logs which is a list of {@link SecurityEvent}, * or {@code null} if rate limitation is exceeded or if logging is currently disabled. * @throws SecurityException if {@code admin} is not a device owner, or there is at least one - * profile or secondary user that is not affiliated with the device owner user. + * profile or secondary user that is not affiliated with the device. + * @see #isAffiliatedUser * @see DeviceAdminReceiver#onSecurityLogsAvailable */ public @Nullable List<SecurityEvent> retrieveSecurityLogs(@NonNull ComponentName admin) { @@ -7707,14 +8108,14 @@ public class DevicePolicyManager { * about data corruption when parsing. </strong> * * <p>If there is any other user or profile on the device, it must be affiliated with the - * device owner. Otherwise a {@link SecurityException} will be thrown. See - * {@link #setAffiliationIds} + * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}. * * @param admin Which device owner this request is associated with. * @return Device logs from before the latest reboot of the system, or {@code null} if this API * is not supported on the device. * @throws SecurityException if {@code admin} is not a device owner, or there is at least one - * profile or secondary user that is not affiliated with the device owner user. + * profile or secondary user that is not affiliated with the device. + * @see #isAffiliatedUser * @see #retrieveSecurityLogs */ public @Nullable List<SecurityEvent> retrievePreRebootSecurityLogs( @@ -7922,6 +8323,9 @@ public class DevicePolicyManager { * Indicates the entity that controls the device or profile owner. Two users/profiles are * affiliated if the set of ids set by their device or profile owners intersect. * + * <p>A user/profile that is affiliated with the device owner user is considered to be + * affiliated with the device. + * * <p><strong>Note:</strong> Features that depend on user affiliation (such as security logging * or {@link #bindDeviceAdminServiceAsUser}) won't be available when a secondary user or profile * is created, until it becomes affiliated. Therefore it is recommended that the appropriate @@ -7932,6 +8336,7 @@ public class DevicePolicyManager { * @param ids A set of opaque non-empty affiliation ids. * * @throws IllegalArgumentException if {@code ids} is null or contains an empty string. + * @see #isAffiliatedUser */ public void setAffiliationIds(@NonNull ComponentName admin, @NonNull Set<String> ids) { throwIfParentInstance("setAffiliationIds"); @@ -7959,13 +8364,12 @@ public class DevicePolicyManager { } /** - * @hide * Returns whether this user/profile is affiliated with the device. * <p> * By definition, the user that the device owner runs on is always affiliated with the device. * Any other user/profile is considered affiliated with the device if the set specified by its * profile owner via {@link #setAffiliationIds} intersects with the device owner's. - * + * @see #setAffiliationIds */ public boolean isAffiliatedUser() { throwIfParentInstance("isAffiliatedUser"); @@ -8178,6 +8582,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled whether network logging should be enabled or not. * @throws SecurityException if {@code admin} is not a device owner. + * @see #setAffiliationIds * @see #retrieveNetworkLogs */ public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { @@ -8233,7 +8638,8 @@ public class DevicePolicyManager { * {@code null} if the batch represented by batchToken is no longer available or if * logging is disabled. * @throws SecurityException if {@code admin} is not a device owner, or there is at least one - * profile or secondary user that is not affiliated with the device owner user. + * profile or secondary user that is not affiliated with the device. + * @see #setAffiliationIds * @see DeviceAdminReceiver#onNetworkLogsAvailable */ public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin, @@ -8411,6 +8817,15 @@ public class DevicePolicyManager { } } + /** {@hide} */ + @Condemned + @Deprecated + public boolean clearApplicationUserData(@NonNull ComponentName admin, + @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, + @NonNull Handler handler) { + return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler)); + } + /** * Called by the device owner or profile owner to clear application user data of a given * package. The behaviour of this is equivalent to the target application calling @@ -8422,19 +8837,20 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageName The name of the package which will have its user data wiped. * @param listener A callback object that will inform the caller when the clearing is done. - * @param handler The handler indicating the thread on which the listener should be invoked. + * @param executor The executor through which the listener should be invoked. * @throws SecurityException if the caller is not the device owner/profile owner. * @return whether the clearing succeeded. */ public boolean clearApplicationUserData(@NonNull ComponentName admin, @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener, - @NonNull Handler handler) { + @NonNull @CallbackExecutor Executor executor) { throwIfParentInstance("clearAppData"); + Preconditions.checkNotNull(executor); try { return mService.clearApplicationUserData(admin, packageName, new IPackageDataObserver.Stub() { public void onRemoveCompleted(String pkg, boolean succeeded) { - handler.post(() -> + executor.execute(() -> listener.onApplicationUserDataCleared(pkg, succeeded)); } }); @@ -8444,6 +8860,37 @@ public class DevicePolicyManager { } /** + * Called by a device owner to specify whether logout is enabled for all secondary users. The + * system may show a logout button that stops the user and switches back to the primary user. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param enabled whether logout should be enabled or not. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setLogoutEnabled(@NonNull ComponentName admin, boolean enabled) { + throwIfParentInstance("setLogoutEnabled"); + try { + mService.setLogoutEnabled(admin, enabled); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns whether logout is enabled by a device owner. + * + * @return {@code true} if logout is enabled by device owner, {@code false} otherwise. + */ + public boolean isLogoutEnabled() { + throwIfParentInstance("isLogoutEnabled"); + try { + return mService.isLogoutEnabled(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Callback used in {@link #clearApplicationUserData} * to indicate that the clearing of an application's user data is done. */ @@ -8457,4 +8904,65 @@ public class DevicePolicyManager { */ void onApplicationUserDataCleared(String packageName, boolean succeeded); } + + /** + * Returns set of system apps that should be removed during provisioning. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param userId ID of the user to be provisioned. + * @param provisioningAction action indicating type of provisioning, should be one of + * {@link #ACTION_PROVISION_MANAGED_DEVICE}, {@link #ACTION_PROVISION_MANAGED_PROFILE} or + * {@link #ACTION_PROVISION_MANAGED_USER}. + * + * @hide + */ + public Set<String> getDisallowedSystemApps(ComponentName admin, int userId, + String provisioningAction) { + try { + return new ArraySet<>( + mService.getDisallowedSystemApps(admin, userId, provisioningAction)); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + //TODO STOPSHIP Add link to onTransferComplete callback when implemented. + /** + * Transfers the current administrator. All policies from the current administrator are + * migrated to the new administrator. The whole operation is atomic - the transfer is either + * complete or not done at all. + * + * Depending on the current administrator (device owner, profile owner, corporate owned + * profile owner), you have the following expected behaviour: + * <ul> + * <li>A device owner can only be transferred to a new device owner</li> + * <li>A profile owner can only be transferred to a new profile owner</li> + * <li>A corporate owned managed profile can have two cases: + * <ul> + * <li>If the device owner and profile owner are the same package, + * both will be transferred.</li> + * <li>If the device owner and profile owner are different packages, + * and if this method is called from the profile owner, only the profile owner + * is transferred. Similarly, if it is called from the device owner, only + * the device owner is transferred.</li> + * </ul> + * </li> + * </ul> + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param target Which {@link DeviceAdminReceiver} we want the new administrator to be. + * @param bundle Parameters - This bundle allows the current administrator to pass data to the + * new administrator. The parameters will be received in the + * onTransferComplete callback. + * @hide + */ + public void transferOwner(@NonNull ComponentName admin, @NonNull ComponentName target, + PersistableBundle bundle) { + throwIfParentInstance("transferOwner"); + try { + mService.transferOwner(admin, target, bundle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/android/app/admin/DevicePolicyManagerInternal.java b/android/app/admin/DevicePolicyManagerInternal.java index eef2f983..b692ffd9 100644 --- a/android/app/admin/DevicePolicyManagerInternal.java +++ b/android/app/admin/DevicePolicyManagerInternal.java @@ -16,6 +16,7 @@ package android.app.admin; +import android.annotation.UserIdInt; import android.content.Intent; import java.util.List; @@ -101,4 +102,25 @@ public abstract class DevicePolicyManagerInternal { * not enforced by the profile/device owner. */ public abstract Intent createUserRestrictionSupportIntent(int userId, String userRestriction); + + /** + * Returns whether this user/profile is affiliated with the device. + * + * <p> + * By definition, the user that the device owner runs on is always affiliated with the device. + * Any other user/profile is considered affiliated with the device if the set specified by its + * profile owner via {@link DevicePolicyManager#setAffiliationIds} intersects with the device + * owner's. + * <p> + * Profile owner on the primary user will never be considered as affiliated as there is no + * device owner to be affiliated with. + */ + public abstract boolean isUserAffiliatedWithDevice(int userId); + + /** + * Reports that a profile has changed to use a unified or separate credential. + * + * @param userId User ID of the profile. + */ + public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId); } diff --git a/android/app/admin/PasswordMetrics.java b/android/app/admin/PasswordMetrics.java index 4658a474..5fee8532 100644 --- a/android/app/admin/PasswordMetrics.java +++ b/android/app/admin/PasswordMetrics.java @@ -223,7 +223,12 @@ public class PasswordMetrics implements Parcelable { } @Retention(RetentionPolicy.SOURCE) - @IntDef({CHAR_UPPER_CASE, CHAR_LOWER_CASE, CHAR_DIGIT, CHAR_SYMBOL}) + @IntDef(prefix = { "CHAR_" }, value = { + CHAR_UPPER_CASE, + CHAR_LOWER_CASE, + CHAR_DIGIT, + CHAR_SYMBOL + }) private @interface CharacterCatagory {} private static final int CHAR_LOWER_CASE = 0; private static final int CHAR_UPPER_CASE = 1; diff --git a/android/app/admin/SecurityLog.java b/android/app/admin/SecurityLog.java index 2b590e0d..d3b66d0d 100644 --- a/android/app/admin/SecurityLog.java +++ b/android/app/admin/SecurityLog.java @@ -17,6 +17,7 @@ package android.app.admin; import android.annotation.IntDef; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemProperties; @@ -26,6 +27,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collection; +import java.util.Objects; /** * Definitions for working with security logs. @@ -43,10 +45,17 @@ public class SecurityLog { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({TAG_ADB_SHELL_INTERACTIVE, TAG_ADB_SHELL_CMD, TAG_SYNC_RECV_FILE, TAG_SYNC_SEND_FILE, - TAG_APP_PROCESS_START, TAG_KEYGUARD_DISMISSED, TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, - TAG_KEYGUARD_SECURED}) - public @interface SECURITY_LOG_TAG {} + @IntDef(prefix = { "TAG_" }, value = { + TAG_ADB_SHELL_INTERACTIVE, + TAG_ADB_SHELL_CMD, + TAG_SYNC_RECV_FILE, + TAG_SYNC_SEND_FILE, + TAG_APP_PROCESS_START, + TAG_KEYGUARD_DISMISSED, + TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, + TAG_KEYGUARD_SECURED + }) + public @interface SecurityLogTag {} /** * Indicate that an ADB interactive shell was opened via "adb shell". @@ -128,9 +137,28 @@ public class SecurityLog { */ public static final class SecurityEvent implements Parcelable { private Event mEvent; + private long mId; + + /** + * Constructor used by native classes to generate SecurityEvent instances. + * @hide + */ + /* package */ SecurityEvent(byte[] data) { + this(0, data); + } + + /** + * Constructor used by Parcelable.Creator to generate SecurityEvent instances. + * @hide + */ + /* package */ SecurityEvent(Parcel source) { + this(source.readLong(), source.createByteArray()); + } /** @hide */ - /*package*/ SecurityEvent(byte[] data) { + @TestApi + public SecurityEvent(long id, byte[] data) { + mId = id; mEvent = Event.fromBytes(data); } @@ -143,13 +171,8 @@ public class SecurityLog { /** * Returns the tag of this log entry, which specifies entry's semantics. - * Could be one of {@link SecurityLog#TAG_SYNC_RECV_FILE}, - * {@link SecurityLog#TAG_SYNC_SEND_FILE}, {@link SecurityLog#TAG_ADB_SHELL_CMD}, - * {@link SecurityLog#TAG_ADB_SHELL_INTERACTIVE}, {@link SecurityLog#TAG_APP_PROCESS_START}, - * {@link SecurityLog#TAG_KEYGUARD_DISMISSED}, {@link SecurityLog#TAG_KEYGUARD_SECURED}, - * {@link SecurityLog#TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT}. */ - public @SECURITY_LOG_TAG int getTag() { + public @SecurityLogTag int getTag() { return mEvent.getTag(); } @@ -160,6 +183,21 @@ public class SecurityLog { return mEvent.getData(); } + /** + * @hide + */ + public void setId(long id) { + this.mId = id; + } + + /** + * Returns the id of the event, where the id monotonically increases for each event. The id + * is reset when the device reboots, and when security logging is enabled. + */ + public long getId() { + return mId; + } + @Override public int describeContents() { return 0; @@ -167,6 +205,7 @@ public class SecurityLog { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mId); dest.writeByteArray(mEvent.getBytes()); } @@ -174,7 +213,7 @@ public class SecurityLog { new Parcelable.Creator<SecurityEvent>() { @Override public SecurityEvent createFromParcel(Parcel source) { - return new SecurityEvent(source.createByteArray()); + return new SecurityEvent(source); } @Override @@ -191,7 +230,7 @@ public class SecurityLog { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SecurityEvent other = (SecurityEvent) o; - return mEvent.equals(other.mEvent); + return mEvent.equals(other.mEvent) && mId == other.mId; } /** @@ -199,7 +238,7 @@ public class SecurityLog { */ @Override public int hashCode() { - return mEvent.hashCode(); + return Objects.hash(mEvent, mId); } } /** diff --git a/android/app/admin/SystemUpdateInfo.java b/android/app/admin/SystemUpdateInfo.java index fa31273e..b0376b50 100644 --- a/android/app/admin/SystemUpdateInfo.java +++ b/android/app/admin/SystemUpdateInfo.java @@ -52,7 +52,11 @@ public final class SystemUpdateInfo implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({SECURITY_PATCH_STATE_FALSE, SECURITY_PATCH_STATE_TRUE, SECURITY_PATCH_STATE_UNKNOWN}) + @IntDef(prefix = { "SECURITY_PATCH_STATE_" }, value = { + SECURITY_PATCH_STATE_FALSE, + SECURITY_PATCH_STATE_TRUE, + SECURITY_PATCH_STATE_UNKNOWN + }) public @interface SecurityPatchState {} private static final String ATTR_RECEIVED_TIME = "received-time"; diff --git a/android/app/admin/SystemUpdatePolicy.java b/android/app/admin/SystemUpdatePolicy.java index 995d98a7..232a6887 100644 --- a/android/app/admin/SystemUpdatePolicy.java +++ b/android/app/admin/SystemUpdatePolicy.java @@ -36,10 +36,11 @@ import java.lang.annotation.RetentionPolicy; public class SystemUpdatePolicy implements Parcelable { /** @hide */ - @IntDef({ - TYPE_INSTALL_AUTOMATIC, - TYPE_INSTALL_WINDOWED, - TYPE_POSTPONE}) + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_INSTALL_AUTOMATIC, + TYPE_INSTALL_WINDOWED, + TYPE_POSTPONE + }) @Retention(RetentionPolicy.SOURCE) @interface SystemUpdatePolicyType {} diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java index da5569d2..7b549cd5 100644 --- a/android/app/assist/AssistStructure.java +++ b/android/app/assist/AssistStructure.java @@ -2139,6 +2139,16 @@ public class AssistStructure implements Parcelable { return mActivityComponent; } + /** + * Called by Autofill server when app forged a different value. + * + * @hide + */ + public void setActivityComponent(ComponentName componentName) { + ensureData(); + mActivityComponent = componentName; + } + /** @hide */ public int getFlags() { return mFlags; diff --git a/android/app/backup/BackupAgent.java b/android/app/backup/BackupAgent.java index 7aa80d26..861cb9a8 100644 --- a/android/app/backup/BackupAgent.java +++ b/android/app/backup/BackupAgent.java @@ -263,6 +263,17 @@ public abstract class BackupAgent extends ContextWrapper { ParcelFileDescriptor newState) throws IOException; /** + * New version of {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)} + * that handles a long app version code. Default implementation casts the version code to + * an int and calls {@link #onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor)}. + */ + public void onRestore(BackupDataInput data, long appVersionCode, + ParcelFileDescriptor newState) + throws IOException { + onRestore(data, (int) appVersionCode, newState); + } + + /** * The application is having its entire file system contents backed up. {@code data} * points to the backup destination, and the app has the opportunity to choose which * files are to be stored. To commit a file as part of the backup, call the @@ -947,7 +958,7 @@ public abstract class BackupAgent extends ContextWrapper { } @Override - public void doRestore(ParcelFileDescriptor data, int appVersionCode, + public void doRestore(ParcelFileDescriptor data, long appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder) throws RemoteException { // Ensure that we're running with the app's normal permission level diff --git a/android/app/backup/BackupManager.java b/android/app/backup/BackupManager.java index 9f9b2170..6512b98c 100644 --- a/android/app/backup/BackupManager.java +++ b/android/app/backup/BackupManager.java @@ -16,10 +16,12 @@ package android.app.backup; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -446,6 +448,57 @@ public class BackupManager { } /** + * Update the attributes of the transport identified by {@code transportComponent}. If the + * specified transport has not been bound at least once (for registration), this call will be + * ignored. Only the host process of the transport can change its description, otherwise a + * {@link SecurityException} will be thrown. + * + * @param transportComponent The identity of the transport being described. + * @param name A {@link String} with the new name for the transport. This is NOT for + * identification. MUST NOT be {@code null}. + * @param configurationIntent An {@link Intent} that can be passed to + * {@link Context#startActivity} in order to launch the transport's configuration UI. It may + * be {@code null} if the transport does not offer any user-facing configuration UI. + * @param currentDestinationString A {@link String} describing the destination to which the + * transport is currently sending data. MUST NOT be {@code null}. + * @param dataManagementIntent An {@link Intent} that can be passed to + * {@link Context#startActivity} in order to launch the transport's data-management UI. It + * may be {@code null} if the transport does not offer any user-facing data + * management UI. + * @param dataManagementLabel A {@link String} to be used as the label for the transport's data + * management affordance. This MUST be {@code null} when dataManagementIntent is + * {@code null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}. + * @throws SecurityException If the UID of the calling process differs from the package UID of + * {@code transportComponent} or if the caller does NOT have BACKUP permission. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public void updateTransportAttributes( + ComponentName transportComponent, + String name, + @Nullable Intent configurationIntent, + String currentDestinationString, + @Nullable Intent dataManagementIntent, + @Nullable String dataManagementLabel) { + checkServiceBinder(); + if (sService != null) { + try { + sService.updateTransportAttributes( + transportComponent, + name, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } catch (RemoteException e) { + Log.e(TAG, "describeTransport() couldn't connect"); + } + } + } + + /** * Specify the current backup transport. * * @param transport The name of the transport to select. This should be one diff --git a/android/app/backup/BackupManagerMonitor.java b/android/app/backup/BackupManagerMonitor.java index ebad16e0..ae4a98a4 100644 --- a/android/app/backup/BackupManagerMonitor.java +++ b/android/app/backup/BackupManagerMonitor.java @@ -40,9 +40,14 @@ public class BackupManagerMonitor { /** string : the package name */ public static final String EXTRA_LOG_EVENT_PACKAGE_NAME = "android.app.backup.extra.LOG_EVENT_PACKAGE_NAME"; - /** int : the versionCode of the package named by EXTRA_LOG_EVENT_PACKAGE_NAME */ + /** int : the versionCode of the package named by EXTRA_LOG_EVENT_PACKAGE_NAME + * @deprecated Use {@link #EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION} */ + @Deprecated public static final String EXTRA_LOG_EVENT_PACKAGE_VERSION = "android.app.backup.extra.LOG_EVENT_PACKAGE_VERSION"; + /** long : the full versionCode of the package named by EXTRA_LOG_EVENT_PACKAGE_NAME */ + public static final String EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION = + "android.app.backup.extra.LOG_EVENT_PACKAGE_FULL_VERSION"; /** int : the id of the log message, will be a unique identifier */ public static final String EXTRA_LOG_EVENT_ID = "android.app.backup.extra.LOG_EVENT_ID"; /** diff --git a/android/app/servertransaction/ActivityConfigurationChangeItem.java b/android/app/servertransaction/ActivityConfigurationChangeItem.java index 07001e2b..a2b7d580 100644 --- a/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -19,25 +19,25 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.INVALID_DISPLAY; +import android.app.ClientTransactionHandler; import android.content.res.Configuration; import android.os.IBinder; import android.os.Parcel; import android.os.Trace; +import java.util.Objects; + /** * Activity configuration changed callback. * @hide */ public class ActivityConfigurationChangeItem extends ClientTransactionItem { - private final Configuration mConfiguration; - - public ActivityConfigurationChangeItem(Configuration configuration) { - mConfiguration = configuration; - } + private Configuration mConfiguration; @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here. Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY); @@ -45,6 +45,29 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { } + // ObjectPoolItem implementation + + private ActivityConfigurationChangeItem() {} + + /** Obtain an instance initialized with provided params. */ + public static ActivityConfigurationChangeItem obtain(Configuration config) { + ActivityConfigurationChangeItem instance = + ObjectPool.obtain(ActivityConfigurationChangeItem.class); + if (instance == null) { + instance = new ActivityConfigurationChangeItem(); + } + instance.mConfiguration = config; + + return instance; + } + + @Override + public void recycle() { + mConfiguration = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation /** Write to Parcel. */ @@ -78,11 +101,16 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem { return false; } final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o; - return mConfiguration.equals(other.mConfiguration); + return Objects.equals(mConfiguration, other.mConfiguration); } @Override public int hashCode() { return mConfiguration.hashCode(); } + + @Override + public String toString() { + return "ActivityConfigurationChange{config=" + mConfiguration + "}"; + } } diff --git a/android/app/servertransaction/ActivityLifecycleItem.java b/android/app/servertransaction/ActivityLifecycleItem.java index a64108db..0fdc7c56 100644 --- a/android/app/servertransaction/ActivityLifecycleItem.java +++ b/android/app/servertransaction/ActivityLifecycleItem.java @@ -27,16 +27,28 @@ import java.lang.annotation.RetentionPolicy; */ public abstract class ActivityLifecycleItem extends ClientTransactionItem { - static final boolean DEBUG_ORDER = false; - - @IntDef({UNDEFINED, RESUMED, PAUSED, STOPPED, DESTROYED}) + @IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = { + UNDEFINED, + PRE_ON_CREATE, + ON_CREATE, + ON_START, + ON_RESUME, + ON_PAUSE, + ON_STOP, + ON_DESTROY, + ON_RESTART + }) @Retention(RetentionPolicy.SOURCE) - @interface LifecycleState{} + public @interface LifecycleState{} public static final int UNDEFINED = -1; - public static final int RESUMED = 0; - public static final int PAUSED = 1; - public static final int STOPPED = 2; - public static final int DESTROYED = 3; + public static final int PRE_ON_CREATE = 0; + public static final int ON_CREATE = 1; + public static final int ON_START = 2; + public static final int ON_RESUME = 3; + public static final int ON_PAUSE = 4; + public static final int ON_STOP = 5; + public static final int ON_DESTROY = 6; + public static final int ON_RESTART = 7; /** A final lifecycle state that an activity should reach. */ @LifecycleState diff --git a/android/app/servertransaction/ActivityResultItem.java b/android/app/servertransaction/ActivityResultItem.java index 76664d8e..73b5ec44 100644 --- a/android/app/servertransaction/ActivityResultItem.java +++ b/android/app/servertransaction/ActivityResultItem.java @@ -16,9 +16,10 @@ package android.app.servertransaction; -import static android.app.servertransaction.ActivityLifecycleItem.PAUSED; +import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import android.app.ClientTransactionHandler; import android.app.ResultInfo; import android.os.IBinder; import android.os.Parcel; @@ -26,6 +27,7 @@ import android.os.Parcelable; import android.os.Trace; import java.util.List; +import java.util.Objects; /** * Activity result delivery callback. @@ -33,25 +35,44 @@ import java.util.List; */ public class ActivityResultItem extends ClientTransactionItem { - private final List<ResultInfo> mResultInfoList; - - public ActivityResultItem(List<ResultInfo> resultInfos) { - mResultInfoList = resultInfos; - } + private List<ResultInfo> mResultInfoList; @Override public int getPreExecutionState() { - return PAUSED; + return ON_PAUSE; } @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult"); client.handleSendResult(token, mResultInfoList); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } + // ObjectPoolItem implementation + + private ActivityResultItem() {} + + /** Obtain an instance initialized with provided params. */ + public static ActivityResultItem obtain(List<ResultInfo> resultInfoList) { + ActivityResultItem instance = ObjectPool.obtain(ActivityResultItem.class); + if (instance == null) { + instance = new ActivityResultItem(); + } + instance.mResultInfoList = resultInfoList; + + return instance; + } + + @Override + public void recycle() { + mResultInfoList = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation /** Write to Parcel. */ @@ -85,11 +106,16 @@ public class ActivityResultItem extends ClientTransactionItem { return false; } final ActivityResultItem other = (ActivityResultItem) o; - return mResultInfoList.equals(other.mResultInfoList); + return Objects.equals(mResultInfoList, other.mResultInfoList); } @Override public int hashCode() { return mResultInfoList.hashCode(); } + + @Override + public String toString() { + return "ActivityResultItem{resultInfoList=" + mResultInfoList + "}"; + } } diff --git a/android/app/servertransaction/BaseClientRequest.java b/android/app/servertransaction/BaseClientRequest.java index 4bd01afb..c91e0ca5 100644 --- a/android/app/servertransaction/BaseClientRequest.java +++ b/android/app/servertransaction/BaseClientRequest.java @@ -24,7 +24,7 @@ import android.os.IBinder; * Each of them can be prepared before scheduling and, eventually, executed. * @hide */ -public interface BaseClientRequest { +public interface BaseClientRequest extends ObjectPoolItem { /** * Prepare the client request before scheduling. @@ -33,13 +33,25 @@ public interface BaseClientRequest { * @param client Target client handler. * @param token Target activity token. */ - default void prepare(ClientTransactionHandler client, IBinder token) { + default void preExecute(ClientTransactionHandler client, IBinder token) { } /** * Execute the request. * @param client Target client handler. * @param token Target activity token. + * @param pendingActions Container that may have data pending to be used. */ - void execute(ClientTransactionHandler client, IBinder token); + void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions); + + /** + * Perform all actions that need to happen after execution, e.g. report the result to server. + * @param client Target client handler. + * @param token Target activity token. + * @param pendingActions Container that may have data pending to be used. + */ + default void postExecute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + } } diff --git a/android/app/servertransaction/ClientTransaction.java b/android/app/servertransaction/ClientTransaction.java index d2289ba0..3c96f069 100644 --- a/android/app/servertransaction/ClientTransaction.java +++ b/android/app/servertransaction/ClientTransaction.java @@ -16,6 +16,7 @@ package android.app.servertransaction; +import android.annotation.Nullable; import android.app.ClientTransactionHandler; import android.app.IApplicationThread; import android.os.IBinder; @@ -36,7 +37,7 @@ import java.util.Objects; * @see ActivityLifecycleItem * @hide */ -public class ClientTransaction implements Parcelable { +public class ClientTransaction implements Parcelable, ObjectPoolItem { /** A list of individual callbacks to a client. */ private List<ClientTransactionItem> mActivityCallbacks; @@ -53,9 +54,9 @@ public class ClientTransaction implements Parcelable { /** Target client activity. Might be null if the entire transaction is targeting an app. */ private IBinder mActivityToken; - public ClientTransaction(IApplicationThread client, IBinder activityToken) { - mClient = client; - mActivityToken = activityToken; + /** Get the target client of the transaction. */ + public IApplicationThread getClient() { + return mClient; } /** @@ -69,6 +70,23 @@ public class ClientTransaction implements Parcelable { mActivityCallbacks.add(activityCallback); } + /** Get the list of callbacks. */ + @Nullable + List<ClientTransactionItem> getCallbacks() { + return mActivityCallbacks; + } + + /** Get the target activity. */ + @Nullable + public IBinder getActivityToken() { + return mActivityToken; + } + + /** Get the target state lifecycle request. */ + ActivityLifecycleItem getLifecycleStateRequest() { + return mLifecycleStateRequest; + } + /** * Set the lifecycle state in which the client should be after executing the transaction. * @param stateRequest A lifecycle request initialized with right parameters. @@ -82,50 +100,68 @@ public class ClientTransaction implements Parcelable { * @param clientTransactionHandler Handler on the client side that will executed all operations * requested by transaction items. */ - public void prepare(android.app.ClientTransactionHandler clientTransactionHandler) { + public void preExecute(android.app.ClientTransactionHandler clientTransactionHandler) { if (mActivityCallbacks != null) { final int size = mActivityCallbacks.size(); for (int i = 0; i < size; ++i) { - mActivityCallbacks.get(i).prepare(clientTransactionHandler, mActivityToken); + mActivityCallbacks.get(i).preExecute(clientTransactionHandler, mActivityToken); } } if (mLifecycleStateRequest != null) { - mLifecycleStateRequest.prepare(clientTransactionHandler, mActivityToken); - } - } - - /** - * Execute the transaction. - * @param clientTransactionHandler Handler on the client side that will execute all operations - * requested by transaction items. - */ - public void execute(android.app.ClientTransactionHandler clientTransactionHandler) { - if (mActivityCallbacks != null) { - final int size = mActivityCallbacks.size(); - for (int i = 0; i < size; ++i) { - mActivityCallbacks.get(i).execute(clientTransactionHandler, mActivityToken); - } - } - if (mLifecycleStateRequest != null) { - mLifecycleStateRequest.execute(clientTransactionHandler, mActivityToken); + mLifecycleStateRequest.preExecute(clientTransactionHandler, mActivityToken); } } /** * Schedule the transaction after it was initialized. It will be send to client and all its * individual parts will be applied in the following sequence: - * 1. The client calls {@link #prepare(ClientTransactionHandler)}, which triggers all work that - * needs to be done before actually scheduling the transaction for callbacks and lifecycle - * state request. + * 1. The client calls {@link #preExecute(ClientTransactionHandler)}, which triggers all work + * that needs to be done before actually scheduling the transaction for callbacks and + * lifecycle state request. * 2. The transaction message is scheduled. - * 3. The client calls {@link #execute(ClientTransactionHandler)}, which executes all callbacks - * and necessary lifecycle transitions. + * 3. The client calls {@link TransactionExecutor#execute(ClientTransaction)}, which executes + * all callbacks and necessary lifecycle transitions. */ public void schedule() throws RemoteException { mClient.scheduleTransaction(this); } + // ObjectPoolItem implementation + + private ClientTransaction() {} + + /** Obtain an instance initialized with provided params. */ + public static ClientTransaction obtain(IApplicationThread client, IBinder activityToken) { + ClientTransaction instance = ObjectPool.obtain(ClientTransaction.class); + if (instance == null) { + instance = new ClientTransaction(); + } + instance.mClient = client; + instance.mActivityToken = activityToken; + + return instance; + } + + @Override + public void recycle() { + if (mActivityCallbacks != null) { + int size = mActivityCallbacks.size(); + for (int i = 0; i < size; i++) { + mActivityCallbacks.get(i).recycle(); + } + mActivityCallbacks.clear(); + } + if (mLifecycleStateRequest != null) { + mLifecycleStateRequest.recycle(); + mLifecycleStateRequest = null; + } + mClient = null; + mActivityToken = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation /** Write to Parcel. */ diff --git a/android/app/servertransaction/ConfigurationChangeItem.java b/android/app/servertransaction/ConfigurationChangeItem.java index 055923ec..4ab7251e 100644 --- a/android/app/servertransaction/ConfigurationChangeItem.java +++ b/android/app/servertransaction/ConfigurationChangeItem.java @@ -16,32 +16,55 @@ package android.app.servertransaction; +import android.app.ClientTransactionHandler; import android.content.res.Configuration; import android.os.IBinder; import android.os.Parcel; +import java.util.Objects; + /** * App configuration change message. * @hide */ public class ConfigurationChangeItem extends ClientTransactionItem { - private final Configuration mConfiguration; - - public ConfigurationChangeItem(Configuration configuration) { - mConfiguration = new Configuration(configuration); - } + private Configuration mConfiguration; @Override - public void prepare(android.app.ClientTransactionHandler client, IBinder token) { + public void preExecute(android.app.ClientTransactionHandler client, IBinder token) { client.updatePendingConfiguration(mConfiguration); } @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { client.handleConfigurationChanged(mConfiguration); } + + // ObjectPoolItem implementation + + private ConfigurationChangeItem() {} + + /** Obtain an instance initialized with provided params. */ + public static ConfigurationChangeItem obtain(Configuration config) { + ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class); + if (instance == null) { + instance = new ConfigurationChangeItem(); + } + instance.mConfiguration = config; + + return instance; + } + + @Override + public void recycle() { + mConfiguration = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation /** Write to Parcel. */ @@ -75,11 +98,16 @@ public class ConfigurationChangeItem extends ClientTransactionItem { return false; } final ConfigurationChangeItem other = (ConfigurationChangeItem) o; - return mConfiguration.equals(other.mConfiguration); + return Objects.equals(mConfiguration, other.mConfiguration); } @Override public int hashCode() { return mConfiguration.hashCode(); } + + @Override + public String toString() { + return "ConfigurationChangeItem{config=" + mConfiguration + "}"; + } } diff --git a/android/app/servertransaction/DestroyActivityItem.java b/android/app/servertransaction/DestroyActivityItem.java index 38fd5fb6..83da5f33 100644 --- a/android/app/servertransaction/DestroyActivityItem.java +++ b/android/app/servertransaction/DestroyActivityItem.java @@ -29,16 +29,12 @@ import android.os.Trace; */ public class DestroyActivityItem extends ActivityLifecycleItem { - private final boolean mFinished; - private final int mConfigChanges; - - public DestroyActivityItem(boolean finished, int configChanges) { - mFinished = finished; - mConfigChanges = configChanges; - } + private boolean mFinished; + private int mConfigChanges; @Override - public void execute(ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); client.handleDestroyActivity(token, mFinished, mConfigChanges, false /* getNonConfigInstance */); @@ -47,7 +43,31 @@ public class DestroyActivityItem extends ActivityLifecycleItem { @Override public int getTargetState() { - return DESTROYED; + return ON_DESTROY; + } + + + // ObjectPoolItem implementation + + private DestroyActivityItem() {} + + /** Obtain an instance initialized with provided params. */ + public static DestroyActivityItem obtain(boolean finished, int configChanges) { + DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class); + if (instance == null) { + instance = new DestroyActivityItem(); + } + instance.mFinished = finished; + instance.mConfigChanges = configChanges; + + return instance; + } + + @Override + public void recycle() { + mFinished = false; + mConfigChanges = 0; + ObjectPool.recycle(this); } @@ -96,4 +116,10 @@ public class DestroyActivityItem extends ActivityLifecycleItem { result = 31 * result + mConfigChanges; return result; } + + @Override + public String toString() { + return "DestroyActivityItem{finished=" + mFinished + ",mConfigChanges=" + + mConfigChanges + "}"; + } } diff --git a/android/app/servertransaction/LaunchActivityItem.java b/android/app/servertransaction/LaunchActivityItem.java index 417ebac8..7be82bf9 100644 --- a/android/app/servertransaction/LaunchActivityItem.java +++ b/android/app/servertransaction/LaunchActivityItem.java @@ -18,6 +18,7 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.app.ProfilerInfo; import android.app.ResultInfo; @@ -42,68 +43,69 @@ import java.util.Objects; * Request to launch an activity. * @hide */ -public class LaunchActivityItem extends ActivityLifecycleItem { - - private final Intent mIntent; - private final int mIdent; - private final ActivityInfo mInfo; - private final Configuration mCurConfig; - private final Configuration mOverrideConfig; - private final CompatibilityInfo mCompatInfo; - private final String mReferrer; - private final IVoiceInteractor mVoiceInteractor; - private final int mProcState; - private final Bundle mState; - private final PersistableBundle mPersistentState; - private final List<ResultInfo> mPendingResults; - private final List<ReferrerIntent> mPendingNewIntents; - // TODO(lifecycler): use lifecycle request instead of this param. - private final boolean mNotResumed; - private final boolean mIsForward; - private final ProfilerInfo mProfilerInfo; - - public LaunchActivityItem(Intent intent, int ident, ActivityInfo info, - Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, - String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, - PersistableBundle persistentState, List<ResultInfo> pendingResults, - List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, - ProfilerInfo profilerInfo) { - mIntent = intent; - mIdent = ident; - mInfo = info; - mCurConfig = curConfig; - mOverrideConfig = overrideConfig; - mCompatInfo = compatInfo; - mReferrer = referrer; - mVoiceInteractor = voiceInteractor; - mProcState = procState; - mState = state; - mPersistentState = persistentState; - mPendingResults = pendingResults; - mPendingNewIntents = pendingNewIntents; - mNotResumed = notResumed; - mIsForward = isForward; - mProfilerInfo = profilerInfo; - } +public class LaunchActivityItem extends ClientTransactionItem { + + private Intent mIntent; + private int mIdent; + private ActivityInfo mInfo; + private Configuration mCurConfig; + private Configuration mOverrideConfig; + private CompatibilityInfo mCompatInfo; + private String mReferrer; + private IVoiceInteractor mVoiceInteractor; + private int mProcState; + private Bundle mState; + private PersistableBundle mPersistentState; + private List<ResultInfo> mPendingResults; + private List<ReferrerIntent> mPendingNewIntents; + private boolean mIsForward; + private ProfilerInfo mProfilerInfo; @Override - public void prepare(ClientTransactionHandler client, IBinder token) { + public void preExecute(ClientTransactionHandler client, IBinder token) { client.updateProcessState(mProcState, false); client.updatePendingConfiguration(mCurConfig); } @Override - public void execute(ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); - client.handleLaunchActivity(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo, - mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, - mPendingNewIntents, mNotResumed, mIsForward, mProfilerInfo); + ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, + mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, + mPendingResults, mPendingNewIntents, mIsForward, + mProfilerInfo, client); + client.handleLaunchActivity(r, pendingActions); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } + + // ObjectPoolItem implementation + + private LaunchActivityItem() {} + + /** Obtain an instance initialized with provided params. */ + public static LaunchActivityItem obtain(Intent intent, int ident, ActivityInfo info, + Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, + String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, + PersistableBundle persistentState, List<ResultInfo> pendingResults, + List<ReferrerIntent> pendingNewIntents, boolean isForward, ProfilerInfo profilerInfo) { + LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class); + if (instance == null) { + instance = new LaunchActivityItem(); + } + setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer, + voiceInteractor, procState, state, persistentState, pendingResults, + pendingNewIntents, isForward, profilerInfo); + + return instance; + } + @Override - public int getTargetState() { - return mNotResumed ? PAUSED : RESUMED; + public void recycle() { + setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null, + false, null); + ObjectPool.recycle(this); } @@ -119,35 +121,28 @@ public class LaunchActivityItem extends ActivityLifecycleItem { dest.writeTypedObject(mOverrideConfig, flags); dest.writeTypedObject(mCompatInfo, flags); dest.writeString(mReferrer); - dest.writeStrongBinder(mVoiceInteractor != null ? mVoiceInteractor.asBinder() : null); + dest.writeStrongInterface(mVoiceInteractor); dest.writeInt(mProcState); dest.writeBundle(mState); dest.writePersistableBundle(mPersistentState); dest.writeTypedList(mPendingResults, flags); dest.writeTypedList(mPendingNewIntents, flags); - dest.writeBoolean(mNotResumed); dest.writeBoolean(mIsForward); dest.writeTypedObject(mProfilerInfo, flags); } /** Read from Parcel. */ private LaunchActivityItem(Parcel in) { - mIntent = in.readTypedObject(Intent.CREATOR); - mIdent = in.readInt(); - mInfo = in.readTypedObject(ActivityInfo.CREATOR); - mCurConfig = in.readTypedObject(Configuration.CREATOR); - mOverrideConfig = in.readTypedObject(Configuration.CREATOR); - mCompatInfo = in.readTypedObject(CompatibilityInfo.CREATOR); - mReferrer = in.readString(); - mVoiceInteractor = (IVoiceInteractor) in.readStrongBinder(); - mProcState = in.readInt(); - mState = in.readBundle(getClass().getClassLoader()); - mPersistentState = in.readPersistableBundle(getClass().getClassLoader()); - mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR); - mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR); - mNotResumed = in.readBoolean(); - mIsForward = in.readBoolean(); - mProfilerInfo = in.readTypedObject(ProfilerInfo.CREATOR); + setValues(this, in.readTypedObject(Intent.CREATOR), in.readInt(), + in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR), + in.readTypedObject(Configuration.CREATOR), + in.readTypedObject(CompatibilityInfo.CREATOR), in.readString(), + IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(), + in.readBundle(getClass().getClassLoader()), + in.readPersistableBundle(getClass().getClassLoader()), + in.createTypedArrayList(ResultInfo.CREATOR), + in.createTypedArrayList(ReferrerIntent.CREATOR), in.readBoolean(), + in.readTypedObject(ProfilerInfo.CREATOR)); } public static final Creator<LaunchActivityItem> CREATOR = @@ -170,7 +165,9 @@ public class LaunchActivityItem extends ActivityLifecycleItem { return false; } final LaunchActivityItem other = (LaunchActivityItem) o; - return mIntent.filterEquals(other.mIntent) && mIdent == other.mIdent + final boolean intentsEqual = (mIntent == null && other.mIntent == null) + || (mIntent != null && mIntent.filterEquals(other.mIntent)); + return intentsEqual && mIdent == other.mIdent && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig) && Objects.equals(mOverrideConfig, other.mOverrideConfig) && Objects.equals(mCompatInfo, other.mCompatInfo) @@ -179,7 +176,7 @@ public class LaunchActivityItem extends ActivityLifecycleItem { && areBundlesEqual(mPersistentState, other.mPersistentState) && Objects.equals(mPendingResults, other.mPendingResults) && Objects.equals(mPendingNewIntents, other.mPendingNewIntents) - && mNotResumed == other.mNotResumed && mIsForward == other.mIsForward + && mIsForward == other.mIsForward && Objects.equals(mProfilerInfo, other.mProfilerInfo); } @@ -197,14 +194,17 @@ public class LaunchActivityItem extends ActivityLifecycleItem { result = 31 * result + (mPersistentState != null ? mPersistentState.size() : 0); result = 31 * result + Objects.hashCode(mPendingResults); result = 31 * result + Objects.hashCode(mPendingNewIntents); - result = 31 * result + (mNotResumed ? 1 : 0); result = 31 * result + (mIsForward ? 1 : 0); result = 31 * result + Objects.hashCode(mProfilerInfo); return result; } private boolean activityInfoEqual(ActivityInfo other) { - return mInfo.flags == other.flags && mInfo.maxAspectRatio == other.maxAspectRatio + if (mInfo == null) { + return other == null; + } + return other != null && mInfo.flags == other.flags + && mInfo.maxAspectRatio == other.maxAspectRatio && Objects.equals(mInfo.launchToken, other.launchToken) && Objects.equals(mInfo.getComponentName(), other.getComponentName()); } @@ -229,4 +229,38 @@ public class LaunchActivityItem extends ActivityLifecycleItem { } return true; } + + @Override + public String toString() { + return "LaunchActivityItem{intent=" + mIntent + ",ident=" + mIdent + ",info=" + mInfo + + ",curConfig=" + mCurConfig + ",overrideConfig=" + mOverrideConfig + + ",referrer=" + mReferrer + ",procState=" + mProcState + ",state=" + mState + + ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults + + ",pendingNewIntents=" + mPendingNewIntents + ",profilerInfo=" + mProfilerInfo + + "}"; + } + + // Using the same method to set and clear values to make sure we don't forget anything + private static void setValues(LaunchActivityItem instance, Intent intent, int ident, + ActivityInfo info, Configuration curConfig, Configuration overrideConfig, + CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, + int procState, Bundle state, PersistableBundle persistentState, + List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, + boolean isForward, ProfilerInfo profilerInfo) { + instance.mIntent = intent; + instance.mIdent = ident; + instance.mInfo = info; + instance.mCurConfig = curConfig; + instance.mOverrideConfig = overrideConfig; + instance.mCompatInfo = compatInfo; + instance.mReferrer = referrer; + instance.mVoiceInteractor = voiceInteractor; + instance.mProcState = procState; + instance.mState = state; + instance.mPersistentState = persistentState; + instance.mPendingResults = pendingResults; + instance.mPendingNewIntents = pendingNewIntents; + instance.mIsForward = isForward; + instance.mProfilerInfo = profilerInfo; + } } diff --git a/android/app/servertransaction/MoveToDisplayItem.java b/android/app/servertransaction/MoveToDisplayItem.java index ccd80d88..b3dddfb3 100644 --- a/android/app/servertransaction/MoveToDisplayItem.java +++ b/android/app/servertransaction/MoveToDisplayItem.java @@ -18,33 +18,56 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import android.app.ClientTransactionHandler; import android.content.res.Configuration; import android.os.IBinder; import android.os.Parcel; import android.os.Trace; +import java.util.Objects; + /** * Activity move to a different display message. * @hide */ public class MoveToDisplayItem extends ClientTransactionItem { - private final int mTargetDisplayId; - private final Configuration mConfiguration; - - public MoveToDisplayItem(int targetDisplayId, Configuration configuration) { - mTargetDisplayId = targetDisplayId; - mConfiguration = configuration; - } + private int mTargetDisplayId; + private Configuration mConfiguration; @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay"); client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } + // ObjectPoolItem implementation + + private MoveToDisplayItem() {} + + /** Obtain an instance initialized with provided params. */ + public static MoveToDisplayItem obtain(int targetDisplayId, Configuration configuration) { + MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class); + if (instance == null) { + instance = new MoveToDisplayItem(); + } + instance.mTargetDisplayId = targetDisplayId; + instance.mConfiguration = configuration; + + return instance; + } + + @Override + public void recycle() { + mTargetDisplayId = 0; + mConfiguration = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation /** Write to Parcel. */ @@ -80,7 +103,7 @@ public class MoveToDisplayItem extends ClientTransactionItem { } final MoveToDisplayItem other = (MoveToDisplayItem) o; return mTargetDisplayId == other.mTargetDisplayId - && mConfiguration.equals(other.mConfiguration); + && Objects.equals(mConfiguration, other.mConfiguration); } @Override @@ -90,4 +113,10 @@ public class MoveToDisplayItem extends ClientTransactionItem { result = 31 * result + mConfiguration.hashCode(); return result; } + + @Override + public String toString() { + return "MoveToDisplayItem{targetDisplayId=" + mTargetDisplayId + + ",configuration=" + mConfiguration + "}"; + } } diff --git a/android/app/servertransaction/MultiWindowModeChangeItem.java b/android/app/servertransaction/MultiWindowModeChangeItem.java index a0c617fa..c3022d6f 100644 --- a/android/app/servertransaction/MultiWindowModeChangeItem.java +++ b/android/app/servertransaction/MultiWindowModeChangeItem.java @@ -16,10 +16,13 @@ package android.app.servertransaction; +import android.app.ClientTransactionHandler; import android.content.res.Configuration; import android.os.IBinder; import android.os.Parcel; +import java.util.Objects; + /** * Multi-window mode change message. * @hide @@ -28,18 +31,38 @@ import android.os.Parcel; // communicate multi-window mode change with WindowConfiguration. public class MultiWindowModeChangeItem extends ClientTransactionItem { - private final boolean mIsInMultiWindowMode; - private final Configuration mOverrideConfig; + private boolean mIsInMultiWindowMode; + private Configuration mOverrideConfig; + + @Override + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig); + } + + + // ObjectPoolItem implementation - public MultiWindowModeChangeItem(boolean isInMultiWindowMode, + private MultiWindowModeChangeItem() {} + + /** Obtain an instance initialized with provided params. */ + public static MultiWindowModeChangeItem obtain(boolean isInMultiWindowMode, Configuration overrideConfig) { - mIsInMultiWindowMode = isInMultiWindowMode; - mOverrideConfig = overrideConfig; + MultiWindowModeChangeItem instance = ObjectPool.obtain(MultiWindowModeChangeItem.class); + if (instance == null) { + instance = new MultiWindowModeChangeItem(); + } + instance.mIsInMultiWindowMode = isInMultiWindowMode; + instance.mOverrideConfig = overrideConfig; + + return instance; } @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { - client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig); + public void recycle() { + mIsInMultiWindowMode = false; + mOverrideConfig = null; + ObjectPool.recycle(this); } @@ -79,7 +102,7 @@ public class MultiWindowModeChangeItem extends ClientTransactionItem { } final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o; return mIsInMultiWindowMode == other.mIsInMultiWindowMode - && mOverrideConfig.equals(other.mOverrideConfig); + && Objects.equals(mOverrideConfig, other.mOverrideConfig); } @Override @@ -89,4 +112,10 @@ public class MultiWindowModeChangeItem extends ClientTransactionItem { result = 31 * result + mOverrideConfig.hashCode(); return result; } + + @Override + public String toString() { + return "MultiWindowModeChangeItem{isInMultiWindowMode=" + mIsInMultiWindowMode + + ",overrideConfig=" + mOverrideConfig + "}"; + } } diff --git a/android/app/servertransaction/NewIntentItem.java b/android/app/servertransaction/NewIntentItem.java index 61a8965a..7dfde73c 100644 --- a/android/app/servertransaction/NewIntentItem.java +++ b/android/app/servertransaction/NewIntentItem.java @@ -16,9 +16,7 @@ package android.app.servertransaction; -import static android.app.servertransaction.ActivityLifecycleItem.PAUSED; -import static android.app.servertransaction.ActivityLifecycleItem.RESUMED; - +import android.app.ClientTransactionHandler; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -27,6 +25,7 @@ import android.os.Trace; import com.android.internal.content.ReferrerIntent; import java.util.List; +import java.util.Objects; /** * New intent message. @@ -34,32 +33,53 @@ import java.util.List; */ public class NewIntentItem extends ClientTransactionItem { - private final List<ReferrerIntent> mIntents; - private final boolean mPause; - - public NewIntentItem(List<ReferrerIntent> intents, boolean pause) { - mIntents = intents; - mPause = pause; - } + private List<ReferrerIntent> mIntents; + private boolean mPause; - @Override + // TODO(lifecycler): Switch new intent handling to this scheme. + /*@Override public int getPreExecutionState() { - return PAUSED; + return ON_PAUSE; } @Override public int getPostExecutionState() { - return RESUMED; - } + return ON_RESUME; + }*/ @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent"); client.handleNewIntent(token, mIntents, mPause); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } + // ObjectPoolItem implementation + + private NewIntentItem() {} + + /** Obtain an instance initialized with provided params. */ + public static NewIntentItem obtain(List<ReferrerIntent> intents, boolean pause) { + NewIntentItem instance = ObjectPool.obtain(NewIntentItem.class); + if (instance == null) { + instance = new NewIntentItem(); + } + instance.mIntents = intents; + instance.mPause = pause; + + return instance; + } + + @Override + public void recycle() { + mIntents = null; + mPause = false; + ObjectPool.recycle(this); + } + + // Parcelable implementation /** Write to Parcel. */ @@ -95,7 +115,7 @@ public class NewIntentItem extends ClientTransactionItem { return false; } final NewIntentItem other = (NewIntentItem) o; - return mPause == other.mPause && mIntents.equals(other.mIntents); + return mPause == other.mPause && Objects.equals(mIntents, other.mIntents); } @Override @@ -105,4 +125,9 @@ public class NewIntentItem extends ClientTransactionItem { result = 31 * result + mIntents.hashCode(); return result; } + + @Override + public String toString() { + return "NewIntentItem{pause=" + mPause + ",intents=" + mIntents + "}"; + } } diff --git a/android/app/servertransaction/ObjectPool.java b/android/app/servertransaction/ObjectPool.java new file mode 100644 index 00000000..2fec30a0 --- /dev/null +++ b/android/app/servertransaction/ObjectPool.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * An object pool that can provide reused objects if available. + * @hide + */ +class ObjectPool { + + private static final Object sPoolSync = new Object(); + private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap = + new HashMap<>(); + + private static final int MAX_POOL_SIZE = 50; + + /** + * Obtain an instance of a specific class from the pool + * @param itemClass The class of the object we're looking for. + * @return An instance or null if there is none. + */ + public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) { + synchronized (sPoolSync) { + @SuppressWarnings("unchecked") + final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass); + if (itemPool != null && !itemPool.isEmpty()) { + return itemPool.remove(itemPool.size() - 1); + } + return null; + } + } + + /** + * Recycle the object to the pool. The object should be properly cleared before this. + * @param item The object to recycle. + * @see ObjectPoolItem#recycle() + */ + public static <T extends ObjectPoolItem> void recycle(T item) { + synchronized (sPoolSync) { + @SuppressWarnings("unchecked") + ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass()); + if (itemPool == null) { + itemPool = new ArrayList<>(); + sPoolMap.put(item.getClass(), itemPool); + } + // Check if the item is already in the pool + final int size = itemPool.size(); + for (int i = 0; i < size; i++) { + if (itemPool.get(i) == item) { + throw new IllegalStateException("Trying to recycle already recycled item"); + } + } + + if (size < MAX_POOL_SIZE) { + itemPool.add(item); + } + } + } +} diff --git a/android/app/servertransaction/ObjectPoolItem.java b/android/app/servertransaction/ObjectPoolItem.java new file mode 100644 index 00000000..17bd4f30 --- /dev/null +++ b/android/app/servertransaction/ObjectPoolItem.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +/** + * Base interface for all lifecycle items that can be put in object pool. + * @hide + */ +public interface ObjectPoolItem { + /** + * Clear the contents of the item and putting it to a pool. The implementation should call + * {@link ObjectPool#recycle(ObjectPoolItem)} passing itself. + */ + void recycle(); +} diff --git a/android/app/servertransaction/PauseActivityItem.java b/android/app/servertransaction/PauseActivityItem.java index e561a4b5..880fef73 100644 --- a/android/app/servertransaction/PauseActivityItem.java +++ b/android/app/servertransaction/PauseActivityItem.java @@ -18,11 +18,12 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import android.app.ActivityManager; import android.app.ClientTransactionHandler; import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteException; import android.os.Trace; -import android.util.Slog; /** * Request to move an activity to paused state. @@ -32,43 +33,81 @@ public class PauseActivityItem extends ActivityLifecycleItem { private static final String TAG = "PauseActivityItem"; - private final boolean mFinished; - private final boolean mUserLeaving; - private final int mConfigChanges; - private final boolean mDontReport; + private boolean mFinished; + private boolean mUserLeaving; + private int mConfigChanges; + private boolean mDontReport; - private int mLifecycleSeq; + @Override + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); + client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport, + pendingActions); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } - public PauseActivityItem(boolean finished, boolean userLeaving, int configChanges, - boolean dontReport) { - mFinished = finished; - mUserLeaving = userLeaving; - mConfigChanges = configChanges; - mDontReport = dontReport; + @Override + public int getTargetState() { + return ON_PAUSE; } @Override - public void prepare(ClientTransactionHandler client, IBinder token) { - mLifecycleSeq = client.getLifecycleSeq(); - if (DEBUG_ORDER) { - Slog.d(TAG, "Pause transaction for " + client + " received seq: " - + mLifecycleSeq); + public void postExecute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + if (mDontReport) { + return; + } + try { + // TODO(lifecycler): Use interface callback instead of AMS. + ActivityManager.getService().activityPaused(token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } } - @Override - public void execute(ClientTransactionHandler client, IBinder token) { - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport, - mLifecycleSeq); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + + // ObjectPoolItem implementation + + private PauseActivityItem() {} + + /** Obtain an instance initialized with provided params. */ + public static PauseActivityItem obtain(boolean finished, boolean userLeaving, int configChanges, + boolean dontReport) { + PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class); + if (instance == null) { + instance = new PauseActivityItem(); + } + instance.mFinished = finished; + instance.mUserLeaving = userLeaving; + instance.mConfigChanges = configChanges; + instance.mDontReport = dontReport; + + return instance; } - @Override - public int getTargetState() { - return PAUSED; + /** Obtain an instance initialized with default params. */ + public static PauseActivityItem obtain() { + PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class); + if (instance == null) { + instance = new PauseActivityItem(); + } + instance.mFinished = false; + instance.mUserLeaving = false; + instance.mConfigChanges = 0; + instance.mDontReport = true; + + return instance; } + @Override + public void recycle() { + mFinished = false; + mUserLeaving = false; + mConfigChanges = 0; + mDontReport = false; + ObjectPool.recycle(this); + } // Parcelable implementation @@ -122,4 +161,10 @@ public class PauseActivityItem extends ActivityLifecycleItem { result = 31 * result + (mDontReport ? 1 : 0); return result; } + + @Override + public String toString() { + return "PauseActivityItem{finished=" + mFinished + ",userLeaving=" + mUserLeaving + + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport + "}"; + } } diff --git a/android/app/servertransaction/PendingTransactionActions.java b/android/app/servertransaction/PendingTransactionActions.java new file mode 100644 index 00000000..8304c1c5 --- /dev/null +++ b/android/app/servertransaction/PendingTransactionActions.java @@ -0,0 +1,145 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +import static android.app.ActivityThread.DEBUG_MEMORY_TRIM; + +import android.app.ActivityManager; +import android.app.ActivityThread.ActivityClientRecord; +import android.os.Build; +import android.os.Bundle; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.os.TransactionTooLargeException; +import android.util.Log; +import android.util.LogWriter; +import android.util.Slog; + +import com.android.internal.util.IndentingPrintWriter; + +/** + * Container that has data pending to be used at later stages of + * {@link android.app.servertransaction.ClientTransaction}. + * An instance of this class is passed to each individual transaction item, so it can use some + * information from previous steps or add some for the following steps. + * + * @hide + */ +public class PendingTransactionActions { + private boolean mRestoreInstanceState; + private boolean mCallOnPostCreate; + private Bundle mOldState; + private StopInfo mStopInfo; + + public PendingTransactionActions() { + clear(); + } + + /** Reset the state of the instance to default, non-initialized values. */ + public void clear() { + mRestoreInstanceState = false; + mCallOnPostCreate = false; + mOldState = null; + mStopInfo = null; + } + + /** Getter */ + public boolean shouldRestoreInstanceState() { + return mRestoreInstanceState; + } + + public void setRestoreInstanceState(boolean restoreInstanceState) { + mRestoreInstanceState = restoreInstanceState; + } + + /** Getter */ + public boolean shouldCallOnPostCreate() { + return mCallOnPostCreate; + } + + public void setCallOnPostCreate(boolean callOnPostCreate) { + mCallOnPostCreate = callOnPostCreate; + } + + public Bundle getOldState() { + return mOldState; + } + + public void setOldState(Bundle oldState) { + mOldState = oldState; + } + + public StopInfo getStopInfo() { + return mStopInfo; + } + + public void setStopInfo(StopInfo stopInfo) { + mStopInfo = stopInfo; + } + + /** Reports to server about activity stop. */ + public static class StopInfo implements Runnable { + private static final String TAG = "ActivityStopInfo"; + + private ActivityClientRecord mActivity; + private Bundle mState; + private PersistableBundle mPersistentState; + private CharSequence mDescription; + + public void setActivity(ActivityClientRecord activity) { + mActivity = activity; + } + + public void setState(Bundle state) { + mState = state; + } + + public void setPersistentState(PersistableBundle persistentState) { + mPersistentState = persistentState; + } + + public void setDescription(CharSequence description) { + mDescription = description; + } + + @Override + public void run() { + // Tell activity manager we have been stopped. + try { + if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + mActivity); + // TODO(lifecycler): Use interface callback instead of AMS. + ActivityManager.getService().activityStopped( + mActivity.token, mState, mPersistentState, mDescription); + } catch (RemoteException ex) { + // Dump statistics about bundle to help developers debug + final LogWriter writer = new LogWriter(Log.WARN, TAG); + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println("Bundle stats:"); + Bundle.dumpStats(pw, mState); + pw.println("PersistableBundle stats:"); + Bundle.dumpStats(pw, mPersistentState); + + if (ex instanceof TransactionTooLargeException + && mActivity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) { + Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); + return; + } + throw ex.rethrowFromSystemServer(); + } + } + } +} diff --git a/android/app/servertransaction/PipModeChangeItem.java b/android/app/servertransaction/PipModeChangeItem.java index 923839ee..b999cd7e 100644 --- a/android/app/servertransaction/PipModeChangeItem.java +++ b/android/app/servertransaction/PipModeChangeItem.java @@ -16,10 +16,13 @@ package android.app.servertransaction; +import android.app.ClientTransactionHandler; import android.content.res.Configuration; import android.os.IBinder; import android.os.Parcel; +import java.util.Objects; + /** * Picture in picture mode change message. * @hide @@ -28,17 +31,37 @@ import android.os.Parcel; // communicate multi-window mode change with WindowConfiguration. public class PipModeChangeItem extends ClientTransactionItem { - private final boolean mIsInPipMode; - private final Configuration mOverrideConfig; + private boolean mIsInPipMode; + private Configuration mOverrideConfig; + + @Override + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig); + } + + + // ObjectPoolItem implementation - public PipModeChangeItem(boolean isInPipMode, Configuration overrideConfig) { - mIsInPipMode = isInPipMode; - mOverrideConfig = overrideConfig; + private PipModeChangeItem() {} + + /** Obtain an instance initialized with provided params. */ + public static PipModeChangeItem obtain(boolean isInPipMode, Configuration overrideConfig) { + PipModeChangeItem instance = ObjectPool.obtain(PipModeChangeItem.class); + if (instance == null) { + instance = new PipModeChangeItem(); + } + instance.mIsInPipMode = isInPipMode; + instance.mOverrideConfig = overrideConfig; + + return instance; } @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { - client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig); + public void recycle() { + mIsInPipMode = false; + mOverrideConfig = null; + ObjectPool.recycle(this); } @@ -76,7 +99,8 @@ public class PipModeChangeItem extends ClientTransactionItem { return false; } final PipModeChangeItem other = (PipModeChangeItem) o; - return mIsInPipMode == other.mIsInPipMode && mOverrideConfig.equals(other.mOverrideConfig); + return mIsInPipMode == other.mIsInPipMode + && Objects.equals(mOverrideConfig, other.mOverrideConfig); } @Override @@ -86,4 +110,10 @@ public class PipModeChangeItem extends ClientTransactionItem { result = 31 * result + mOverrideConfig.hashCode(); return result; } + + @Override + public String toString() { + return "PipModeChangeItem{isInPipMode=" + mIsInPipMode + + ",overrideConfig=" + mOverrideConfig + "}"; + } } diff --git a/android/app/servertransaction/ResumeActivityItem.java b/android/app/servertransaction/ResumeActivityItem.java index ea31a461..9249c6e8 100644 --- a/android/app/servertransaction/ResumeActivityItem.java +++ b/android/app/servertransaction/ResumeActivityItem.java @@ -18,11 +18,12 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import android.app.ActivityManager; import android.app.ClientTransactionHandler; import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteException; import android.os.Trace; -import android.util.Slog; /** * Request to move an activity to resumed state. @@ -32,37 +33,78 @@ public class ResumeActivityItem extends ActivityLifecycleItem { private static final String TAG = "ResumeActivityItem"; - private final int mProcState; - private final boolean mIsForward; - - private int mLifecycleSeq; - - public ResumeActivityItem(int procState, boolean isForward) { - mProcState = procState; - mIsForward = isForward; - } + private int mProcState; + private boolean mUpdateProcState; + private boolean mIsForward; @Override - public void prepare(ClientTransactionHandler client, IBinder token) { - mLifecycleSeq = client.getLifecycleSeq(); - if (DEBUG_ORDER) { - Slog.d(TAG, "Resume transaction for " + client + " received seq: " - + mLifecycleSeq); + public void preExecute(ClientTransactionHandler client, IBinder token) { + if (mUpdateProcState) { + client.updateProcessState(mProcState, false); } - client.updateProcessState(mProcState, false); } @Override - public void execute(ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); - client.handleResumeActivity(token, true /* clearHide */, mIsForward, - true /* reallyResume */, mLifecycleSeq, "RESUME_ACTIVITY"); + client.handleResumeActivity(token, true /* clearHide */, mIsForward, "RESUME_ACTIVITY"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @Override + public void postExecute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + try { + // TODO(lifecycler): Use interface callback instead of AMS. + ActivityManager.getService().activityResumed(token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + @Override public int getTargetState() { - return RESUMED; + return ON_RESUME; + } + + + // ObjectPoolItem implementation + + private ResumeActivityItem() {} + + /** Obtain an instance initialized with provided params. */ + public static ResumeActivityItem obtain(int procState, boolean isForward) { + ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class); + if (instance == null) { + instance = new ResumeActivityItem(); + } + instance.mProcState = procState; + instance.mUpdateProcState = true; + instance.mIsForward = isForward; + + return instance; + } + + /** Obtain an instance initialized with provided params. */ + public static ResumeActivityItem obtain(boolean isForward) { + ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class); + if (instance == null) { + instance = new ResumeActivityItem(); + } + instance.mProcState = ActivityManager.PROCESS_STATE_UNKNOWN; + instance.mUpdateProcState = false; + instance.mIsForward = isForward; + + return instance; + } + + @Override + public void recycle() { + mProcState = ActivityManager.PROCESS_STATE_UNKNOWN; + mUpdateProcState = false; + mIsForward = false; + ObjectPool.recycle(this); } @@ -72,12 +114,14 @@ public class ResumeActivityItem extends ActivityLifecycleItem { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mProcState); + dest.writeBoolean(mUpdateProcState); dest.writeBoolean(mIsForward); } /** Read from Parcel. */ private ResumeActivityItem(Parcel in) { mProcState = in.readInt(); + mUpdateProcState = in.readBoolean(); mIsForward = in.readBoolean(); } @@ -101,14 +145,22 @@ public class ResumeActivityItem extends ActivityLifecycleItem { return false; } final ResumeActivityItem other = (ResumeActivityItem) o; - return mProcState == other.mProcState && mIsForward == other.mIsForward; + return mProcState == other.mProcState && mUpdateProcState == other.mUpdateProcState + && mIsForward == other.mIsForward; } @Override public int hashCode() { int result = 17; result = 31 * result + mProcState; + result = 31 * result + (mUpdateProcState ? 1 : 0); result = 31 * result + (mIsForward ? 1 : 0); return result; } + + @Override + public String toString() { + return "ResumeActivityItem{procState=" + mProcState + + ",updateProcState=" + mUpdateProcState + ",isForward=" + mIsForward + "}"; + } } diff --git a/android/app/servertransaction/StopActivityItem.java b/android/app/servertransaction/StopActivityItem.java index d62c5077..5c5c3041 100644 --- a/android/app/servertransaction/StopActivityItem.java +++ b/android/app/servertransaction/StopActivityItem.java @@ -22,7 +22,6 @@ import android.app.ClientTransactionHandler; import android.os.IBinder; import android.os.Parcel; import android.os.Trace; -import android.util.Slog; /** * Request to move an activity to stopped state. @@ -32,35 +31,50 @@ public class StopActivityItem extends ActivityLifecycleItem { private static final String TAG = "StopActivityItem"; - private final boolean mShowWindow; - private final int mConfigChanges; + private boolean mShowWindow; + private int mConfigChanges; - private int mLifecycleSeq; - - public StopActivityItem(boolean showWindow, int configChanges) { - mShowWindow = showWindow; - mConfigChanges = configChanges; + @Override + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); + client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @Override - public void prepare(ClientTransactionHandler client, IBinder token) { - mLifecycleSeq = client.getLifecycleSeq(); - if (DEBUG_ORDER) { - Slog.d(TAG, "Stop transaction for " + client + " received seq: " - + mLifecycleSeq); - } + public void postExecute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + client.reportStop(pendingActions); } @Override - public void execute(ClientTransactionHandler client, IBinder token) { - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - client.handleStopActivity(token, mShowWindow, mConfigChanges, mLifecycleSeq); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + public int getTargetState() { + return ON_STOP; + } + + + // ObjectPoolItem implementation + + private StopActivityItem() {} + + /** Obtain an instance initialized with provided params. */ + public static StopActivityItem obtain(boolean showWindow, int configChanges) { + StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class); + if (instance == null) { + instance = new StopActivityItem(); + } + instance.mShowWindow = showWindow; + instance.mConfigChanges = configChanges; + + return instance; } @Override - public int getTargetState() { - return STOPPED; + public void recycle() { + mShowWindow = false; + mConfigChanges = 0; + ObjectPool.recycle(this); } @@ -109,4 +123,10 @@ public class StopActivityItem extends ActivityLifecycleItem { result = 31 * result + mConfigChanges; return result; } + + @Override + public String toString() { + return "StopActivityItem{showWindow=" + mShowWindow + ",configChanges=" + mConfigChanges + + "}"; + } } diff --git a/android/app/servertransaction/TransactionExecutor.java b/android/app/servertransaction/TransactionExecutor.java new file mode 100644 index 00000000..5b0ea6b1 --- /dev/null +++ b/android/app/servertransaction/TransactionExecutor.java @@ -0,0 +1,248 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +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_RESTART; +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.UNDEFINED; + +import android.app.ActivityThread.ActivityClientRecord; +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.util.IntArray; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.List; + +/** + * Class that manages transaction execution in the correct order. + * @hide + */ +public class TransactionExecutor { + + private static final boolean DEBUG_RESOLVER = false; + private static final String TAG = "TransactionExecutor"; + + private ClientTransactionHandler mTransactionHandler; + private PendingTransactionActions mPendingActions = new PendingTransactionActions(); + + // Temp holder for lifecycle path. + // No direct transition between two states should take more than one complete cycle of 6 states. + @ActivityLifecycleItem.LifecycleState + private IntArray mLifecycleSequence = new IntArray(6); + + /** Initialize an instance with transaction handler, that will execute all requested actions. */ + public TransactionExecutor(ClientTransactionHandler clientTransactionHandler) { + mTransactionHandler = clientTransactionHandler; + } + + /** + * Resolve transaction. + * First all callbacks will be executed in the order they appear in the list. If a callback + * requires a certain pre- or post-execution state, the client will be transitioned accordingly. + * Then the client will cycle to the final lifecycle state if provided. Otherwise, it will + * either remain in the initial state, or last state needed by a callback. + */ + public void execute(ClientTransaction transaction) { + final IBinder token = transaction.getActivityToken(); + log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token); + + executeCallbacks(transaction); + + executeLifecycleState(transaction); + mPendingActions.clear(); + log("End resolving transaction"); + } + + /** Cycle through all states requested by callbacks and execute them at proper times. */ + @VisibleForTesting + public void executeCallbacks(ClientTransaction transaction) { + final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); + if (callbacks == null) { + // No callbacks to execute, return early. + return; + } + log("Resolving callbacks"); + + final IBinder token = transaction.getActivityToken(); + ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + final int size = callbacks.size(); + for (int i = 0; i < size; ++i) { + final ClientTransactionItem item = callbacks.get(i); + log("Resolving callback: " + item); + final int preExecutionState = item.getPreExecutionState(); + if (preExecutionState != UNDEFINED) { + cycleToPath(r, preExecutionState); + } + + item.execute(mTransactionHandler, token, mPendingActions); + item.postExecute(mTransactionHandler, token, mPendingActions); + if (r == null) { + // Launch activity request will create an activity record. + r = mTransactionHandler.getActivityClient(token); + } + + final int postExecutionState = item.getPostExecutionState(); + if (postExecutionState != UNDEFINED) { + cycleToPath(r, postExecutionState); + } + } + } + + /** Transition to the final state if requested by the transaction. */ + private void executeLifecycleState(ClientTransaction transaction) { + final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest(); + if (lifecycleItem == null) { + // No lifecycle request, return early. + return; + } + log("Resolving lifecycle state: " + lifecycleItem); + + final IBinder token = transaction.getActivityToken(); + final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + + // Cycle to the state right before the final requested state. + cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */); + + // Execute the final transition with proper parameters. + lifecycleItem.execute(mTransactionHandler, token, mPendingActions); + lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions); + } + + /** Transition the client between states. */ + @VisibleForTesting + public void cycleToPath(ActivityClientRecord r, int finish) { + cycleToPath(r, finish, false /* excludeLastState */); + } + + /** + * Transition the client between states with an option not to perform the last hop in the + * sequence. This is used when resolving lifecycle state request, when the last transition must + * be performed with some specific parameters. + */ + private void cycleToPath(ActivityClientRecord r, int finish, + boolean excludeLastState) { + final int start = r.getLifecycleState(); + log("Cycle from: " + start + " to: " + finish + " excludeLastState:" + excludeLastState); + initLifecyclePath(start, finish, excludeLastState); + performLifecycleSequence(r); + } + + /** Transition the client through previously initialized state sequence. */ + private void performLifecycleSequence(ActivityClientRecord r) { + final int size = mLifecycleSequence.size(); + for (int i = 0, state; i < size; i++) { + state = mLifecycleSequence.get(i); + log("Transitioning to state: " + state); + switch (state) { + case ON_CREATE: + mTransactionHandler.handleLaunchActivity(r, mPendingActions); + break; + case ON_START: + mTransactionHandler.handleStartActivity(r, mPendingActions); + break; + case ON_RESUME: + mTransactionHandler.handleResumeActivity(r.token, false /* clearHide */, + r.isForward, "LIFECYCLER_RESUME_ACTIVITY"); + break; + case ON_PAUSE: + mTransactionHandler.handlePauseActivity(r.token, false /* finished */, + false /* userLeaving */, 0 /* configChanges */, + true /* dontReport */, mPendingActions); + break; + case ON_STOP: + mTransactionHandler.handleStopActivity(r.token, false /* show */, + 0 /* configChanges */, mPendingActions); + break; + case ON_DESTROY: + mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */, + 0 /* configChanges */, false /* getNonConfigInstance */); + break; + case ON_RESTART: + mTransactionHandler.performRestartActivity(r.token, false /* start */); + break; + default: + throw new IllegalArgumentException("Unexpected lifecycle state: " + state); + } + } + } + + /** + * Calculate the path through main lifecycle states for an activity and fill + * @link #mLifecycleSequence} with values starting with the state that follows the initial + * state. + */ + public void initLifecyclePath(int start, int finish, boolean excludeLastState) { + mLifecycleSequence.clear(); + if (finish >= start) { + // just go there + for (int i = start + 1; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } else { // finish < start, can't just cycle down + if (start == ON_PAUSE && finish == ON_RESUME) { + // Special case when we can just directly go to resumed state. + mLifecycleSequence.add(ON_RESUME); + } else if (start <= ON_STOP && finish >= ON_START) { + // Restart and go to required state. + + // Go to stopped state first. + for (int i = start + 1; i <= ON_STOP; i++) { + mLifecycleSequence.add(i); + } + // Restart + mLifecycleSequence.add(ON_RESTART); + // Go to required state + for (int i = ON_START; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } else { + // Relaunch and go to required state + + // Go to destroyed state first. + for (int i = start + 1; i <= ON_DESTROY; i++) { + mLifecycleSequence.add(i); + } + // Go to required state + for (int i = ON_CREATE; i <= finish; i++) { + mLifecycleSequence.add(i); + } + } + } + + // Remove last transition in case we want to perform it with some specific params. + if (excludeLastState && mLifecycleSequence.size() != 0) { + mLifecycleSequence.remove(mLifecycleSequence.size() - 1); + } + } + + @VisibleForTesting + public int[] getLifecycleSequence() { + return mLifecycleSequence.toArray(); + } + + private static void log(String message) { + if (DEBUG_RESOLVER) Slog.d(TAG, message); + } +} diff --git a/android/app/servertransaction/WindowVisibilityItem.java b/android/app/servertransaction/WindowVisibilityItem.java index 8e88b38d..d9956b13 100644 --- a/android/app/servertransaction/WindowVisibilityItem.java +++ b/android/app/servertransaction/WindowVisibilityItem.java @@ -18,6 +18,7 @@ package android.app.servertransaction; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import android.app.ClientTransactionHandler; import android.os.IBinder; import android.os.Parcel; import android.os.Trace; @@ -28,20 +29,39 @@ import android.os.Trace; */ public class WindowVisibilityItem extends ClientTransactionItem { - private final boolean mShowWindow; - - public WindowVisibilityItem(boolean showWindow) { - mShowWindow = showWindow; - } + private boolean mShowWindow; @Override - public void execute(android.app.ClientTransactionHandler client, IBinder token) { + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow"); client.handleWindowVisibility(token, mShowWindow); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } + // ObjectPoolItem implementation + + private WindowVisibilityItem() {} + + /** Obtain an instance initialized with provided params. */ + public static WindowVisibilityItem obtain(boolean showWindow) { + WindowVisibilityItem instance = ObjectPool.obtain(WindowVisibilityItem.class); + if (instance == null) { + instance = new WindowVisibilityItem(); + } + instance.mShowWindow = showWindow; + + return instance; + } + + @Override + public void recycle() { + mShowWindow = false; + ObjectPool.recycle(this); + } + + // Parcelable implementation /** Write to Parcel. */ @@ -82,4 +102,9 @@ public class WindowVisibilityItem extends ClientTransactionItem { public int hashCode() { return 17 + 31 * (mShowWindow ? 1 : 0); } + + @Override + public String toString() { + return "WindowVisibilityItem{showWindow=" + mShowWindow + "}"; + } } diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java index ddc5760a..5c7f6741 100644 --- a/android/app/slice/Slice.java +++ b/android/app/slice/Slice.java @@ -37,6 +37,8 @@ import android.os.RemoteException; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -53,9 +55,21 @@ public final class Slice implements Parcelable { /** * @hide */ - @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED, - HINT_NO_TINT, HINT_PARTIAL}) - public @interface SliceHint{ } + @StringDef(prefix = { "HINT_" }, value = { + HINT_TITLE, + HINT_LIST, + HINT_LIST_ITEM, + HINT_LARGE, + HINT_ACTIONS, + HINT_SELECTED, + HINT_NO_TINT, + HINT_SHORTCUT, + HINT_TOGGLE, + HINT_HORIZONTAL, + HINT_PARTIAL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SliceHint {} /** * The meta-data key that allows an activity to easily be linked directly to a slice. @@ -104,12 +118,15 @@ public final class Slice implements Parcelable { */ public static final String HINT_NO_TINT = "no_tint"; /** - * Hint to indicate that this content should not be shown in larger renderings - * of Slices. This content may be used to populate the shortcut/icon - * format of the slice. - * @hide + * Hint to indicate that this content should only be displayed if the slice is presented + * as a shortcut. + */ + public static final String HINT_SHORTCUT = "shortcut"; + /** + * Hint indicating this content should be shown instead of the normal content when the slice + * is in small format. */ - public static final String HINT_HIDDEN = "hidden"; + public static final String HINT_SUMMARY = "summary"; /** * Hint to indicate that this content has a toggle action associated with it. To indicate that * the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the intent @@ -129,10 +146,14 @@ public final class Slice implements Parcelable { * OS and should not be cached by apps. */ public static final String HINT_PARTIAL = "partial"; + /** + * A hint representing that this item is the max value possible for the slice containing this. + * Used to indicate the maximum integer value for a {@link #SUBTYPE_SLIDER}. + */ + public static final String HINT_MAX = "max"; /** * Key to retrieve an extra added to an intent when a control is changed. - * @hide */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; /** @@ -144,6 +165,25 @@ public final class Slice implements Parcelable { * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}. */ public static final String SUBTYPE_SOURCE = "source"; + /** + * Subtype to tag an item as representing a color. + */ + public static final String SUBTYPE_COLOR = "color"; + /** + * Subtype to tag an item represents a slider. + */ + public static final String SUBTYPE_SLIDER = "slider"; + /** + * Subtype to indicate that this content has a toggle action associated with it. To indicate + * that the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the + * intent associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} + * which can be retrieved to see the new state of the toggle. + */ + public static final String SUBTYPE_TOGGLE = "toggle"; + /** + * Subtype to tag an item representing priority. + */ + public static final String SUBTYPE_PRIORITY = "priority"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; @@ -375,9 +415,10 @@ public final class Slice implements Parcelable { * Add a color to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} + * @deprecated will be removed once supportlib updates */ public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) { - mItems.add(new SliceItem(color, SliceItem.FORMAT_COLOR, subType, hints)); + mItems.add(new SliceItem(color, SliceItem.FORMAT_INT, subType, hints)); return this; } @@ -385,6 +426,7 @@ public final class Slice implements Parcelable { * Add a color to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} + * @deprecated will be removed once supportlib updates */ public Builder addColor(int color, @Nullable String subType, @SliceHint List<String> hints) { @@ -392,6 +434,26 @@ public final class Slice implements Parcelable { } /** + * Add a color to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Builder addInt(int value, @Nullable String subType, @SliceHint String... hints) { + mItems.add(new SliceItem(value, SliceItem.FORMAT_INT, subType, hints)); + return this; + } + + /** + * Add a color to the slice being constructed + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Builder addInt(int value, @Nullable String subType, + @SliceHint List<String> hints) { + return addInt(value, subType, hints.toArray(new String[hints.size()])); + } + + /** * Add a timestamp to the slice being constructed * @param subType Optional template-specific type information * @see {@link SliceItem#getSubType()} @@ -414,6 +476,32 @@ public final class Slice implements Parcelable { } /** + * Add a bundle to the slice being constructed. + * <p>Expected to be used for support library extension, should not be used for general + * development + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Slice.Builder addBundle(Bundle bundle, @Nullable String subType, + @SliceHint String... hints) { + mItems.add(new SliceItem(bundle, SliceItem.FORMAT_BUNDLE, subType, + hints)); + return this; + } + + /** + * Add a bundle to the slice being constructed. + * <p>Expected to be used for support library extension, should not be used for general + * development + * @param subType Optional template-specific type information + * @see {@link SliceItem#getSubType()} + */ + public Slice.Builder addBundle(Bundle bundle, @Nullable String subType, + @SliceHint List<String> hints) { + return addBundle(bundle, subType, hints.toArray(new String[hints.size()])); + } + + /** * Construct the slice. */ public Slice build() { diff --git a/android/app/slice/SliceItem.java b/android/app/slice/SliceItem.java index cdeee357..bcfd413f 100644 --- a/android/app/slice/SliceItem.java +++ b/android/app/slice/SliceItem.java @@ -21,6 +21,7 @@ import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -29,6 +30,8 @@ import android.widget.RemoteViews; import com.android.internal.util.ArrayUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.List; @@ -42,9 +45,10 @@ import java.util.List; * <li>{@link #FORMAT_TEXT}</li> * <li>{@link #FORMAT_IMAGE}</li> * <li>{@link #FORMAT_ACTION}</li> - * <li>{@link #FORMAT_COLOR}</li> + * <li>{@link #FORMAT_INT}</li> * <li>{@link #FORMAT_TIMESTAMP}</li> * <li>{@link #FORMAT_REMOTE_INPUT}</li> + * <li>{@link #FORMAT_BUNDLE}</li> * * The hints that a {@link SliceItem} are a set of strings which annotate * the content. The hints that are guaranteed to be understood by the system @@ -52,11 +56,22 @@ import java.util.List; */ public final class SliceItem implements Parcelable { + private static final String TAG = "SliceItem"; + /** * @hide */ - @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR, - FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT}) + @StringDef(prefix = { "FORMAT_" }, value = { + FORMAT_SLICE, + FORMAT_TEXT, + FORMAT_IMAGE, + FORMAT_ACTION, + FORMAT_INT, + FORMAT_TIMESTAMP, + FORMAT_REMOTE_INPUT, + FORMAT_BUNDLE, + }) + @Retention(RetentionPolicy.SOURCE) public @interface SliceType {} /** @@ -79,7 +94,12 @@ public final class SliceItem implements Parcelable { */ public static final String FORMAT_ACTION = "action"; /** - * A {@link SliceItem} that contains a Color int. + * A {@link SliceItem} that contains an int. + */ + public static final String FORMAT_INT = "int"; + /** + * A {@link SliceItem} that contains an int. + * @deprecated to be removed */ public static final String FORMAT_COLOR = "color"; /** @@ -90,6 +110,10 @@ public final class SliceItem implements Parcelable { * A {@link SliceItem} that contains a {@link RemoteInput}. */ public static final String FORMAT_REMOTE_INPUT = "input"; + /** + * A {@link SliceItem} that contains a {@link Bundle}. + */ + public static final String FORMAT_BUNDLE = "bundle"; /** * @hide @@ -128,20 +152,6 @@ public final class SliceItem implements Parcelable { } /** - * @hide - */ - public void addHint(@Slice.SliceHint String hint) { - mHints = ArrayUtils.appendElement(String.class, mHints, hint); - } - - /** - * @hide - */ - public void removeHint(String hint) { - ArrayUtils.removeElement(String.class, mHints, hint); - } - - /** * Get the format of this SliceItem. * <p> * The format will be one of the following types supported by the platform: @@ -149,9 +159,10 @@ public final class SliceItem implements Parcelable { * <li>{@link #FORMAT_TEXT}</li> * <li>{@link #FORMAT_IMAGE}</li> * <li>{@link #FORMAT_ACTION}</li> - * <li>{@link #FORMAT_COLOR}</li> + * <li>{@link #FORMAT_INT}</li> * <li>{@link #FORMAT_TIMESTAMP}</li> * <li>{@link #FORMAT_REMOTE_INPUT}</li> + * <li>{@link #FORMAT_BUNDLE}</li> * @see #getSubType() () */ public String getFormat() { @@ -178,6 +189,13 @@ public final class SliceItem implements Parcelable { } /** + * @return The parcelable held by this {@link #FORMAT_BUNDLE} SliceItem + */ + public Bundle getBundle() { + return (Bundle) mObj; + } + + /** * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem */ public Icon getIcon() { @@ -206,7 +224,14 @@ public final class SliceItem implements Parcelable { } /** - * @return The color held by this {@link #FORMAT_COLOR} SliceItem + * @return The color held by this {@link #FORMAT_INT} SliceItem + */ + public int getInt() { + return (Integer) mObj; + } + + /** + * @deprecated to be removed. */ public int getColor() { return (Integer) mObj; @@ -299,6 +324,7 @@ public final class SliceItem implements Parcelable { case FORMAT_SLICE: case FORMAT_IMAGE: case FORMAT_REMOTE_INPUT: + case FORMAT_BUNDLE: ((Parcelable) obj).writeToParcel(dest, flags); break; case FORMAT_ACTION: @@ -308,7 +334,7 @@ public final class SliceItem implements Parcelable { case FORMAT_TEXT: TextUtils.writeToParcel((CharSequence) obj, dest, flags); break; - case FORMAT_COLOR: + case FORMAT_INT: dest.writeInt((Integer) obj); break; case FORMAT_TIMESTAMP: @@ -329,12 +355,14 @@ public final class SliceItem implements Parcelable { return new Pair<>( PendingIntent.CREATOR.createFromParcel(in), Slice.CREATOR.createFromParcel(in)); - case FORMAT_COLOR: + case FORMAT_INT: return in.readInt(); case FORMAT_TIMESTAMP: return in.readLong(); case FORMAT_REMOTE_INPUT: return RemoteInput.CREATOR.createFromParcel(in); + case FORMAT_BUNDLE: + return Bundle.CREATOR.createFromParcel(in); } throw new RuntimeException("Unsupported type " + type); } diff --git a/android/app/slice/SliceManager.java b/android/app/slice/SliceManager.java new file mode 100644 index 00000000..0c5f225d --- /dev/null +++ b/android/app/slice/SliceManager.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.slice; + +import android.annotation.NonNull; +import android.annotation.SystemService; +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.ArrayMap; +import android.util.Pair; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Class to handle interactions with {@link Slice}s. + * <p> + * The SliceManager manages permissions and pinned state for slices. + */ +@SystemService(Context.SLICE_SERVICE) +public class SliceManager { + + private final ISliceManager mService; + private final Context mContext; + private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup = + new ArrayMap<>(); + + /** + * @hide + */ + public SliceManager(Context context, Handler handler) throws ServiceNotFoundException { + mContext = context; + mService = ISliceManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.SLICE_SERVICE)); + } + + /** + * Adds a callback to a specific slice uri. + * <p> + * This is a convenience that performs a few slice actions at once. It will put + * the slice in a pinned state since there is a callback attached. It will also + * listen for content changes, when a content change observes, the android system + * will bind the new slice and provide it to all registered {@link SliceCallback}s. + * + * @param uri The uri of the slice being listened to. + * @param callback The listener that should receive the callbacks. + * @param specs The list of supported {@link SliceSpec}s of the callback. + * @see SliceProvider#onSlicePinned(Uri) + */ + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List<SliceSpec> specs) { + registerSliceCallback(uri, callback, specs, Handler.getMain()); + } + + /** + * Adds a callback to a specific slice uri. + * <p> + * This is a convenience that performs a few slice actions at once. It will put + * the slice in a pinned state since there is a callback attached. It will also + * listen for content changes, when a content change observes, the android system + * will bind the new slice and provide it to all registered {@link SliceCallback}s. + * + * @param uri The uri of the slice being listened to. + * @param callback The listener that should receive the callbacks. + * @param specs The list of supported {@link SliceSpec}s of the callback. + * @see SliceProvider#onSlicePinned(Uri) + */ + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List<SliceSpec> specs, Handler handler) { + try { + mService.addSliceListener(uri, mContext.getPackageName(), + getListener(uri, callback, new ISliceListener.Stub() { + @Override + public void onSliceUpdated(Slice s) throws RemoteException { + handler.post(() -> callback.onSliceUpdated(s)); + } + }), specs.toArray(new SliceSpec[specs.size()])); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds a callback to a specific slice uri. + * <p> + * This is a convenience that performs a few slice actions at once. It will put + * the slice in a pinned state since there is a callback attached. It will also + * listen for content changes, when a content change observes, the android system + * will bind the new slice and provide it to all registered {@link SliceCallback}s. + * + * @param uri The uri of the slice being listened to. + * @param callback The listener that should receive the callbacks. + * @param specs The list of supported {@link SliceSpec}s of the callback. + * @see SliceProvider#onSlicePinned(Uri) + */ + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List<SliceSpec> specs, Executor executor) { + try { + mService.addSliceListener(uri, mContext.getPackageName(), + getListener(uri, callback, new ISliceListener.Stub() { + @Override + public void onSliceUpdated(Slice s) throws RemoteException { + executor.execute(() -> callback.onSliceUpdated(s)); + } + }), specs.toArray(new SliceSpec[specs.size()])); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private ISliceListener getListener(Uri uri, SliceCallback callback, + ISliceListener listener) { + Pair<Uri, SliceCallback> key = new Pair<>(uri, callback); + if (mListenerLookup.containsKey(key)) { + try { + mService.removeSliceListener(uri, mContext.getPackageName(), + mListenerLookup.get(key)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + mListenerLookup.put(key, listener); + return listener; + } + + /** + * Removes a callback for a specific slice uri. + * <p> + * Removes the app from the pinned state (if there are no other apps/callbacks pinning it) + * in addition to removing the callback. + * + * @param uri The uri of the slice being listened to + * @param callback The listener that should no longer receive callbacks. + * @see #registerSliceCallback + */ + public void unregisterSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback) { + try { + mService.removeSliceListener(uri, mContext.getPackageName(), + mListenerLookup.remove(new Pair<>(uri, callback))); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Ensures that a slice is in a pinned state. + * <p> + * Pinned state is not persisted across reboots, so apps are expected to re-pin any slices + * they still care about after a reboot. + * + * @param uri The uri of the slice being pinned. + * @param specs The list of supported {@link SliceSpec}s of the callback. + * @see SliceProvider#onSlicePinned(Uri) + */ + public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) { + try { + mService.pinSlice(mContext.getPackageName(), uri, + specs.toArray(new SliceSpec[specs.size()])); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove a pin for a slice. + * <p> + * If the slice has no other pins/callbacks then the slice will be unpinned. + * + * @param uri The uri of the slice being unpinned. + * @see #pinSlice + * @see SliceProvider#onSliceUnpinned(Uri) + */ + public void unpinSlice(@NonNull Uri uri) { + try { + mService.unpinSlice(mContext.getPackageName(), uri); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + public boolean hasSliceAccess() { + try { + return mService.hasSliceAccess(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the current set of specs for a pinned slice. + * <p> + * This is the set of specs supported for a specific pinned slice. It will take + * into account all clients and returns only specs supported by all. + * @see SliceSpec + */ + public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) { + try { + return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Class that listens to changes in {@link Slice}s. + */ + public interface SliceCallback { + + /** + * Called when slice is updated. + * + * @param s The updated slice. + * @see #registerSliceCallback + */ + void onSliceUpdated(Slice s); + } +} diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java index ac5365c3..8483931c 100644 --- a/android/app/slice/SliceProvider.java +++ b/android/app/slice/SliceProvider.java @@ -105,6 +105,14 @@ public abstract class SliceProvider extends ContentProvider { /** * @hide */ + public static final String METHOD_PIN = "pin"; + /** + * @hide + */ + public static final String METHOD_UNPIN = "unpin"; + /** + * @hide + */ public static final String EXTRA_INTENT = "slice_intent"; /** * @hide @@ -143,6 +151,38 @@ public abstract class SliceProvider extends ContentProvider { } /** + * Called to inform an app that a slice has been pinned. + * <p> + * Pinning is a way that slice hosts use to notify apps of which slices + * they care about updates for. When a slice is pinned the content is + * expected to be relatively fresh and kept up to date. + * <p> + * Being pinned does not provide any escalated privileges for the slice + * provider. So apps should do things such as turn on syncing or schedule + * a job in response to a onSlicePinned. + * <p> + * Pinned state is not persisted through a reboot, and apps can expect a + * new call to onSlicePinned for any slices that should remain pinned + * after a reboot occurs. + * + * @param sliceUri The uri of the slice being unpinned. + * @see #onSliceUnpinned(Uri) + */ + public void onSlicePinned(Uri sliceUri) { + } + + /** + * Called to inform an app that a slices is no longer pinned. + * <p> + * This means that no other apps on the device care about updates to this + * slice anymore and therefore it is not important to be updated. Any syncs + * or jobs related to this slice should be cancelled. + * @see #onSlicePinned(Uri) + */ + public void onSliceUnpinned(Uri sliceUri) { + } + + /** * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. * In that case, this method can be called and is expected to return a non-null Uri representing * a slice. Otherwise this will throw {@link UnsupportedOperationException}. @@ -221,6 +261,7 @@ public abstract class SliceProvider extends ContentProvider { getContext().enforceCallingPermission(permission.BIND_SLICE, "Slice binding requires the permission BIND_SLICE"); Intent intent = extras.getParcelable(EXTRA_INTENT); + if (intent == null) return null; Uri uri = onMapIntentToUri(intent); List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); Bundle b = new Bundle(); @@ -231,10 +272,62 @@ public abstract class SliceProvider extends ContentProvider { b.putParcelable(EXTRA_SLICE, null); } return b; + } else if (method.equals(METHOD_PIN)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { + getContext().enforceUriPermission(uri, permission.BIND_SLICE, + permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + "Slice binding requires the permission BIND_SLICE"); + } + handlePinSlice(uri); + } else if (method.equals(METHOD_UNPIN)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { + getContext().enforceUriPermission(uri, permission.BIND_SLICE, + permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + "Slice binding requires the permission BIND_SLICE"); + } + handleUnpinSlice(uri); } return super.call(method, arg, extras); } + private void handlePinSlice(Uri sliceUri) { + if (Looper.myLooper() == Looper.getMainLooper()) { + onSlicePinned(sliceUri); + } else { + CountDownLatch latch = new CountDownLatch(1); + Handler.getMain().post(() -> { + onSlicePinned(sliceUri); + latch.countDown(); + }); + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + private void handleUnpinSlice(Uri sliceUri) { + if (Looper.myLooper() == Looper.getMainLooper()) { + onSliceUnpinned(sliceUri); + } else { + CountDownLatch latch = new CountDownLatch(1); + Handler.getMain().post(() -> { + onSliceUnpinned(sliceUri); + latch.countDown(); + }); + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) { if (Looper.myLooper() == Looper.getMainLooper()) { return onBindSliceStrict(sliceUri, supportedSpecs); diff --git a/android/app/slice/SliceSpec.java b/android/app/slice/SliceSpec.java index 433b67e9..8cc0384c 100644 --- a/android/app/slice/SliceSpec.java +++ b/android/app/slice/SliceSpec.java @@ -103,6 +103,11 @@ public final class SliceSpec implements Parcelable { return mType.equals(other.mType) && mRevision == other.mRevision; } + @Override + public String toString() { + return String.format("SliceSpec{%s,%d}", mType, mRevision); + } + public static final Creator<SliceSpec> CREATOR = new Creator<SliceSpec>() { @Override public SliceSpec createFromParcel(Parcel source) { diff --git a/android/app/timezone/Callback.java b/android/app/timezone/Callback.java index aea80380..e3840be6 100644 --- a/android/app/timezone/Callback.java +++ b/android/app/timezone/Callback.java @@ -30,9 +30,14 @@ import java.lang.annotation.RetentionPolicy; public abstract class Callback { @Retention(RetentionPolicy.SOURCE) - @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_INSTALL_BAD_DISTRO_STRUCTURE, - ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION, ERROR_INSTALL_RULES_TOO_OLD, - ERROR_INSTALL_VALIDATION_ERROR}) + @IntDef(prefix = { "SUCCESS", "ERROR_" }, value = { + SUCCESS, + ERROR_UNKNOWN_FAILURE, + ERROR_INSTALL_BAD_DISTRO_STRUCTURE, + ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION, + ERROR_INSTALL_RULES_TOO_OLD, + ERROR_INSTALL_VALIDATION_ERROR + }) public @interface AsyncResultCode {} /** diff --git a/android/app/timezone/RulesManager.java b/android/app/timezone/RulesManager.java index ad9b698a..0a38eb9a 100644 --- a/android/app/timezone/RulesManager.java +++ b/android/app/timezone/RulesManager.java @@ -69,7 +69,11 @@ public final class RulesManager { private static final boolean DEBUG = false; @Retention(RetentionPolicy.SOURCE) - @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_OPERATION_IN_PROGRESS}) + @IntDef(prefix = { "SUCCESS", "ERROR_" }, value = { + SUCCESS, + ERROR_UNKNOWN_FAILURE, + ERROR_OPERATION_IN_PROGRESS + }) public @interface ResultCode {} /** @@ -105,9 +109,9 @@ public final class RulesManager { */ public RulesState getRulesState() { try { - logDebug("sIRulesManager.getRulesState()"); + logDebug("mIRulesManager.getRulesState()"); RulesState rulesState = mIRulesManager.getRulesState(); - logDebug("sIRulesManager.getRulesState() returned " + rulesState); + logDebug("mIRulesManager.getRulesState() returned " + rulesState); return rulesState; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -131,7 +135,7 @@ public final class RulesManager { ICallback iCallback = new CallbackWrapper(mContext, callback); try { - logDebug("sIRulesManager.requestInstall()"); + logDebug("mIRulesManager.requestInstall()"); return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -151,7 +155,7 @@ public final class RulesManager { public int requestUninstall(byte[] checkToken, Callback callback) { ICallback iCallback = new CallbackWrapper(mContext, callback); try { - logDebug("sIRulesManager.requestUninstall()"); + logDebug("mIRulesManager.requestUninstall()"); return mIRulesManager.requestUninstall(checkToken, iCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -196,7 +200,7 @@ public final class RulesManager { */ public void requestNothing(byte[] checkToken, boolean succeeded) { try { - logDebug("sIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken)); + logDebug("mIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken)); mIRulesManager.requestNothing(checkToken, succeeded); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/android/app/timezone/RulesState.java b/android/app/timezone/RulesState.java index ec247ebf..16309fab 100644 --- a/android/app/timezone/RulesState.java +++ b/android/app/timezone/RulesState.java @@ -63,11 +63,12 @@ import java.lang.annotation.RetentionPolicy; public final class RulesState implements Parcelable { @Retention(RetentionPolicy.SOURCE) - @IntDef({ + @IntDef(prefix = { "STAGED_OPERATION_" }, value = { STAGED_OPERATION_UNKNOWN, STAGED_OPERATION_NONE, STAGED_OPERATION_UNINSTALL, - STAGED_OPERATION_INSTALL }) + STAGED_OPERATION_INSTALL + }) private @interface StagedOperationType {} /** Staged state could not be determined. */ @@ -80,10 +81,11 @@ public final class RulesState implements Parcelable { public static final int STAGED_OPERATION_INSTALL = 3; @Retention(RetentionPolicy.SOURCE) - @IntDef({ + @IntDef(prefix = { "DISTRO_STATUS_" }, value = { DISTRO_STATUS_UNKNOWN, DISTRO_STATUS_NONE, - DISTRO_STATUS_INSTALLED }) + DISTRO_STATUS_INSTALLED + }) private @interface DistroStatus {} /** The current distro status could not be determined. */ diff --git a/android/app/usage/AppStandby.java b/android/app/usage/AppStandby.java deleted file mode 100644 index 6f9fc2fa..00000000 --- a/android/app/usage/AppStandby.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app.usage; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets - * that affect how frequently they can run in the background or perform other battery-consuming - * actions. Buckets will be assigned based on how frequently or when the system thinks the user - * is likely to use the app. - * @hide - */ -public class AppStandby { - - /** The app was used very recently, currently in use or likely to be used very soon. */ - public static final int STANDBY_BUCKET_ACTIVE = 0; - - // Leave some gap in case we want to increase the number of buckets - - /** The app was used recently and/or likely to be used in the next few hours */ - public static final int STANDBY_BUCKET_WORKING_SET = 3; - - // Leave some gap in case we want to increase the number of buckets - - /** The app was used in the last few days and/or likely to be used in the next few days */ - public static final int STANDBY_BUCKET_FREQUENT = 6; - - // Leave some gap in case we want to increase the number of buckets - - /** The app has not be used for several days and/or is unlikely to be used for several days */ - public static final int STANDBY_BUCKET_RARE = 9; - - // Leave some gap in case we want to increase the number of buckets - - /** The app has never been used. */ - public static final int STANDBY_BUCKET_NEVER = 12; - - /** Reason for bucketing -- default initial state */ - public static final String REASON_DEFAULT = "default"; - - /** Reason for bucketing -- timeout */ - public static final String REASON_TIMEOUT = "timeout"; - - /** Reason for bucketing -- usage */ - public static final String REASON_USAGE = "usage"; - - /** Reason for bucketing -- forced by user / shell command */ - public static final String REASON_FORCED = "forced"; - - /** - * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will - * be appended. - */ - public static final String REASON_PREDICTED = "predicted"; - - @IntDef(flag = false, value = { - STANDBY_BUCKET_ACTIVE, - STANDBY_BUCKET_WORKING_SET, - STANDBY_BUCKET_FREQUENT, - STANDBY_BUCKET_RARE, - STANDBY_BUCKET_NEVER, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface StandbyBuckets {} -} diff --git a/android/app/usage/NetworkStats.java b/android/app/usage/NetworkStats.java index 222e9a0e..2e44a630 100644 --- a/android/app/usage/NetworkStats.java +++ b/android/app/usage/NetworkStats.java @@ -129,7 +129,11 @@ public final class NetworkStats implements AutoCloseable { */ public static class Bucket { /** @hide */ - @IntDef({STATE_ALL, STATE_DEFAULT, STATE_FOREGROUND}) + @IntDef(prefix = { "STATE_" }, value = { + STATE_ALL, + STATE_DEFAULT, + STATE_FOREGROUND + }) @Retention(RetentionPolicy.SOURCE) public @interface State {} @@ -164,7 +168,11 @@ public final class NetworkStats implements AutoCloseable { public static final int UID_TETHERING = TrafficStats.UID_TETHERING; /** @hide */ - @IntDef({METERED_ALL, METERED_NO, METERED_YES}) + @IntDef(prefix = { "METERED_" }, value = { + METERED_ALL, + METERED_NO, + METERED_YES + }) @Retention(RetentionPolicy.SOURCE) public @interface Metered {} @@ -187,7 +195,11 @@ public final class NetworkStats implements AutoCloseable { public static final int METERED_YES = 0x2; /** @hide */ - @IntDef({ROAMING_ALL, ROAMING_NO, ROAMING_YES}) + @IntDef(prefix = { "ROAMING_" }, value = { + ROAMING_ALL, + ROAMING_NO, + ROAMING_YES + }) @Retention(RetentionPolicy.SOURCE) public @interface Roaming {} diff --git a/android/app/usage/StorageStatsManager.java b/android/app/usage/StorageStatsManager.java index 3d187ec7..a86c27a0 100644 --- a/android/app/usage/StorageStatsManager.java +++ b/android/app/usage/StorageStatsManager.java @@ -78,6 +78,16 @@ public class StorageStatsManager { return isQuotaSupported(convert(uuid)); } + /** {@hide} */ + @TestApi + public boolean isReservedSupported(@NonNull UUID storageUuid) { + try { + return mService.isReservedSupported(convert(storageUuid), mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Return the total size of the underlying physical media that is hosting * this storage volume. diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java index 8200414f..f04e9074 100644 --- a/android/app/usage/UsageEvents.java +++ b/android/app/usage/UsageEvents.java @@ -110,10 +110,9 @@ public final class UsageEvents implements Parcelable { public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0; /** @hide */ - @IntDef(flag = true, - value = { - FLAG_IS_PACKAGE_INSTANT_APP, - }) + @IntDef(flag = true, prefix = { "FLAG_" }, value = { + FLAG_IS_PACKAGE_INSTANT_APP, + }) @Retention(RetentionPolicy.SOURCE) public @interface EventFlags {} diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java index 3a3e16e0..edb6a74b 100644 --- a/android/app/usage/UsageStatsManager.java +++ b/android/app/usage/UsageStatsManager.java @@ -16,16 +16,18 @@ package android.app.usage; +import android.annotation.IntDef; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.app.usage.AppStandby.StandbyBuckets; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; import java.util.Map; @@ -90,6 +92,76 @@ public final class UsageStatsManager { */ public static final int INTERVAL_COUNT = 4; + + /** + * The app is whitelisted for some reason and the bucket cannot be changed. + * {@hide} + */ + @SystemApi + public static final int STANDBY_BUCKET_EXEMPTED = 5; + + /** + * The app was used very recently, currently in use or likely to be used very soon. + * @see #getAppStandbyBucket() + */ + public static final int STANDBY_BUCKET_ACTIVE = 10; + + /** + * The app was used recently and/or likely to be used in the next few hours. + * @see #getAppStandbyBucket() + */ + public static final int STANDBY_BUCKET_WORKING_SET = 20; + + /** + * The app was used in the last few days and/or likely to be used in the next few days. + * @see #getAppStandbyBucket() + */ + public static final int STANDBY_BUCKET_FREQUENT = 30; + + /** + * The app has not be used for several days and/or is unlikely to be used for several days. + * @see #getAppStandbyBucket() + */ + public static final int STANDBY_BUCKET_RARE = 40; + + /** + * The app has never been used. + * {@hide} + */ + @SystemApi + public static final int STANDBY_BUCKET_NEVER = 50; + + /** {@hide} Reason for bucketing -- default initial state */ + public static final String REASON_DEFAULT = "default"; + + /** {@hide} Reason for bucketing -- timeout */ + public static final String REASON_TIMEOUT = "timeout"; + + /** {@hide} Reason for bucketing -- usage */ + public static final String REASON_USAGE = "usage"; + + /** {@hide} Reason for bucketing -- forced by user / shell command */ + public static final String REASON_FORCED = "forced"; + + /** + * {@hide} + * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will + * be appended. + */ + public static final String REASON_PREDICTED = "predicted"; + + /** @hide */ + @IntDef(flag = false, prefix = { "STANDBY_BUCKET_" }, value = { + STANDBY_BUCKET_EXEMPTED, + STANDBY_BUCKET_ACTIVE, + STANDBY_BUCKET_WORKING_SET, + STANDBY_BUCKET_FREQUENT, + STANDBY_BUCKET_RARE, + STANDBY_BUCKET_NEVER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StandbyBuckets {} + private static final UsageEvents sEmptyResults = new UsageEvents(); private final Context mContext; @@ -237,7 +309,7 @@ public final class UsageStatsManager { } /** - * @hide + * {@hide} */ public void setAppInactive(String packageName, boolean inactive) { try { @@ -248,20 +320,52 @@ public final class UsageStatsManager { } /** - * @hide + * Returns the current standby bucket of the calling app. The system determines the standby + * state of the app based on app usage patterns. Standby buckets determine how much an app will + * be restricted from running background tasks such as jobs, alarms and certain PendingIntent + * callbacks. + * <p>Restrictions increase progressively from {@link #STANDBY_BUCKET_ACTIVE} to + * {@link #STANDBY_BUCKET_RARE}, with {@link #STANDBY_BUCKET_ACTIVE} being the least + * restrictive. The battery level of the device might also affect the restrictions. + * + * @return the current standby bucket of the calling app. One of STANDBY_BUCKET_* constants. */ + public @StandbyBuckets int getAppStandbyBucket() { + try { + return mService.getAppStandbyBucket(mContext.getOpPackageName(), + mContext.getOpPackageName(), + mContext.getUserId()); + } catch (RemoteException e) { + } + return STANDBY_BUCKET_ACTIVE; + } + + /** + * {@hide} + * Returns the current standby bucket of the specified app. The caller must hold the permission + * android.permission.PACKAGE_USAGE_STATS. + * @param packageName the package for which to fetch the current standby bucket. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public @StandbyBuckets int getAppStandbyBucket(String packageName) { try { return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { } - return AppStandby.STANDBY_BUCKET_ACTIVE; + return STANDBY_BUCKET_ACTIVE; } /** - * @hide - * Changes the app standby state to the provided bucket. + * {@hide} + * Changes an app's standby bucket to the provided value. The caller can only set the standby + * bucket for a different app than itself. + * @param packageName the package name of the app to set the bucket for. A SecurityException + * will be thrown if the package name is that of the caller. + * @param bucket the standby bucket to set it to, which should be one of STANDBY_BUCKET_*. + * Setting a standby bucket outside of the range of STANDBY_BUCKET_ACTIVE to + * STANDBY_BUCKET_NEVER will result in a SecurityException. */ @SystemApi @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) @@ -275,6 +379,39 @@ public final class UsageStatsManager { /** * {@hide} + * Returns the current standby bucket of every app that has a bucket assigned to it. + * The caller must hold the permission android.permission.PACKAGE_USAGE_STATS. The key of the + * returned Map is the package name and the value is the bucket assigned to the package. + * @see #getAppStandbyBucket() + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) + public Map<String, Integer> getAppStandbyBuckets() { + try { + return (Map<String, Integer>) mService.getAppStandbyBuckets( + mContext.getOpPackageName(), mContext.getUserId()); + } catch (RemoteException e) { + } + return Collections.EMPTY_MAP; + } + + /** + * {@hide} + * Changes the app standby bucket for multiple apps at once. The Map is keyed by the package + * name and the value is one of STANDBY_BUCKET_*. + * @param appBuckets a map of package name to bucket value. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) + public void setAppStandbyBuckets(Map<String, Integer> appBuckets) { + try { + mService.setAppStandbyBuckets(appBuckets, mContext.getUserId()); + } catch (RemoteException e) { + } + } + + /** + * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app * receiving a high priority message to be able to access the network and acquire wakelocks * even if the device is in power-save mode or the app is currently considered inactive. diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java index 9954484f..4b4fe72f 100644 --- a/android/app/usage/UsageStatsManagerInternal.java +++ b/android/app/usage/UsageStatsManagerInternal.java @@ -16,7 +16,7 @@ package android.app.usage; -import android.app.usage.AppStandby.StandbyBuckets; +import android.app.usage.UsageStatsManager.StandbyBuckets; import android.content.ComponentName; import android.content.res.Configuration; |