summaryrefslogtreecommitdiff
path: root/android/app
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2018-01-03 13:39:41 -0500
committerJustin Klaassen <justinklaassen@google.com>2018-01-03 13:39:41 -0500
commit98fe7819c6d14f4f464a5cac047f9e82dee5da58 (patch)
treea6b8b93eb21e205b27590ab5e2a1fb9efe27f892 /android/app
parent4217cf85c20565a3446a662a7f07f26137b26b7f (diff)
downloadandroid-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')
-rw-r--r--android/app/ActionBar.java23
-rw-r--r--android/app/Activity.java14
-rw-r--r--android/app/ActivityManager.java117
-rw-r--r--android/app/ActivityManagerInternal.java22
-rw-r--r--android/app/ActivityOptions.java16
-rw-r--r--android/app/ActivityThread.java1006
-rw-r--r--android/app/AlarmManager.java22
-rw-r--r--android/app/AppComponentFactory.java112
-rw-r--r--android/app/AppOpsManager.java25
-rw-r--r--android/app/Application.java2
-rw-r--r--android/app/ApplicationPackageManager.java19
-rw-r--r--android/app/ClientTransactionHandler.java46
-rw-r--r--android/app/ContextImpl.java216
-rw-r--r--android/app/DialogFragment.java4
-rw-r--r--android/app/Fragment.java4
-rw-r--r--android/app/FragmentContainer.java3
-rw-r--r--android/app/FragmentController.java3
-rw-r--r--android/app/FragmentHostCallback.java3
-rw-r--r--android/app/FragmentManager.java15
-rw-r--r--android/app/FragmentManagerNonConfig.java3
-rw-r--r--android/app/FragmentTransaction.java10
-rw-r--r--android/app/Instrumentation.java28
-rw-r--r--android/app/KeyguardManager.java8
-rw-r--r--android/app/LauncherActivity.java2
-rw-r--r--android/app/ListFragment.java4
-rw-r--r--android/app/LoadedApk.java104
-rw-r--r--android/app/LoaderManager.java6
-rw-r--r--android/app/LocalActivityManager.java13
-rw-r--r--android/app/Notification.java77
-rw-r--r--android/app/SharedPreferencesImpl.java133
-rw-r--r--android/app/StatusBarManager.java11
-rw-r--r--android/app/SystemServiceRegistry.java28
-rw-r--r--android/app/UiAutomation.java49
-rw-r--r--android/app/UiAutomationConnection.java7
-rw-r--r--android/app/UiModeManager.java6
-rw-r--r--android/app/VrManager.java19
-rw-r--r--android/app/WallpaperInfo.java60
-rw-r--r--android/app/WallpaperManager.java15
-rw-r--r--android/app/WindowConfiguration.java17
-rw-r--r--android/app/admin/DeviceAdminReceiver.java6
-rw-r--r--android/app/admin/DevicePolicyManager.java628
-rw-r--r--android/app/admin/DevicePolicyManagerInternal.java22
-rw-r--r--android/app/admin/PasswordMetrics.java7
-rw-r--r--android/app/admin/SecurityLog.java67
-rw-r--r--android/app/admin/SystemUpdateInfo.java6
-rw-r--r--android/app/admin/SystemUpdatePolicy.java9
-rw-r--r--android/app/assist/AssistStructure.java10
-rw-r--r--android/app/backup/BackupAgent.java13
-rw-r--r--android/app/backup/BackupManager.java53
-rw-r--r--android/app/backup/BackupManagerMonitor.java7
-rw-r--r--android/app/servertransaction/ActivityConfigurationChangeItem.java42
-rw-r--r--android/app/servertransaction/ActivityLifecycleItem.java28
-rw-r--r--android/app/servertransaction/ActivityResultItem.java44
-rw-r--r--android/app/servertransaction/BaseClientRequest.java18
-rw-r--r--android/app/servertransaction/ClientTransaction.java94
-rw-r--r--android/app/servertransaction/ConfigurationChangeItem.java44
-rw-r--r--android/app/servertransaction/DestroyActivityItem.java44
-rw-r--r--android/app/servertransaction/LaunchActivityItem.java178
-rw-r--r--android/app/servertransaction/MoveToDisplayItem.java47
-rw-r--r--android/app/servertransaction/MultiWindowModeChangeItem.java45
-rw-r--r--android/app/servertransaction/NewIntentItem.java57
-rw-r--r--android/app/servertransaction/ObjectPool.java77
-rw-r--r--android/app/servertransaction/ObjectPoolItem.java29
-rw-r--r--android/app/servertransaction/PauseActivityItem.java97
-rw-r--r--android/app/servertransaction/PendingTransactionActions.java145
-rw-r--r--android/app/servertransaction/PipModeChangeItem.java46
-rw-r--r--android/app/servertransaction/ResumeActivityItem.java94
-rw-r--r--android/app/servertransaction/StopActivityItem.java60
-rw-r--r--android/app/servertransaction/TransactionExecutor.java248
-rw-r--r--android/app/servertransaction/WindowVisibilityItem.java37
-rw-r--r--android/app/slice/Slice.java108
-rw-r--r--android/app/slice/SliceItem.java72
-rw-r--r--android/app/slice/SliceManager.java239
-rw-r--r--android/app/slice/SliceProvider.java93
-rw-r--r--android/app/slice/SliceSpec.java5
-rw-r--r--android/app/timezone/Callback.java11
-rw-r--r--android/app/timezone/RulesManager.java16
-rw-r--r--android/app/timezone/RulesState.java10
-rw-r--r--android/app/usage/AppStandby.java83
-rw-r--r--android/app/usage/NetworkStats.java18
-rw-r--r--android/app/usage/StorageStatsManager.java10
-rw-r--r--android/app/usage/UsageEvents.java7
-rw-r--r--android/app/usage/UsageStatsManager.java149
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java2
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;