summaryrefslogtreecommitdiff
path: root/android/app
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2018-04-03 23:21:57 -0400
committerJustin Klaassen <justinklaassen@google.com>2018-04-03 23:21:57 -0400
commit4d01eeaffaa720e4458a118baa137a11614f00f7 (patch)
tree66751893566986236788e3c796a7cc5e90d05f52 /android/app
parenta192cc2a132cb0ee8588e2df755563ec7008c179 (diff)
downloadandroid-28-4d01eeaffaa720e4458a118baa137a11614f00f7.tar.gz
Import Android SDK Platform P [4697573]
/google/data/ro/projects/android/fetch_artifact \ --bid 4697573 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4697573.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: If80578c3c657366cc9cf75f8db13d46e2dd4e077
Diffstat (limited to 'android/app')
-rw-r--r--android/app/Activity.java345
-rw-r--r--android/app/ActivityManager.java188
-rw-r--r--android/app/ActivityManagerInternal.java59
-rw-r--r--android/app/ActivityOptions.java9
-rw-r--r--android/app/ActivityThread.java1206
-rw-r--r--android/app/ActivityView.java11
-rw-r--r--android/app/AlarmManager.java4
-rw-r--r--android/app/AppComponentFactory.java18
-rw-r--r--android/app/AppOpsManager.java273
-rw-r--r--android/app/Application.java80
-rw-r--r--android/app/ApplicationPackageManager.java86
-rw-r--r--android/app/ClientTransactionHandler.java86
-rw-r--r--android/app/ContextImpl.java268
-rw-r--r--android/app/Dialog.java42
-rw-r--r--android/app/EphemeralResolverService.java116
-rw-r--r--android/app/Fragment.java2
-rw-r--r--android/app/GrantedUriPermission.java74
-rw-r--r--android/app/InstantAppResolverService.java139
-rw-r--r--android/app/Instrumentation.java26
-rw-r--r--android/app/KeyguardManager.java4
-rw-r--r--android/app/LoadedApk.java99
-rw-r--r--android/app/LocalActivityManager.java25
-rw-r--r--android/app/Notification.java587
-rw-r--r--android/app/NotificationChannel.java6
-rw-r--r--android/app/NotificationChannelGroup.java4
-rw-r--r--android/app/NotificationManager.java201
-rw-r--r--android/app/PendingIntent.java9
-rw-r--r--android/app/PendingIntentPerfTest.java134
-rw-r--r--android/app/ProcessMemoryState.java88
-rw-r--r--android/app/ProfilerInfo.java9
-rw-r--r--android/app/RemoteAction.java24
-rw-r--r--android/app/RemoteInput.java14
-rw-r--r--android/app/ResourcesManager.java202
-rw-r--r--android/app/SearchManager.java10
-rw-r--r--android/app/Service.java12
-rw-r--r--android/app/StatsManager.java134
-rw-r--r--android/app/StatusBarManager.java6
-rw-r--r--android/app/SystemServiceRegistry.java118
-rw-r--r--android/app/TaskStackBuilder.java6
-rw-r--r--android/app/UiAutomation.java95
-rw-r--r--android/app/WallpaperColors.java16
-rw-r--r--android/app/WallpaperManager.java48
-rw-r--r--android/app/WindowConfiguration.java3
-rw-r--r--android/app/admin/DeviceAdminInfo.java49
-rw-r--r--android/app/admin/DeviceAdminReceiver.java82
-rw-r--r--android/app/admin/DevicePolicyCache.java57
-rw-r--r--android/app/admin/DevicePolicyManager.java624
-rw-r--r--android/app/admin/DevicePolicyManagerInternal.java6
-rw-r--r--android/app/admin/FreezeInterval.java303
-rw-r--r--android/app/admin/SecurityLog.java443
-rw-r--r--android/app/admin/SystemUpdatePolicy.java485
-rw-r--r--android/app/assist/AssistStructure.java80
-rw-r--r--android/app/backup/BackupAgent.java107
-rw-r--r--android/app/backup/BackupDataOutput.java24
-rw-r--r--android/app/backup/BackupManager.java116
-rw-r--r--android/app/backup/BackupManagerMonitor.java1
-rw-r--r--android/app/backup/BackupTransport.java27
-rw-r--r--android/app/backup/FullBackup.java193
-rw-r--r--android/app/backup/FullBackupDataOutput.java32
-rw-r--r--android/app/job/JobInfo.java213
-rw-r--r--android/app/job/JobParameters.java11
-rw-r--r--android/app/job/JobWorkItem.java89
-rw-r--r--android/app/servertransaction/ActivityLifecycleItem.java5
-rw-r--r--android/app/servertransaction/ActivityRelaunchItem.java176
-rw-r--r--android/app/servertransaction/ActivityResultItem.java6
-rw-r--r--android/app/servertransaction/ClientTransactionItem.java6
-rw-r--r--android/app/servertransaction/DestroyActivityItem.java3
-rw-r--r--android/app/servertransaction/NewIntentItem.java5
-rw-r--r--android/app/servertransaction/PauseActivityItem.java5
-rw-r--r--android/app/servertransaction/PendingTransactionActions.java21
-rw-r--r--android/app/servertransaction/ResumeActivityItem.java4
-rw-r--r--android/app/servertransaction/StopActivityItem.java4
-rw-r--r--android/app/servertransaction/TransactionExecutor.java114
-rw-r--r--android/app/servertransaction/TransactionExecutorHelper.java246
-rw-r--r--android/app/slice/Slice.java257
-rw-r--r--android/app/slice/SliceItem.java19
-rw-r--r--android/app/slice/SliceManager.java384
-rw-r--r--android/app/slice/SliceMetrics.java72
-rw-r--r--android/app/slice/SliceProvider.java228
-rw-r--r--android/app/slice/SliceSpec.java2
-rw-r--r--android/app/timezone/RulesManager.java28
-rw-r--r--android/app/usage/AppStandbyInfo.java64
-rw-r--r--android/app/usage/EventStats.java181
-rw-r--r--android/app/usage/NetworkStats.java41
-rw-r--r--android/app/usage/NetworkStatsManager.java78
-rw-r--r--android/app/usage/TimeSparseArray.java27
-rw-r--r--android/app/usage/UsageEvents.java122
-rw-r--r--android/app/usage/UsageStats.java20
-rw-r--r--android/app/usage/UsageStatsManager.java323
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java41
90 files changed, 7731 insertions, 2579 deletions
diff --git a/android/app/Activity.java b/android/app/Activity.java
index cd029c06..20149dec 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -114,6 +114,7 @@ import android.view.Window.WindowControllerCallback;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
@@ -123,17 +124,20 @@ import android.widget.Toast;
import android.widget.Toolbar;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ToolbarActionBar;
import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.DecorView;
import com.android.internal.policy.PhoneWindow;
+import dalvik.system.VMRuntime;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -421,9 +425,15 @@ import java.util.List;
* vs. those targeting prior platforms. Starting with Honeycomb, an application
* is not in the killable state until its {@link #onStop} has returned. This
* impacts when {@link #onSaveInstanceState(Bundle)} may be called (it may be
- * safely called after {@link #onPause()} and allows and application to safely
+ * safely called after {@link #onPause()}) and allows an application to safely
* wait until {@link #onStop()} to save persistent state.</p>
*
+ * <p class="note">For applications targeting platforms starting with
+ * {@link android.os.Build.VERSION_CODES#P} {@link #onSaveInstanceState(Bundle)}
+ * will always be called after {@link #onStop}, so an application may safely
+ * perform fragment transactions in {@link #onStop} and will be able to save
+ * persistent state later.</p>
+ *
* <p>For those methods that are not marked as being killable, the activity's
* process will not be killed by the system starting from the time the method
* is called and continuing after it returns. Thus an activity is in the killable
@@ -647,13 +657,13 @@ import java.util.List;
* <a name="ProcessLifecycle"></a>
* <h3>Process Lifecycle</h3>
*
- * <p>The Android system attempts to keep application process around for as
+ * <p>The Android system attempts to keep an application process around for as
* long as possible, but eventually will need to remove old processes when
- * memory runs low. As described in <a href="#ActivityLifecycle">Activity
+ * memory runs low. As described in <a href="#ActivityLifecycle">Activity
* Lifecycle</a>, the decision about which process to remove is intimately
- * tied to the state of the user's interaction with it. In general, there
+ * tied to the state of the user's interaction with it. In general, there
* are four states a process can be in based on the activities running in it,
- * listed here in order of importance. The system will kill less important
+ * listed here in order of importance. The system will kill less important
* processes (the last ones) before it resorts to killing more important
* processes (the first ones).
*
@@ -742,6 +752,14 @@ public class Activity extends ContextThemeWrapper
private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui";
+ private static final int LOG_AM_ON_CREATE_CALLED = 30057;
+ private static final int LOG_AM_ON_START_CALLED = 30059;
+ private static final int LOG_AM_ON_RESUME_CALLED = 30022;
+ private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+ private static final int LOG_AM_ON_STOP_CALLED = 30049;
+ private static final int LOG_AM_ON_RESTART_CALLED = 30058;
+ private static final int LOG_AM_ON_DESTROY_CALLED = 30060;
+
private static class ManagedDialog {
Dialog mDialog;
Bundle mArgs;
@@ -978,9 +996,9 @@ public class Activity extends ContextThemeWrapper
* cursors for data being displayed, etc.
*
* <p>You can call {@link #finish} from within this function, in
- * which case onDestroy() will be immediately called without any of the rest
- * of the activity lifecycle ({@link #onStart}, {@link #onResume},
- * {@link #onPause}, etc) executing.
+ * which case onDestroy() will be immediately called after {@link #onCreate} without any of the
+ * rest of the activity lifecycle ({@link #onStart}, {@link #onResume}, {@link #onPause}, etc)
+ * executing.
*
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
@@ -1383,6 +1401,7 @@ public class Activity extends ContextThemeWrapper
*
* {@hide}
*/
+ @Override
public int getNextAutofillId() {
if (mLastAutofillId == Integer.MAX_VALUE - 1) {
mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
@@ -1394,6 +1413,14 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @hide
+ */
+ @Override
+ public AutofillId autofillClientGetNextAutofillId() {
+ return new AutofillId(getNextAutofillId());
+ }
+
+ /**
* Check whether this activity is running as part of a voice interaction with the user.
* If true, it should perform its interaction with the user through the
* {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
@@ -1576,8 +1603,11 @@ public class Activity extends ContextThemeWrapper
* call through to the default implementation, otherwise be prepared to save
* all of the state of each view yourself.
*
- * <p>If called, this method will occur before {@link #onStop}. There are
- * no guarantees about whether it will occur before or after {@link #onPause}.
+ * <p>If called, this method will occur after {@link #onStop} for applications
+ * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}.
+ * For applications targeting earlier platform versions this method will occur
+ * before {@link #onStop} and there are no guarantees about whether it will
+ * occur before or after {@link #onPause}.
*
* @param outState Bundle in which to place your saved state.
*
@@ -1723,7 +1753,7 @@ public class Activity extends ContextThemeWrapper
*
* <p>This callback and {@link #onUserInteraction} are intended to help
* activities manage status bar notifications intelligently; specifically,
- * for helping activities determine the proper time to cancel a notfication.
+ * for helping activities determine the proper time to cancel a notification.
*
* @see #onUserInteraction()
*/
@@ -1731,32 +1761,16 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Generate a new thumbnail for this activity. This method is called before
- * pausing the activity, and should draw into <var>outBitmap</var> the
- * imagery for the desired thumbnail in the dimensions of that bitmap. It
- * can use the given <var>canvas</var>, which is configured to draw into the
- * bitmap, for rendering if desired.
- *
- * <p>The default implementation returns fails and does not draw a thumbnail;
- * this will result in the platform creating its own thumbnail if needed.
- *
- * @param outBitmap The bitmap to contain the thumbnail.
- * @param canvas Can be used to render into the bitmap.
- *
- * @return Return true if you have drawn into the bitmap; otherwise after
- * you return it will be filled with a default thumbnail.
- *
- * @see #onCreateDescription
- * @see #onSaveInstanceState
- * @see #onPause
+ * @deprecated Method doesn't do anything and will be removed in the future.
*/
+ @Deprecated
public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
return false;
}
/**
* Generate a new description for this activity. This method is called
- * before pausing the activity and can, if desired, return some textual
+ * before stopping the activity and can, if desired, return some textual
* description of its current state to be displayed to the user.
*
* <p>The default implementation returns null, which will cause you to
@@ -1767,9 +1781,8 @@ public class Activity extends ContextThemeWrapper
* @return A description of what the user is doing. It should be short and
* sweet (only a few words).
*
- * @see #onCreateThumbnail
* @see #onSaveInstanceState
- * @see #onPause
+ * @see #onStop
*/
@Nullable
public CharSequence onCreateDescription() {
@@ -1905,7 +1918,7 @@ public class Activity extends ContextThemeWrapper
if (isFinishing()) {
if (mAutoFillResetNeeded) {
- getAutofillManager().onActivityFinished();
+ getAutofillManager().onActivityFinishing();
} else if (mIntent != null
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
// Activity was launched when user tapped a link in the Autofill Save UI - since
@@ -2136,11 +2149,15 @@ public class Activity extends ContextThemeWrapper
* @param params non-null parameters to be combined with previously set parameters when entering
* picture-in-picture.
*
- * @return true if the system puts this activity into picture-in-picture mode or was already
- * in picture-in-picture mode (@see {@link #isInPictureInPictureMode())
+ * @return true if the system successfully put this activity into picture-in-picture mode or was
+ * already in picture-in-picture mode (@see {@link #isInPictureInPictureMode()). If the device
+ * does not support picture-in-picture, return false.
*/
public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) {
try {
+ if (!deviceSupportsPictureInPictureMode()) {
+ return false;
+ }
if (params == null) {
throw new IllegalArgumentException("Expected non-null picture-in-picture params");
}
@@ -2168,6 +2185,9 @@ public class Activity extends ContextThemeWrapper
*/
public void setPictureInPictureParams(@NonNull PictureInPictureParams params) {
try {
+ if (!deviceSupportsPictureInPictureMode()) {
+ return;
+ }
if (params == null) {
throw new IllegalArgumentException("Expected non-null picture-in-picture params");
}
@@ -2190,6 +2210,13 @@ public class Activity extends ContextThemeWrapper
}
}
+ /**
+ * @return Whether this device supports picture-in-picture.
+ */
+ private boolean deviceSupportsPictureInPictureMode() {
+ return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
+ }
+
void dispatchMovedToDisplay(int displayId, Configuration config) {
updateDisplay(displayId);
onMovedToDisplay(displayId, config);
@@ -4135,9 +4162,10 @@ public class Activity extends ContextThemeWrapper
* <p>You can override this function to force global search, e.g. in response to a dedicated
* search key, or to block search entirely (by simply returning false).
*
- * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_TELEVISION}, the default
- * implementation changes to simply return false and you must supply your own custom
- * implementation if you want to support search.</p>
+ * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_TELEVISION} or
+ * {@link Configuration#UI_MODE_TYPE_WATCH}, the default implementation changes to simply
+ * return false and you must supply your own custom implementation if you want to support
+ * search.
*
* @param searchEvent The {@link SearchEvent} that signaled this search.
* @return Returns {@code true} if search launched, and {@code false} if the activity does
@@ -4157,8 +4185,10 @@ public class Activity extends ContextThemeWrapper
* @see #onSearchRequested(SearchEvent)
*/
public boolean onSearchRequested() {
- if ((getResources().getConfiguration().uiMode&Configuration.UI_MODE_TYPE_MASK)
- != Configuration.UI_MODE_TYPE_TELEVISION) {
+ final int uiMode = getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK;
+ if (uiMode != Configuration.UI_MODE_TYPE_TELEVISION
+ && uiMode != Configuration.UI_MODE_TYPE_WATCH) {
startSearch(null, false, null, false);
return true;
} else {
@@ -4187,6 +4217,9 @@ public class Activity extends ContextThemeWrapper
* is to inject specific data such as context data, it is preferred to <i>override</i>
* onSearchRequested(), so that any callers to it will benefit from the override.
*
+ * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_WATCH}, use of this API is
+ * not supported.
+ *
* @param initialQuery Any non-null non-empty string will be inserted as
* pre-entered text in the search query box.
* @param selectInitialQuery If true, the initial query will be preselected, which means that
@@ -4697,7 +4730,6 @@ public class Activity extends ContextThemeWrapper
* their launch had come from the original activity.
* @param intent The Intent to start.
* @param options ActivityOptions or null.
- * @param permissionToken Token received from the system that permits this call to be made.
* @param ignoreTargetSecurity If true, the activity manager will not check whether the
* caller it is doing the start is, is actually allowed to start the target activity.
* If you set this to true, you must set an explicit component in the Intent and do any
@@ -4706,7 +4738,7 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
- IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
+ boolean ignoreTargetSecurity, int userId) {
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
@@ -4714,7 +4746,7 @@ public class Activity extends ContextThemeWrapper
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
- intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
+ intent, -1, options, ignoreTargetSecurity, userId);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, -1, ar.getResultCode(),
@@ -5545,13 +5577,7 @@ public class Activity extends ContextThemeWrapper
if (mParent != null) {
throw new IllegalStateException("Can only be called on top-level activity");
}
- if (Looper.myLooper() != mMainThread.getLooper()) {
- throw new IllegalStateException("Must be called from main thread");
- }
- try {
- ActivityManager.getService().requestActivityRelaunch(mToken);
- } catch (RemoteException e) {
- }
+ mMainThread.handleRelaunchActivityLocally(mToken);
}
/**
@@ -5809,7 +5835,7 @@ public class Activity extends ContextThemeWrapper
ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
mParent == null ? mToken : mParent.mToken,
mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null,
- UserHandle.myUserId());
+ getUserId());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
// Empty
@@ -5938,12 +5964,16 @@ public class Activity extends ContextThemeWrapper
*
* @return Returns the complete component name for this activity
*/
- @Override
- public ComponentName getComponentName()
- {
+ public ComponentName getComponentName() {
return mComponent;
}
+ /** @hide */
+ @Override
+ public final ComponentName autofillClientGetComponentName() {
+ return getComponentName();
+ }
+
/**
* Retrieve a {@link SharedPreferences} object for accessing preferences
* that are private to this activity. This simply calls the underlying
@@ -6239,7 +6269,6 @@ public class Activity extends ContextThemeWrapper
*
* @param action the action to run on the UI thread
*/
- @Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
@@ -6248,6 +6277,12 @@ public class Activity extends ContextThemeWrapper
}
}
+ /** @hide */
+ @Override
+ public final void autofillClientRunOnUiThread(Runnable action) {
+ runOnUiThread(action);
+ }
+
/**
* Standard implementation of
* {@link android.view.LayoutInflater.Factory#onCreateView} used when
@@ -6324,12 +6359,16 @@ public class Activity extends ContextThemeWrapper
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
- final AutofillManager afm = mAutofillManager;
+ final AutofillManager afm = getAutofillManager();
if (afm != null) {
+ writer.print(prefix); writer.print("Autofill Compat Mode: ");
+ writer.println(isAutofillCompatibilityEnabled());
afm.dump(prefix, writer);
} else {
writer.print(prefix); writer.println("No AutofillManager");
}
+
+ ResourcesManager.getInstance().dump(prefix, writer);
}
/**
@@ -7053,6 +7092,18 @@ public class Activity extends ContextThemeWrapper
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
+
+ setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
+ enableAutofillCompatibilityIfNeeded();
+ }
+
+ private void enableAutofillCompatibilityIfNeeded() {
+ if (isAutofillCompatibilityEnabled()) {
+ final AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.enableCompatibilityMode();
+ }
+ }
}
/** @hide */
@@ -7060,6 +7111,12 @@ public class Activity extends ContextThemeWrapper
return mParent != null ? mParent.getActivityToken() : mToken;
}
+ /** @hide */
+ @VisibleForTesting
+ public final ActivityThread getActivityThread() {
+ return mMainThread;
+ }
+
final void performCreate(Bundle icicle) {
performCreate(icicle, null);
}
@@ -7072,6 +7129,7 @@ public class Activity extends ContextThemeWrapper
} else {
onCreate(icicle);
}
+ writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate");
mActivityTransitionState.readState(icicle);
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
@@ -7085,12 +7143,14 @@ public class Activity extends ContextThemeWrapper
onNewIntent(intent);
}
- final void performStart() {
+ final void performStart(String reason) {
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
mInstrumentation.callActivityOnStart(this);
+ writeEventLog(LOG_AM_ON_START_CALLED, reason);
+
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
@@ -7099,11 +7159,12 @@ public class Activity extends ContextThemeWrapper
mFragments.dispatchStart();
mFragments.reportLoaderStart();
- // This property is set for all builds except final release
- boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1;
boolean isAppDebuggable =
(mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ // This property is set for all non-user builds except final release
+ boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1;
+
if (isAppDebuggable || isDlwarningEnabled) {
String dlwarning = getDlWarning();
if (dlwarning != null) {
@@ -7124,6 +7185,31 @@ public class Activity extends ContextThemeWrapper
}
}
+ // This property is set for all non-user builds except final release
+ boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1;
+
+ if (isAppDebuggable || isApiWarningEnabled) {
+ if (!mMainThread.mHiddenApiWarningShown && VMRuntime.getRuntime().hasUsedHiddenApi()) {
+ // Only show the warning once per process.
+ mMainThread.mHiddenApiWarningShown = true;
+
+ String appName = getApplicationInfo().loadLabel(getPackageManager())
+ .toString();
+ String warning = "Detected problems with API compatibility\n"
+ + "(visit g.co/dev/appcompat for more info)";
+ if (isAppDebuggable) {
+ new AlertDialog.Builder(this)
+ .setTitle(appName)
+ .setMessage(warning)
+ .setPositiveButton(android.R.string.ok, null)
+ .setCancelable(false)
+ .show();
+ } else {
+ Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
mActivityTransitionState.enterReady(this);
}
@@ -7133,7 +7219,7 @@ public class Activity extends ContextThemeWrapper
* The option to not start immediately is needed in case a transaction with
* multiple lifecycle transitions is in progress.
*/
- final void performRestart(boolean start) {
+ final void performRestart(boolean start, String reason) {
mCanEnterPictureInPicture = true;
mFragments.noteStateNotSaved();
@@ -7166,19 +7252,20 @@ public class Activity extends ContextThemeWrapper
mCalled = false;
mInstrumentation.callActivityOnRestart(this);
+ writeEventLog(LOG_AM_ON_RESTART_CALLED, reason);
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onRestart()");
}
if (start) {
- performStart();
+ performStart(reason);
}
}
}
- final void performResume(boolean followedByPause) {
- performRestart(true /* start */);
+ final void performResume(boolean followedByPause, String reason) {
+ performRestart(true /* start */, reason);
mFragments.execPendingActions();
@@ -7197,6 +7284,7 @@ public class Activity extends ContextThemeWrapper
mCalled = false;
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this);
+ writeEventLog(LOG_AM_ON_RESUME_CALLED, reason);
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
@@ -7233,6 +7321,7 @@ public class Activity extends ContextThemeWrapper
mFragments.dispatchPause();
mCalled = false;
onPause();
+ writeEventLog(LOG_AM_ON_PAUSE_CALLED, "performPause");
mResumed = false;
if (!mCalled && getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.GINGERBREAD) {
@@ -7240,7 +7329,6 @@ public class Activity extends ContextThemeWrapper
"Activity " + mComponent.toShortString() +
" did not call through to super.onPause()");
}
- mResumed = false;
}
final void performUserLeaving() {
@@ -7248,7 +7336,7 @@ public class Activity extends ContextThemeWrapper
onUserLeaveHint();
}
- final void performStop(boolean preserveWindow) {
+ final void performStop(boolean preserveWindow, String reason) {
mDoReportFullyDrawn = false;
mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
@@ -7271,6 +7359,7 @@ public class Activity extends ContextThemeWrapper
mCalled = false;
mInstrumentation.callActivityOnStop(this);
+ writeEventLog(LOG_AM_ON_STOP_CALLED, reason);
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
@@ -7298,6 +7387,7 @@ public class Activity extends ContextThemeWrapper
mWindow.destroy();
mFragments.dispatchDestroy();
onDestroy();
+ writeEventLog(LOG_AM_ON_DESTROY_CALLED, "performDestroy");
mFragments.doLoaderDestroy();
if (mVoiceInteractor != null) {
mVoiceInteractor.detachActivity();
@@ -7523,7 +7613,7 @@ public class Activity extends ContextThemeWrapper
/** @hide */
@Override
- final public void autofillCallbackAuthenticate(int authenticationId, IntentSender intent,
+ public final void autofillClientAuthenticate(int authenticationId, IntentSender intent,
Intent fillInIntent) {
try {
startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
@@ -7535,13 +7625,13 @@ public class Activity extends ContextThemeWrapper
/** @hide */
@Override
- final public void autofillCallbackResetableStateAvailable() {
+ public final void autofillClientResetableStateAvailable() {
mAutoFillResetNeeded = true;
}
/** @hide */
@Override
- final public boolean autofillCallbackRequestShowFillUi(@NonNull View anchor, int width,
+ public final boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width,
int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
final boolean wasShowing;
@@ -7558,7 +7648,20 @@ public class Activity extends ContextThemeWrapper
/** @hide */
@Override
- final public boolean autofillCallbackRequestHideFillUi() {
+ public final void autofillClientDispatchUnhandledKey(@NonNull View anchor,
+ @NonNull KeyEvent keyEvent) {
+ ViewRootImpl rootImpl = anchor.getViewRootImpl();
+ if (rootImpl != null) {
+ // dont care if anchorView is current focus, for example a custom view may only receive
+ // touchEvent, not focusable but can still trigger autofill window. The Key handling
+ // might be inside parent of the custom view.
+ rootImpl.dispatchKeyFromAutofill(keyEvent);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public final boolean autofillClientRequestHideFillUi() {
if (mAutofillPopupWindow == null) {
return false;
}
@@ -7569,8 +7672,16 @@ public class Activity extends ContextThemeWrapper
/** @hide */
@Override
- @NonNull public View[] findViewsByAutofillIdTraversal(@NonNull int[] viewIds) {
- final View[] views = new View[viewIds.length];
+ public final boolean autofillClientIsFillUiShowing() {
+ return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing();
+ }
+
+ /** @hide */
+ @Override
+ @NonNull
+ public final View[] autofillClientFindViewsByAutofillIdTraversal(
+ @NonNull AutofillId[] autofillId) {
+ final View[] views = new View[autofillId.length];
final ArrayList<ViewRootImpl> roots =
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
@@ -7578,10 +7689,11 @@ public class Activity extends ContextThemeWrapper
final View rootView = roots.get(rootNum).getView();
if (rootView != null) {
- for (int viewNum = 0; viewNum < viewIds.length; viewNum++) {
+ final int viewCount = autofillId.length;
+ for (int viewNum = 0; viewNum < viewCount; viewNum++) {
if (views[viewNum] == null) {
views[viewNum] = rootView.findViewByAutofillIdTraversal(
- viewIds[viewNum]);
+ autofillId[viewNum].getViewId());
}
}
}
@@ -7592,14 +7704,15 @@ public class Activity extends ContextThemeWrapper
/** @hide */
@Override
- @Nullable public View findViewByAutofillIdTraversal(int viewId) {
+ @Nullable
+ public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
final ArrayList<ViewRootImpl> roots =
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
final View rootView = roots.get(rootNum).getView();
if (rootView != null) {
- final View view = rootView.findViewByAutofillIdTraversal(viewId);
+ final View view = rootView.findViewByAutofillIdTraversal(autofillId.getViewId());
if (view != null) {
return view;
}
@@ -7611,50 +7724,65 @@ public class Activity extends ContextThemeWrapper
/** @hide */
@Override
- @NonNull public boolean[] getViewVisibility(@NonNull int[] viewIds) {
- final boolean[] isVisible = new boolean[viewIds.length];
- final View views[] = findViewsByAutofillIdTraversal(viewIds);
-
- for (int i = 0; i < viewIds.length; i++) {
- View view = views[i];
- if (view == null) {
- isVisible[i] = false;
- continue;
- }
-
- isVisible[i] = true;
-
- // Check if the view is visible by checking all parents
- while (true) {
- if (view instanceof DecorView && view.getViewRootImpl() == view.getParent()) {
- break;
- }
-
- if (view.getVisibility() != View.VISIBLE) {
- isVisible[i] = false;
- break;
+ public final @NonNull boolean[] autofillClientGetViewVisibility(
+ @NonNull AutofillId[] autofillIds) {
+ final int autofillIdCount = autofillIds.length;
+ final boolean[] visible = new boolean[autofillIdCount];
+ for (int i = 0; i < autofillIdCount; i++) {
+ final AutofillId autofillId = autofillIds[i];
+ final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
+ if (view != null) {
+ if (!autofillId.isVirtual()) {
+ visible[i] = view.isVisibleToUser();
+ } else {
+ visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildId());
}
+ }
+ }
+ if (android.view.autofill.Helper.sVerbose) {
+ Log.v(TAG, "autofillClientGetViewVisibility(): " + Arrays.toString(visible));
+ }
+ return visible;
+ }
- if (view.getParent() instanceof View) {
- view = (View) view.getParent();
- } else {
- break;
+ /** @hide */
+ public final @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId,
+ int windowId) {
+ final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance()
+ .getRootViews(getActivityToken());
+ for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+ final View rootView = roots.get(rootNum).getView();
+ if (rootView != null && rootView.getAccessibilityWindowId() == windowId) {
+ final View view = rootView.findViewByAccessibilityIdTraversal(viewId);
+ if (view != null) {
+ return view;
}
}
}
+ return null;
+ }
- return isVisible;
+ /** @hide */
+ @Override
+ public final @Nullable IBinder autofillClientGetActivityToken() {
+ return getActivityToken();
}
/** @hide */
@Override
- public boolean isVisibleForAutofill() {
+ public final boolean autofillClientIsVisibleForAutofill() {
return !mStopped;
}
/** @hide */
@Override
- public boolean isDisablingEnterExitEventForAutofill() {
+ public final boolean autofillClientIsCompatibilityModeEnabled() {
+ return isAutofillCompatibilityEnabled();
+ }
+
+ /** @hide */
+ @Override
+ public final boolean isDisablingEnterExitEventForAutofill() {
return mAutoFillIgnoreFirstResumePause || !mResumed;
}
@@ -7676,7 +7804,6 @@ public class Activity extends ContextThemeWrapper
* @param disable {@code true} to disable preview screenshots; {@code false} otherwise.
* @hide
*/
- @SystemApi
public void setDisablePreviewScreenshots(boolean disable) {
try {
ActivityManager.getService().setDisablePreviewScreenshots(mToken, disable);
@@ -7748,6 +7875,12 @@ public class Activity extends ContextThemeWrapper
}
}
+ /** Log a lifecycle event for current user id and component class. */
+ private void writeEventLog(int event, String reason) {
+ EventLog.writeEvent(event, UserHandle.myUserId(), getComponentName().getClassName(),
+ reason);
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 80350584..289a4dd7 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -28,7 +28,6 @@ import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.UriPermission;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
@@ -71,6 +70,7 @@ import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.MemInfoReader;
import com.android.server.LocalServices;
import org.xmlpull.v1.XmlSerializer;
@@ -450,31 +450,6 @@ public class ActivityManager {
*/
public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5;
- /**
- * Extra included on intents that are delegating the call to
- * ActivityManager#startActivityAsCaller to another app. This token is necessary for that call
- * to succeed. Type is IBinder.
- * @hide
- */
- public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN";
-
- /**
- * Extra included on intents that contain an EXTRA_INTENT, with options that the contained
- * intent may want to be started with. Type is Bundle.
- * TODO: remove once the ChooserActivity moves to systemui
- * @hide
- */
- public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS";
-
- /**
- * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the
- * parameter of the same name when starting the contained intent.
- * TODO: remove once the ChooserActivity moves to systemui
- * @hide
- */
- public static final String EXTRA_IGNORE_TARGET_SECURITY =
- "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY";
-
/** @hide User operation call: success! */
public static final int USER_OP_SUCCESS = 0;
@@ -576,18 +551,68 @@ public class ActivityManager {
/** @hide Process does not exist. */
public static final int PROCESS_STATE_NONEXISTENT = 19;
- // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
- // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
+ // NOTE: If PROCESS_STATEs are added, then new fields must be added
+ // to frameworks/base/core/proto/android/app/enums.proto and the following method must
// be updated to correctly map between them.
+ // However, if the current ActivityManager values are merely modified, no update should be made
+ // to enums.proto, to which values can only be added but never modified. Note that the proto
+ // versions do NOT have the ordering restrictions of the ActivityManager process state.
/**
- * Maps ActivityManager.PROCESS_STATE_ values to ProcessState enum.
+ * Maps ActivityManager.PROCESS_STATE_ values to enums.proto ProcessStateEnum value.
*
* @param amInt a process state of the form ActivityManager.PROCESS_STATE_
- * @return the value of the corresponding ActivityManager's ProcessState enum.
+ * @return the value of the corresponding enums.proto ProcessStateEnum value.
* @hide
*/
public static final int processStateAmToProto(int amInt) {
- return amInt * 100;
+ switch (amInt) {
+ case PROCESS_STATE_UNKNOWN:
+ return AppProtoEnums.PROCESS_STATE_UNKNOWN;
+ case PROCESS_STATE_PERSISTENT:
+ return AppProtoEnums.PROCESS_STATE_PERSISTENT;
+ case PROCESS_STATE_PERSISTENT_UI:
+ return AppProtoEnums.PROCESS_STATE_PERSISTENT_UI;
+ case PROCESS_STATE_TOP:
+ return AppProtoEnums.PROCESS_STATE_TOP;
+ case PROCESS_STATE_FOREGROUND_SERVICE:
+ return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE;
+ case PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+ return AppProtoEnums.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ case PROCESS_STATE_IMPORTANT_FOREGROUND:
+ return AppProtoEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ case PROCESS_STATE_IMPORTANT_BACKGROUND:
+ return AppProtoEnums.PROCESS_STATE_IMPORTANT_BACKGROUND;
+ case PROCESS_STATE_TRANSIENT_BACKGROUND:
+ return AppProtoEnums.PROCESS_STATE_TRANSIENT_BACKGROUND;
+ case PROCESS_STATE_BACKUP:
+ return AppProtoEnums.PROCESS_STATE_BACKUP;
+ case PROCESS_STATE_SERVICE:
+ return AppProtoEnums.PROCESS_STATE_SERVICE;
+ case PROCESS_STATE_RECEIVER:
+ return AppProtoEnums.PROCESS_STATE_RECEIVER;
+ case PROCESS_STATE_TOP_SLEEPING:
+ return AppProtoEnums.PROCESS_STATE_TOP_SLEEPING;
+ case PROCESS_STATE_HEAVY_WEIGHT:
+ return AppProtoEnums.PROCESS_STATE_HEAVY_WEIGHT;
+ case PROCESS_STATE_HOME:
+ return AppProtoEnums.PROCESS_STATE_HOME;
+ case PROCESS_STATE_LAST_ACTIVITY:
+ return AppProtoEnums.PROCESS_STATE_LAST_ACTIVITY;
+ case PROCESS_STATE_CACHED_ACTIVITY:
+ return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY;
+ case PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+ case PROCESS_STATE_CACHED_RECENT:
+ return AppProtoEnums.PROCESS_STATE_CACHED_RECENT;
+ case PROCESS_STATE_CACHED_EMPTY:
+ return AppProtoEnums.PROCESS_STATE_CACHED_EMPTY;
+ case PROCESS_STATE_NONEXISTENT:
+ return AppProtoEnums.PROCESS_STATE_NONEXISTENT;
+ default:
+ // ActivityManager process state (amInt)
+ // could not be mapped to an AppProtoEnums ProcessState state.
+ return AppProtoEnums.PROCESS_STATE_UNKNOWN_TO_PROTO;
+ }
}
/** @hide The lowest process state number */
@@ -934,8 +959,21 @@ public class ActivityManager {
* @hide
*/
static public boolean isHighEndGfx() {
- return !isLowRamDeviceStatic() &&
- !Resources.getSystem().getBoolean(com.android.internal.R.bool.config_avoidGfxAccel);
+ return !isLowRamDeviceStatic()
+ && !RoSystemProperties.CONFIG_AVOID_GFX_ACCEL
+ && !Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_avoidGfxAccel);
+ }
+
+ /**
+ * Return the total number of bytes of RAM this device has.
+ * @hide
+ */
+ @TestApi
+ public long getTotalRam() {
+ MemInfoReader memreader = new MemInfoReader();
+ memreader.readMemInfo();
+ return memreader.getTotalSize();
}
/**
@@ -1667,7 +1705,7 @@ public class ActivityManager {
if (maxNum < 0) {
throw new IllegalArgumentException("The requested number of tasks should be >= 0");
}
- return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
+ return getService().getRecentTasks(maxNum, flags, mContext.getUserId()).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2065,15 +2103,17 @@ public class ActivityManager {
private final int mOrientation;
private final Rect mContentInsets;
private final boolean mReducedResolution;
+ private final boolean mIsRealSnapshot;
private final float mScale;
public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets,
- boolean reducedResolution, float scale) {
+ boolean reducedResolution, float scale, boolean isRealSnapshot) {
mSnapshot = snapshot;
mOrientation = orientation;
mContentInsets = new Rect(contentInsets);
mReducedResolution = reducedResolution;
mScale = scale;
+ mIsRealSnapshot = isRealSnapshot;
}
private TaskSnapshot(Parcel source) {
@@ -2082,6 +2122,7 @@ public class ActivityManager {
mContentInsets = source.readParcelable(null /* classLoader */);
mReducedResolution = source.readBoolean();
mScale = source.readFloat();
+ mIsRealSnapshot = source.readBoolean();
}
/**
@@ -2114,6 +2155,14 @@ public class ActivityManager {
}
/**
+ * @return Whether or not the snapshot is a real snapshot or an app-theme generated snapshot
+ * due to the task having a secure window or having previews disabled.
+ */
+ public boolean isRealSnapshot() {
+ return mIsRealSnapshot;
+ }
+
+ /**
* @return The scale this snapshot was taken in.
*/
public float getScale() {
@@ -2132,13 +2181,15 @@ public class ActivityManager {
dest.writeParcelable(mContentInsets, 0);
dest.writeBoolean(mReducedResolution);
dest.writeFloat(mScale);
+ dest.writeBoolean(mIsRealSnapshot);
}
@Override
public String toString() {
return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation
+ " mContentInsets=" + mContentInsets.toShortString()
- + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale;
+ + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale
+ + " mIsRealSnapshot=" + mIsRealSnapshot;
}
public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
@@ -2661,7 +2712,7 @@ public class ActivityManager {
public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
try {
return getService().clearApplicationUserData(packageName, false,
- observer, UserHandle.myUserId());
+ observer, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2686,17 +2737,22 @@ public class ActivityManager {
/**
* Permits an application to get the persistent URI permissions granted to another.
*
- * <p>Typically called by Settings.
+ * <p>Typically called by Settings or DocumentsUI, requires
+ * {@code GET_APP_GRANTED_URI_PERMISSIONS}.
*
- * @param packageName application to look for the granted permissions
+ * @param packageName application to look for the granted permissions, or {@code null} to get
+ * granted permissions for all applications
* @return list of granted URI permissions
*
* @hide
*/
- public ParceledListSlice<UriPermission> getGrantedUriPermissions(String packageName) {
+ public ParceledListSlice<GrantedUriPermission> getGrantedUriPermissions(
+ @Nullable String packageName) {
try {
- return getService().getGrantedUriPermissions(packageName,
- UserHandle.myUserId());
+ @SuppressWarnings("unchecked")
+ final ParceledListSlice<GrantedUriPermission> castedList = getService()
+ .getGrantedUriPermissions(packageName, mContext.getUserId());
+ return castedList;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2705,7 +2761,7 @@ public class ActivityManager {
/**
* Permits an application to clear the persistent URI permissions granted to another.
*
- * <p>Typically called by Settings.
+ * <p>Typically called by Settings, requires {@code CLEAR_APP_GRANTED_URI_PERMISSIONS}.
*
* @param packageName application to clear its granted permissions
*
@@ -2714,7 +2770,7 @@ public class ActivityManager {
public void clearGrantedUriPermissions(String packageName) {
try {
getService().clearGrantedUriPermissions(packageName,
- UserHandle.myUserId());
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3281,6 +3337,28 @@ public class ActivityManager {
}
/**
+ * Query whether the user has enabled background restrictions for this app.
+ *
+ * <p> The user may chose to do this, if they see that an app is consuming an unreasonable
+ * amount of battery while in the background. </p>
+ *
+ * <p> If true, any work that the app tries to do will be aggressively restricted while it is in
+ * the background. At a minimum, jobs and alarms will not execute and foreground services
+ * cannot be started unless an app activity is in the foreground. </p>
+ *
+ * <p><b> Note that these restrictions stay in effect even when the device is charging.</b></p>
+ *
+ * @return true if user has enforced background restrictions for this app, false otherwise.
+ */
+ public boolean isBackgroundRestricted() {
+ try {
+ return getService().isBackgroundRestricted(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Sets the memory trim mode for a process and schedules a memory trim operation.
*
* <p><b>Note: this method is only intended for testing framework.</b></p>
@@ -3499,7 +3577,7 @@ public class ActivityManager {
public void killBackgroundProcesses(String packageName) {
try {
getService().killBackgroundProcesses(packageName,
- UserHandle.myUserId());
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3557,7 +3635,7 @@ public class ActivityManager {
@SystemApi
@RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES)
public void forceStopPackage(String packageName) {
- forceStopPackageAsUser(packageName, UserHandle.myUserId());
+ forceStopPackageAsUser(packageName, mContext.getUserId());
}
/**
@@ -3669,6 +3747,24 @@ public class ActivityManager {
}
/**
+ * Unsupported compiled sdk warning should always be shown for the intput activity
+ * even in cases where the system would normally not show the warning. E.g. when running in a
+ * test harness.
+ *
+ * @param activity The component name of the activity to always show the warning for.
+ *
+ * @hide
+ */
+ @TestApi
+ public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
+ try {
+ getService().alwaysShowUnsupportedCompileSdkWarning(activity);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the launch count of each installed package.
*
* @hide
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
index da9f7285..db9d9234 100644
--- a/android/app/ActivityManagerInternal.java
+++ b/android/app/ActivityManagerInternal.java
@@ -27,6 +27,7 @@ import android.os.IBinder;
import android.os.SystemClock;
import android.service.voice.IVoiceInteractionSession;
import android.util.SparseIntArray;
+import android.view.RemoteAnimationAdapter;
import com.android.internal.app.IVoiceInteractor;
@@ -43,25 +44,36 @@ public abstract class ActivityManagerInternal {
* Type for {@link #notifyAppTransitionStarting}: The transition was started because we drew
* the splash screen.
*/
- public static final int APP_TRANSITION_SPLASH_SCREEN = 1;
+ public static final int APP_TRANSITION_SPLASH_SCREEN =
+ AppProtoEnums.APP_TRANSITION_SPLASH_SCREEN; // 1
/**
* Type for {@link #notifyAppTransitionStarting}: The transition was started because we all
* app windows were drawn
*/
- public static final int APP_TRANSITION_WINDOWS_DRAWN = 2;
+ public static final int APP_TRANSITION_WINDOWS_DRAWN =
+ AppProtoEnums.APP_TRANSITION_WINDOWS_DRAWN; // 2
/**
* Type for {@link #notifyAppTransitionStarting}: The transition was started because of a
* timeout.
*/
- public static final int APP_TRANSITION_TIMEOUT = 3;
+ public static final int APP_TRANSITION_TIMEOUT =
+ AppProtoEnums.APP_TRANSITION_TIMEOUT; // 3
/**
* Type for {@link #notifyAppTransitionStarting}: The transition was started because of a
* we drew a task snapshot.
*/
- public static final int APP_TRANSITION_SNAPSHOT = 4;
+ public static final int APP_TRANSITION_SNAPSHOT =
+ AppProtoEnums.APP_TRANSITION_SNAPSHOT; // 4
+
+ /**
+ * Type for {@link #notifyAppTransitionStarting}: The transition was started because it was a
+ * recents animation and we only needed to wait on the wallpaper.
+ */
+ public static final int APP_TRANSITION_RECENTS_ANIM =
+ AppProtoEnums.APP_TRANSITION_RECENTS_ANIM; // 5
/**
* The bundle key to extract the assist data.
@@ -150,8 +162,8 @@ public abstract class ActivityManagerInternal {
* Callback for window manager to let activity manager know that we are finally starting the
* app transition;
*
- * @param reasons A map from stack id to a reason integer why the transition was started,, which
- * must be one of the APP_TRANSITION_* values.
+ * @param reasons A map from windowing mode to a reason integer why the transition was started,
+ * which must be one of the APP_TRANSITION_* values.
* @param timestamp The time at which the app transition started in
* {@link SystemClock#uptimeMillis()} timebase.
*/
@@ -215,6 +227,9 @@ public abstract class ActivityManagerInternal {
/**
* Start activity {@code intents} as if {@code packageName} on user {@code userId} did it.
*
+ * - DO NOT call it with the calling UID cleared.
+ * - All the necessary caller permission checks must be done at callsites.
+ *
* @return error codes used by {@link IActivityManager#startActivity} and its siblings.
*/
public abstract int startActivitiesAsPackage(String packageName,
@@ -257,6 +272,17 @@ public abstract class ActivityManagerInternal {
public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
/**
+ * Sets if the given pid is currently running a remote animation, which is taken a signal for
+ * determining oom adjustment and scheduling behavior.
+ *
+ * @param pid The pid we are setting overlay UI for.
+ * @param runningRemoteAnimation True if the process is running a remote animation, false
+ * otherwise.
+ * @see RemoteAnimationAdapter
+ */
+ public abstract void setRunningRemoteAnimation(int pid, boolean runningRemoteAnimation);
+
+ /**
* Called after the network policy rules are updated by
* {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
* {@param procStateSeq}.
@@ -344,4 +370,25 @@ public abstract class ActivityManagerInternal {
* Returns is the caller has the same uid as the Recents component
*/
public abstract boolean isCallerRecents(int callingUid);
+
+ /**
+ * Returns whether the recents component is the home activity for the given user.
+ */
+ public abstract boolean isRecentsComponentHomeActivity(int userId);
+
+ /**
+ * Whether an UID is active or idle.
+ */
+ public abstract boolean isUidActive(int uid);
+
+ /**
+ * Returns a list that contains the memory stats for currently running processes.
+ */
+ public abstract List<ProcessMemoryState> getMemoryStateForProcesses();
+
+ /**
+ * This enforces {@code func} can only be called if either the caller is Recents activity or
+ * has {@code permission}.
+ */
+ public abstract void enforceCallerIsRecentsOrHasPermission(String permission, String func);
}
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index fee58274..09dcbf21 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -164,7 +164,7 @@ public class ActivityOptions {
/**
* Whether the activity should be launched into LockTask mode.
- * @see #setLockTaskMode(boolean)
+ * @see #setLockTaskEnabled(boolean)
*/
private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
@@ -1106,6 +1106,11 @@ public class ActivityOptions {
}
/** @hide */
+ public void setRemoteAnimationAdapter(RemoteAnimationAdapter remoteAnimationAdapter) {
+ mRemoteAnimationAdapter = remoteAnimationAdapter;
+ }
+
+ /** @hide */
public static ActivityOptions fromBundle(Bundle bOptions) {
return bOptions != null ? new ActivityOptions(bOptions) : null;
}
@@ -1143,7 +1148,7 @@ public class ActivityOptions {
* @see Activity#startLockTask()
* @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
*/
- public ActivityOptions setLockTaskMode(boolean lockTaskMode) {
+ public ActivityOptions setLockTaskEnabled(boolean lockTaskMode) {
mLockTaskMode = lockTaskMode;
return this;
}
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 934b0f3c..50a43989 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -30,12 +30,15 @@ import android.annotation.Nullable;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
+import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.PendingTransactionActions.StopInfo;
import android.app.servertransaction.TransactionExecutor;
+import android.app.servertransaction.TransactionExecutorHelper;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -64,6 +67,7 @@ import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.ImageDecoder;
import android.hardware.display.DisplayManagerGlobal;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
@@ -113,6 +117,7 @@ import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
+import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -142,7 +147,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.TrustedCertificateStore;
-import com.android.server.am.proto.MemInfoProto;
+import com.android.server.am.MemInfoDumpProto;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.CloseGuard;
@@ -166,9 +171,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.text.DateFormat;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -204,12 +209,9 @@ public final class ActivityThread extends ClientTransactionHandler {
private static final boolean DEBUG_SERVICE = false;
public static final boolean DEBUG_MEMORY_TRIM = false;
private static final boolean DEBUG_PROVIDER = false;
- private static final boolean DEBUG_ORDER = false;
+ public static final boolean DEBUG_ORDER = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
- private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
- private static final int LOG_AM_ON_RESUME_CALLED = 30022;
- private static final int LOG_AM_ON_STOP_CALLED = 30049;
/** Type for IActivityManager.serviceDoneExecuting: anonymous operation */
public static final int SERVICE_DONE_EXECUTING_ANON = 0;
@@ -222,7 +224,7 @@ public final class ActivityThread extends ClientTransactionHandler {
private static final boolean REPORT_TO_ACTIVITY = true;
// Maximum number of recent tokens to maintain for debugging purposes
- private static final int MAX_RECENT_TOKENS = 10;
+ private static final int MAX_DESTROYED_ACTIVITIES = 10;
/**
* Denotes an invalid sequence number corresponding to a process state change.
@@ -256,7 +258,7 @@ public final class ActivityThread extends ClientTransactionHandler {
final H mH = new H();
final Executor mExecutor = new HandlerExecutor(mH);
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
- final ArrayDeque<Integer> mRecentTokens = new ArrayDeque<>();
+ final ArrayList<DestroyedActivityInfo> mRecentDestroyedActivities = new ArrayList<>();
// List of new activities (via ActivityRecord.nextIdle) that should
// be reported when next we idle.
@@ -291,6 +293,7 @@ public final class ActivityThread extends ClientTransactionHandler {
boolean mJitEnabled = false;
boolean mSomeActivitiesChanged = false;
boolean mUpdatingSystemConfig = false;
+ /* package */ boolean mHiddenApiWarningShown = false;
// These can be accessed by multiple threads; mResourcesManager is the lock.
// XXX For now we keep around information about all packages we have
@@ -338,6 +341,26 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
+ /**
+ * TODO(b/71506345): Remove this once bug is resolved.
+ */
+ private static final class DestroyedActivityInfo {
+ private final Integer mToken;
+ private final String mReason;
+ private final long mTime;
+
+ DestroyedActivityInfo(Integer token, String reason) {
+ mToken = token;
+ mReason = reason;
+ mTime = System.currentTimeMillis();
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "[token:" + mToken + " | time:" + mTime + " | reason:" + mReason
+ + "]");
+ }
+ }
+
// The lock of mProviderMap protects the following variables.
final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
= new ArrayMap<ProviderKey, ProviderClientRecord>();
@@ -389,7 +412,7 @@ public final class ActivityThread extends ClientTransactionHandler {
ActivityInfo activityInfo;
CompatibilityInfo compatInfo;
- public LoadedApk loadedApk;
+ public LoadedApk packageInfo;
List<ResultInfo> pendingResults;
List<ReferrerIntent> pendingIntents;
@@ -397,7 +420,6 @@ public final class ActivityThread extends ClientTransactionHandler {
boolean startsNotResumed;
public final boolean isForward;
int pendingConfigChanges;
- boolean onlyLocalRequest;
Window mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@@ -432,7 +454,7 @@ public final class ActivityThread extends ClientTransactionHandler {
this.isForward = isForward;
this.profilerInfo = profilerInfo;
this.overrideConfig = overrideConfig;
- this.loadedApk = client.getLoadedApkNoCheck(activityInfo.applicationInfo,
+ this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
compatInfo);
init();
}
@@ -487,18 +509,24 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- public boolean isPreHoneycomb() {
- if (activity != null) {
- return activity.getApplicationInfo().targetSdkVersion
- < android.os.Build.VERSION_CODES.HONEYCOMB;
- }
- return false;
+ private boolean isPreHoneycomb() {
+ return activity != null && activity.getApplicationInfo().targetSdkVersion
+ < android.os.Build.VERSION_CODES.HONEYCOMB;
+ }
+
+ private boolean isPreP() {
+ return activity != null && activity.getApplicationInfo().targetSdkVersion
+ < android.os.Build.VERSION_CODES.P;
}
public boolean isPersistable() {
return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS;
}
+ public boolean isVisibleFromServer() {
+ return activity != null && activity.mVisibleFromServer;
+ }
+
public String toString() {
ComponentName componentName = intent != null ? intent.getComponent() : null;
return "ActivityRecord{"
@@ -517,7 +545,6 @@ public final class ActivityThread extends ClientTransactionHandler {
sb.append(", startsNotResumed=").append(startsNotResumed);
sb.append(", isForward=").append(isForward);
sb.append(", pendingConfigChanges=").append(pendingConfigChanges);
- sb.append(", onlyLocalRequest=").append(onlyLocalRequest);
sb.append(", preserveWindow=").append(mPreserveWindow);
if (activity != null) {
sb.append(", Activity{");
@@ -614,7 +641,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
static final class AppBindData {
- LoadedApk loadedApk;
+ LoadedApk info;
String processName;
ApplicationInfo appInfo;
List<ProviderInfo> providers;
@@ -634,6 +661,8 @@ public final class ActivityThread extends ClientTransactionHandler {
/** Initial values for {@link Profiler}. */
ProfilerInfo initProfilerInfo;
+ boolean autofillCompatibilityEnabled;
+
public String toString() {
return "AppBindData{appInfo=" + appInfo + "}";
}
@@ -683,7 +712,7 @@ public final class ActivityThread extends ClientTransactionHandler {
streamingOutput);
profiling = true;
} catch (RuntimeException e) {
- Slog.w(TAG, "Profiling failed on path " + profileFile);
+ Slog.w(TAG, "Profiling failed on path " + profileFile, e);
try {
profileFd.close();
profileFd = null;
@@ -760,15 +789,6 @@ public final class ActivityThread extends ClientTransactionHandler {
sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
}
- @Override
- public final void scheduleRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config,
- Configuration overrideConfig, boolean preserveWindow) {
- requestRelaunchActivity(token, pendingResults, pendingNewIntents,
- configChanges, notResumed, config, overrideConfig, true, preserveWindow);
- }
-
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean sync, int sendingUser, int processState) {
@@ -860,7 +880,7 @@ public final class ActivityThread extends ClientTransactionHandler {
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
- String buildSerial) {
+ String buildSerial, boolean autofillCompatibilityEnabled) {
if (services != null) {
// Setup the service cache in the ServiceManager
@@ -886,6 +906,7 @@ public final class ActivityThread extends ClientTransactionHandler {
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
data.buildSerial = buildSerial;
+ data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
sendMessage(H.BIND_APPLICATION, data);
}
@@ -1238,55 +1259,62 @@ public final class ActivityThread extends ClientTransactionHandler {
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,
+ final long mToken = proto.start(MemInfoDumpProto.AppData.PROCESS_MEMORY);
+ proto.write(MemInfoDumpProto.ProcessMemory.PID, Process.myPid());
+ proto.write(MemInfoDumpProto.ProcessMemory.PROCESS_NAME,
(mBoundApplication != null) ? mBoundApplication.processName : "unknown");
dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly,
nativeMax, nativeAllocated, nativeFree,
dalvikMax, dalvikAllocated, dalvikFree);
proto.end(mToken);
- final long oToken = proto.start(MemInfoProto.AppData.OBJECTS);
- proto.write(MemInfoProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT, viewInstanceCount);
- proto.write(MemInfoProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT,
+ final long oToken = proto.start(MemInfoDumpProto.AppData.OBJECTS);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT,
+ viewInstanceCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT,
viewRootInstanceCount);
- proto.write(MemInfoProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT,
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT,
appContextInstanceCount);
- proto.write(MemInfoProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT,
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT,
activityInstanceCount);
- proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT, globalAssetCount);
- proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT,
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT,
+ globalAssetCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT,
globalAssetManagerCount);
- proto.write(MemInfoProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT,
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT,
binderLocalObjectCount);
- proto.write(MemInfoProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT,
+ proto.write(MemInfoDumpProto.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,
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_MEMORY_KB,
+ parcelSize / 1024);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount);
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT,
binderDeathObjectCount);
- proto.write(MemInfoProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, openSslSocketCount);
- proto.write(MemInfoProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT,
+ proto.write(MemInfoDumpProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT,
+ openSslSocketCount);
+ proto.write(MemInfoDumpProto.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,
+ final long sToken = proto.start(MemInfoDumpProto.AppData.SQL);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.MEMORY_USED_KB,
+ stats.memoryUsed / 1024);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB,
stats.pageCacheOverflow / 1024);
- proto.write(MemInfoProto.AppData.SqlStats.MALLOC_SIZE_KB, stats.largestMemAlloc / 1024);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.MALLOC_SIZE_KB,
+ stats.largestMemAlloc / 1024);
int n = stats.dbStats.size();
for (int i = 0; i < n; i++) {
DbStats dbStats = stats.dbStats.get(i);
- final long dToken = proto.start(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);
+ final long dToken = proto.start(MemInfoDumpProto.AppData.SqlStats.DATABASES);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.NAME, dbStats.dbName);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.LOOKASIDE_B,
+ dbStats.lookaside);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE, dbStats.cache);
proto.end(dToken);
}
proto.end(sToken);
@@ -1294,7 +1322,7 @@ public final class ActivityThread extends ClientTransactionHandler {
// Asset details.
String assetAlloc = AssetManager.getAssetAllocations();
if (assetAlloc != null) {
- proto.write(MemInfoProto.AppData.ASSET_ALLOCATIONS, assetAlloc);
+ proto.write(MemInfoDumpProto.AppData.ASSET_ALLOCATIONS, assetAlloc);
}
// Unreachable native memory
@@ -1302,7 +1330,7 @@ public final class ActivityThread extends ClientTransactionHandler {
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,
+ proto.write(MemInfoDumpProto.AppData.UNREACHABLE_MEMORY,
Debug.getUnreachableMemory(100, showContents));
}
}
@@ -1525,7 +1553,6 @@ public final class ActivityThread extends ClientTransactionHandler {
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
- public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
public static final int DESTROY_BACKUP_AGENT = 129;
@@ -1571,7 +1598,6 @@ public final class ActivityThread extends ClientTransactionHandler {
case UNBIND_SERVICE: return "UNBIND_SERVICE";
case DUMP_SERVICE: return "DUMP_SERVICE";
case LOW_MEMORY: return "LOW_MEMORY";
- case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
@@ -1605,12 +1631,6 @@ public final class ActivityThread extends ClientTransactionHandler {
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
- case RELAUNCH_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
- ActivityClientRecord r = (ActivityClientRecord)msg.obj;
- handleRelaunchActivity(r);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
@@ -1784,6 +1804,7 @@ public final class ActivityThread extends ClientTransactionHandler {
// message is handled.
transaction.recycle();
}
+ // TODO(lifecycler): Recycle locally scheduled transactions.
break;
}
Object obj = msg.obj;
@@ -1913,13 +1934,13 @@ public final class ActivityThread extends ClientTransactionHandler {
return mH;
}
- 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) {
+ return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId());
}
- public final LoadedApk getLoadedApkForPackageName(String packageName,
- CompatibilityInfo compatInfo, int flags, int userId) {
+ public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
+ int flags, int userId) {
final boolean differentUser = (UserHandle.myUserId() != userId);
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
@@ -1932,13 +1953,13 @@ public final class ActivityThread extends ClientTransactionHandler {
ref = mResourcePackages.get(packageName);
}
- 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()
+ 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()
&& (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) {
throw new SecurityException(
"Requesting code from " + packageName
@@ -1946,7 +1967,7 @@ public final class ActivityThread extends ClientTransactionHandler {
+ mBoundApplication.processName
+ "/" + mBoundApplication.appInfo.uid);
}
- return loadedApk;
+ return packageInfo;
}
}
@@ -1961,13 +1982,13 @@ public final class ActivityThread extends ClientTransactionHandler {
}
if (ai != null) {
- return getLoadedApk(ai, compatInfo, flags);
+ return getPackageInfo(ai, compatInfo, flags);
}
return null;
}
- public final LoadedApk getLoadedApk(ApplicationInfo ai, CompatibilityInfo compatInfo,
+ public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
int flags) {
boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
boolean securityViolation = includeCode && ai.uid != 0
@@ -1989,17 +2010,17 @@ public final class ActivityThread extends ClientTransactionHandler {
throw new SecurityException(msg);
}
}
- return getLoadedApk(ai, compatInfo, null, securityViolation, includeCode,
+ return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode,
registerPackage);
}
@Override
- public final LoadedApk getLoadedApkNoCheck(ApplicationInfo ai,
+ public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
- return getLoadedApk(ai, compatInfo, null, false, true, false);
+ return getPackageInfo(ai, compatInfo, null, false, true, false);
}
- public final LoadedApk peekLoadedApk(String packageName, boolean includeCode) {
+ public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) {
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
if (includeCode) {
@@ -2011,7 +2032,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- private LoadedApk getLoadedApk(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
+ private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
@@ -2026,35 +2047,35 @@ public final class ActivityThread extends ClientTransactionHandler {
ref = mResourcePackages.get(aInfo.packageName);
}
- LoadedApk loadedApk = ref != null ? ref.get() : null;
- if (loadedApk == null || (loadedApk.mResources != null
- && !loadedApk.mResources.getAssets().isUpToDate())) {
+ LoadedApk packageInfo = ref != null ? ref.get() : null;
+ if (packageInfo == null || (packageInfo.mResources != null
+ && !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
+ ")");
- loadedApk =
+ packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
if (mSystemThread && "android".equals(aInfo.packageName)) {
- loadedApk.installSystemApplicationInfo(aInfo,
- getSystemContext().mLoadedApk.getClassLoader());
+ packageInfo.installSystemApplicationInfo(aInfo,
+ getSystemContext().mPackageInfo.getClassLoader());
}
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
- new WeakReference<LoadedApk>(loadedApk));
+ new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
- new WeakReference<LoadedApk>(loadedApk));
+ new WeakReference<LoadedApk>(packageInfo));
}
}
- return loadedApk;
+ return packageInfo;
}
}
@@ -2176,14 +2197,28 @@ public final class ActivityThread extends ClientTransactionHandler {
@Override
public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "mActivities:");
+ pw.println(prefix + "Activities:");
+
+ if (!mActivities.isEmpty()) {
+ final Iterator<Map.Entry<IBinder, ActivityClientRecord>> activitiesIterator =
+ mActivities.entrySet().iterator();
- for (ArrayMap.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
- pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:"
- + entry.getValue().toString() + "]");
+ while (activitiesIterator.hasNext()) {
+ final ArrayMap.Entry<IBinder, ActivityClientRecord> entry =
+ activitiesIterator.next();
+ pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:"
+ + entry.getValue().toString() + "]");
+ }
}
- pw.println(prefix + "mRecentTokens:" + mRecentTokens);
+ if (!mRecentDestroyedActivities.isEmpty()) {
+ pw.println(prefix + "Recent destroyed activities:");
+ for (int i = 0, size = mRecentDestroyedActivities.size(); i < size; i++) {
+ final DestroyedActivityInfo info = mRecentDestroyedActivities.get(i);
+ pw.print(prefix);
+ info.dump(pw, " ");
+ }
+ }
}
public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
@@ -2486,17 +2521,17 @@ public final class ActivityThread extends ClientTransactionHandler {
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);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.NAME, name);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
if (hasSwappedOutPss) {
- proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
} else {
- proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
+ proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
}
proto.end(token);
@@ -2511,26 +2546,26 @@ public final class ActivityThread extends ClientTransactionHandler {
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",
+ final long nhToken = proto.start(MemInfoDumpProto.ProcessMemory.NATIVE_HEAP);
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap",
memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
memInfo.nativePrivateClean, memInfo.hasSwappedOutPss,
memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss);
- 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.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree);
proto.end(nhToken);
- final long dvToken = proto.start(MemInfoProto.ProcessMemory.DALVIK_HEAP);
- dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap",
+ final long dvToken = proto.start(MemInfoDumpProto.ProcessMemory.DALVIK_HEAP);
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap",
memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss,
memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss);
- 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.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree);
proto.end(dvToken);
int otherPss = memInfo.otherPss;
@@ -2554,7 +2589,7 @@ public final class ActivityThread extends ClientTransactionHandler {
if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
|| mySharedClean != 0 || myPrivateClean != 0
|| (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
- dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.OTHER_HEAPS,
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.OTHER_HEAPS,
Debug.MemoryInfo.getOtherLabel(i),
myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
mySharedClean, myPrivateClean,
@@ -2571,21 +2606,23 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.UNKNOWN_HEAP, "Unknown",
+ dumpMemoryInfo(proto, MemInfoDumpProto.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",
+ final long tToken = proto.start(MemInfoDumpProto.ProcessMemory.TOTAL_HEAP);
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL",
memInfo.getTotalPss(), memInfo.getTotalSwappablePss(),
memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(),
memInfo.getTotalSwappedOutPss());
- proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax);
- proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB,
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB,
+ nativeMax + dalvikMax);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB,
nativeAllocated + dalvikAllocated);
- proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree);
+ proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB,
+ nativeFree + dalvikFree);
proto.end(tToken);
if (dumpDalvik) {
@@ -2603,7 +2640,7 @@ public final class ActivityThread extends ClientTransactionHandler {
if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
|| mySharedClean != 0 || myPrivateClean != 0
|| (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
- dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.DALVIK_DETAILS,
+ dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.DALVIK_DETAILS,
Debug.MemoryInfo.getOtherLabel(i),
myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
mySharedClean, myPrivateClean,
@@ -2613,24 +2650,26 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- final long asToken = proto.start(MemInfoProto.ProcessMemory.APP_SUMMARY);
- proto.write(MemInfoProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB,
+ final long asToken = proto.start(MemInfoDumpProto.ProcessMemory.APP_SUMMARY);
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB,
memInfo.getSummaryJavaHeap());
- proto.write(MemInfoProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB,
+ proto.write(MemInfoDumpProto.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,
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.CODE_PSS_KB,
+ memInfo.getSummaryCode());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.STACK_PSS_KB,
+ memInfo.getSummaryStack());
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB,
memInfo.getSummaryGraphics());
- proto.write(MemInfoProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB,
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB,
memInfo.getSummaryPrivateOther());
- proto.write(MemInfoProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB,
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB,
memInfo.getSummarySystem());
if (memInfo.hasSwappedOutPss) {
- proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
memInfo.getSummaryTotalSwapPss());
} else {
- proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
+ proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
memInfo.getSummaryTotalSwap());
}
proto.end(asToken);
@@ -2724,6 +2763,11 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
+ @Override
+ TransactionExecutor getTransactionExecutor() {
+ return mTransactionExecutor;
+ }
+
void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
@@ -2778,8 +2822,8 @@ public final class ActivityThread extends ClientTransactionHandler {
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r) {
ActivityInfo aInfo = r.activityInfo;
- if (r.loadedApk == null) {
- r.loadedApk = getLoadedApk(aInfo.applicationInfo, r.compatInfo,
+ if (r.packageInfo == null) {
+ r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
@@ -2816,15 +2860,15 @@ public final class ActivityThread extends ClientTransactionHandler {
}
try {
- Application app = r.loadedApk.makeApplication(false, mInstrumentation);
+ Application app = r.packageInfo.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.loadedApk.getPackageName()
+ + ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
- + ", dir=" + r.loadedApk.getAppDir());
+ + ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
@@ -2870,11 +2914,6 @@ public final class ActivityThread extends ClientTransactionHandler {
r.setState(ON_CREATE);
mActivities.put(r.token, r);
- mRecentTokens.push(r.token.hashCode());
-
- if (mRecentTokens.size() > MAX_RECENT_TOKENS) {
- mRecentTokens.removeLast();
- }
} catch (SuperNotCalledException e) {
throw e;
@@ -2907,7 +2946,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
// Start
- activity.performStart();
+ activity.performStart("handleStartActivity");
r.setState(ON_START);
if (pendingActions == null) {
@@ -2969,7 +3008,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
ContextImpl appContext = ContextImpl.createActivityContext(
- this, r.loadedApk, r.activityInfo, r.token, displayId, r.overrideConfig);
+ this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
@@ -2977,7 +3016,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.loadedApk.mPackageName.contains(pkgName)) {
+ && r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
@@ -3096,7 +3135,7 @@ public final class ActivityThread extends ClientTransactionHandler {
checkAndBlockForNetworkAccess();
deliverNewIntents(r, intents);
if (resumed) {
- r.activity.performResume(false);
+ r.activity.performResume(false, "performNewIntents");
r.activity.mTemporaryPause = false;
}
@@ -3309,7 +3348,7 @@ public final class ActivityThread extends ClientTransactionHandler {
String component = data.intent.getComponent().getClassName();
- LoadedApk loadedApk = getLoadedApkNoCheck(
+ LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
IActivityManager mgr = ActivityManager.getService();
@@ -3318,7 +3357,7 @@ public final class ActivityThread extends ClientTransactionHandler {
BroadcastReceiver receiver;
ContextImpl context;
try {
- app = loadedApk.makeApplication(false, mInstrumentation);
+ app = packageInfo.makeApplication(false, mInstrumentation);
context = (ContextImpl) app.getBaseContext();
if (data.info.splitName != null) {
context = (ContextImpl) context.createContextForSplit(data.info.splitName);
@@ -3327,7 +3366,7 @@ public final class ActivityThread extends ClientTransactionHandler {
data.intent.setExtrasClassLoader(cl);
data.intent.prepareToEnterProcess();
data.setExtrasClassLoader(cl);
- receiver = loadedApk.getAppFactory()
+ receiver = packageInfo.getAppFactory()
.instantiateReceiver(cl, data.info.name, data.intent);
} catch (Exception e) {
if (DEBUG_BROADCAST) Slog.i(TAG,
@@ -3343,9 +3382,9 @@ public final class ActivityThread extends ClientTransactionHandler {
TAG, "Performing receive of " + data.intent
+ ": app=" + app
+ ", appName=" + app.getPackageName()
- + ", pkg=" + loadedApk.getPackageName()
+ + ", pkg=" + packageInfo.getPackageName()
+ ", comp=" + data.intent.getComponent().toShortString()
- + ", dir=" + loadedApk.getAppDir());
+ + ", dir=" + packageInfo.getAppDir());
sCurrentBroadcastIntent.set(data.intent);
receiver.setPendingResult(data);
@@ -3390,8 +3429,8 @@ public final class ActivityThread extends ClientTransactionHandler {
unscheduleGcIdler();
// instantiate the BackupAgent class named in the manifest
- LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo);
- String packageName = loadedApk.mPackageName;
+ LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ String packageName = packageInfo.mPackageName;
if (packageName == null) {
Slog.d(TAG, "Asked to create backup agent for nonexistent package");
return;
@@ -3417,11 +3456,11 @@ public final class ActivityThread extends ClientTransactionHandler {
try {
if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);
- java.lang.ClassLoader cl = loadedApk.getClassLoader();
+ java.lang.ClassLoader cl = packageInfo.getClassLoader();
agent = (BackupAgent) cl.loadClass(classname).newInstance();
// set up the agent's context
- ContextImpl context = ContextImpl.createAppContext(this, loadedApk);
+ ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(agent);
agent.attach(context);
@@ -3457,8 +3496,8 @@ public final class ActivityThread extends ClientTransactionHandler {
private void handleDestroyBackupAgent(CreateBackupAgentData data) {
if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
- LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo);
- String packageName = loadedApk.mPackageName;
+ LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ String packageName = packageInfo.mPackageName;
BackupAgent agent = mBackupAgents.get(packageName);
if (agent != null) {
try {
@@ -3478,12 +3517,12 @@ public final class ActivityThread extends ClientTransactionHandler {
// we are back active so skip it.
unscheduleGcIdler();
- LoadedApk loadedApk = getLoadedApkNoCheck(
+ LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
- java.lang.ClassLoader cl = loadedApk.getClassLoader();
- service = loadedApk.getAppFactory()
+ java.lang.ClassLoader cl = packageInfo.getClassLoader();
+ service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
@@ -3496,10 +3535,10 @@ public final class ActivityThread extends ClientTransactionHandler {
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
- ContextImpl context = ContextImpl.createAppContext(this, loadedApk);
+ ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
- Application app = loadedApk.makeApplication(false, mInstrumentation);
+ Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
@@ -3697,56 +3736,64 @@ public final class ActivityThread extends ClientTransactionHandler {
//Slog.i(TAG, "Running services: " + mServices);
}
- 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);
- if (r != null && !r.activity.mFinished) {
- if (clearHide) {
- r.hideForNow = false;
- r.activity.mStartedActivity = false;
+ /**
+ * Resume the activity.
+ * @param token Target activity token.
+ * @param finalStateRequest Flag indicating if this is part of final state resolution for a
+ * transaction.
+ * @param reason Reason for performing the action.
+ *
+ * @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise.
+ */
+ @VisibleForTesting
+ public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
+ String reason) {
+ final ActivityClientRecord r = mActivities.get(token);
+ if (localLOGV) {
+ Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
+ }
+ if (r == null || r.activity.mFinished) {
+ return null;
+ }
+ if (r.getLifecycleState() == ON_RESUME) {
+ if (!finalStateRequest) {
+ final RuntimeException e = new IllegalStateException(
+ "Trying to resume activity which is already resumed");
+ Slog.e(TAG, e.getMessage(), e);
+ Slog.e(TAG, r.getStateString());
+ // TODO(lifecycler): A double resume request is possible when an activity
+ // receives two consequent transactions with relaunch requests and "resumed"
+ // final state requests and the second relaunch is omitted. We still try to
+ // handle two resume requests for the final state. For cases other than this
+ // one, we don't expect it to happen.
}
- try {
- r.activity.onStateNotSaved();
- r.activity.mFragments.noteStateNotSaved();
- checkAndBlockForNetworkAccess();
- if (r.pendingIntents != null) {
- deliverNewIntents(r, r.pendingIntents);
- r.pendingIntents = null;
- }
- if (r.pendingResults != null) {
- deliverResults(r, r.pendingResults);
- r.pendingResults = null;
- }
- r.activity.performResume(r.startsNotResumed);
-
- synchronized (mResourcesManager) {
- // If there is a pending local relaunch that was requested when the activity was
- // paused, it will put the activity into paused state when it finally happens.
- // Since the activity resumed before being relaunched, we don't want that to
- // happen, so we need to clear the request to relaunch paused.
- for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) {
- final ActivityClientRecord relaunching = mRelaunchingActivities.get(i);
- if (relaunching.token == r.token
- && relaunching.onlyLocalRequest && relaunching.startsNotResumed) {
- relaunching.startsNotResumed = false;
- }
- }
- }
-
- EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED, UserHandle.myUserId(),
- r.activity.getComponentName().getClassName(), reason);
+ return null;
+ }
+ if (finalStateRequest) {
+ r.hideForNow = false;
+ r.activity.mStartedActivity = false;
+ }
+ try {
+ r.activity.onStateNotSaved();
+ r.activity.mFragments.noteStateNotSaved();
+ checkAndBlockForNetworkAccess();
+ if (r.pendingIntents != null) {
+ deliverNewIntents(r, r.pendingIntents);
+ r.pendingIntents = null;
+ }
+ if (r.pendingResults != null) {
+ deliverResults(r, r.pendingResults);
+ r.pendingResults = null;
+ }
+ r.activity.performResume(r.startsNotResumed, reason);
- r.state = null;
- r.persistentState = null;
- r.setState(ON_RESUME);
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to resume activity "
- + r.intent.getComponent().toShortString()
- + ": " + e.toString(), e);
- }
+ r.state = null;
+ r.persistentState = null;
+ r.setState(ON_RESUME);
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException("Unable to resume activity "
+ + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
return r;
@@ -3770,7 +3817,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -3778,190 +3825,121 @@ public final class ActivityThread extends ClientTransactionHandler {
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
- final ActivityClientRecord r = performResumeActivity(token, clearHide, reason);
+ final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
+ if (r == null) {
+ // We didn't actually resume the activity, so skipping any follow-up actions.
+ return;
+ }
- if (r != null) {
- final Activity a = r.activity;
+ final Activity a = r.activity;
- if (localLOGV) Slog.v(
- TAG, "Resume " + r + " started activity: " +
- a.mStartedActivity + ", hideForNow: " + r.hideForNow
- + ", finished: " + a.mFinished);
-
- final int forwardBit = isForward ?
- WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
-
- // If the window hasn't yet been added to the window manager,
- // and this guy didn't finish itself or start another activity,
- // then go ahead and add the window.
- boolean willBeVisible = !a.mStartedActivity;
- if (!willBeVisible) {
- try {
- willBeVisible = ActivityManager.getService().willActivityBeVisible(
- a.getActivityToken());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (localLOGV) {
+ Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
+ + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
+ }
+
+ final int forwardBit = isForward
+ ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
+
+ // If the window hasn't yet been added to the window manager,
+ // and this guy didn't finish itself or start another activity,
+ // then go ahead and add the window.
+ boolean willBeVisible = !a.mStartedActivity;
+ if (!willBeVisible) {
+ try {
+ willBeVisible = ActivityManager.getService().willActivityBeVisible(
+ a.getActivityToken());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ if (r.window == null && !a.mFinished && willBeVisible) {
+ r.window = r.activity.getWindow();
+ View decor = r.window.getDecorView();
+ decor.setVisibility(View.INVISIBLE);
+ ViewManager wm = a.getWindowManager();
+ WindowManager.LayoutParams l = r.window.getAttributes();
+ a.mDecor = decor;
+ l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+ l.softInputMode |= forwardBit;
+ if (r.mPreserveWindow) {
+ a.mWindowAdded = true;
+ r.mPreserveWindow = false;
+ // Normally the ViewRoot sets up callbacks with the Activity
+ // in addView->ViewRootImpl#setView. If we are instead reusing
+ // the decor view we have to notify the view root that the
+ // callbacks may have changed.
+ ViewRootImpl impl = decor.getViewRootImpl();
+ if (impl != null) {
+ impl.notifyChildRebuilt();
}
}
- if (r.window == null && !a.mFinished && willBeVisible) {
- r.window = r.activity.getWindow();
- View decor = r.window.getDecorView();
- decor.setVisibility(View.INVISIBLE);
- ViewManager wm = a.getWindowManager();
- WindowManager.LayoutParams l = r.window.getAttributes();
- a.mDecor = decor;
- l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
- l.softInputMode |= forwardBit;
- if (r.mPreserveWindow) {
+ if (a.mVisibleFromClient) {
+ if (!a.mWindowAdded) {
a.mWindowAdded = true;
- r.mPreserveWindow = false;
- // Normally the ViewRoot sets up callbacks with the Activity
- // in addView->ViewRootImpl#setView. If we are instead reusing
- // the decor view we have to notify the view root that the
- // callbacks may have changed.
- ViewRootImpl impl = decor.getViewRootImpl();
- if (impl != null) {
- impl.notifyChildRebuilt();
- }
- }
- if (a.mVisibleFromClient) {
- if (!a.mWindowAdded) {
- a.mWindowAdded = true;
- wm.addView(decor, l);
- } else {
- // The activity will get a callback for this {@link LayoutParams} change
- // earlier. However, at that time the decor will not be set (this is set
- // in this method), so no action will be taken. This call ensures the
- // callback occurs with the decor set.
- a.onWindowAttributesChanged(l);
- }
+ wm.addView(decor, l);
+ } else {
+ // The activity will get a callback for this {@link LayoutParams} change
+ // earlier. However, at that time the decor will not be set (this is set
+ // in this method), so no action will be taken. This call ensures the
+ // callback occurs with the decor set.
+ a.onWindowAttributesChanged(l);
}
+ }
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
- } else if (!willBeVisible) {
- if (localLOGV) Slog.v(
- TAG, "Launch " + r + " mStartedActivity set");
- r.hideForNow = true;
- }
-
- // Get rid of anything left hanging around.
- cleanUpPendingRemoveWindows(r, false /* force */);
-
- // The window is now visible if it has been added, we are not
- // simply finishing, and we are not starting another activity.
- if (!r.activity.mFinished && willBeVisible
- && r.activity.mDecor != null && !r.hideForNow) {
- if (r.newConfig != null) {
- performConfigurationChangedForActivity(r, r.newConfig);
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
- + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
- r.newConfig = null;
- }
- if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
- + isForward);
- WindowManager.LayoutParams l = r.window.getAttributes();
- if ((l.softInputMode
- & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
- != forwardBit) {
- l.softInputMode = (l.softInputMode
- & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
- | forwardBit;
- if (r.activity.mVisibleFromClient) {
- ViewManager wm = a.getWindowManager();
- View decor = r.window.getDecorView();
- wm.updateViewLayout(decor, l);
- }
- }
-
- r.activity.mVisibleFromServer = true;
- mNumVisibleActivities++;
- if (r.activity.mVisibleFromClient) {
- r.activity.makeVisible();
- }
- }
-
- if (!r.onlyLocalRequest) {
- r.nextIdle = mNewActivities;
- mNewActivities = r;
- if (localLOGV) Slog.v(
- TAG, "Scheduling idle handler for " + r);
- Looper.myQueue().addIdleHandler(new Idler());
- }
- r.onlyLocalRequest = false;
- } else {
- // If an exception was thrown when trying to resume, then
- // just end this activity.
- try {
- ActivityManager.getService()
- .finishActivity(token, Activity.RESULT_CANCELED, null,
- Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ } else if (!willBeVisible) {
+ if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
+ r.hideForNow = true;
}
- }
- private int mThumbnailWidth = -1;
- private int mThumbnailHeight = -1;
- private Bitmap mAvailThumbnailBitmap = null;
- private Canvas mThumbnailCanvas = null;
+ // Get rid of anything left hanging around.
+ cleanUpPendingRemoveWindows(r, false /* force */);
- private Bitmap createThumbnailBitmap(ActivityClientRecord r) {
- Bitmap thumbnail = mAvailThumbnailBitmap;
- try {
- if (thumbnail == null) {
- int w = mThumbnailWidth;
- int h;
- if (w < 0) {
- Resources res = r.activity.getResources();
- int wId = com.android.internal.R.dimen.thumbnail_width;
- int hId = com.android.internal.R.dimen.thumbnail_height;
- mThumbnailWidth = w = res.getDimensionPixelSize(wId);
- mThumbnailHeight = h = res.getDimensionPixelSize(hId);
- } else {
- h = mThumbnailHeight;
- }
-
- // On platforms where we don't want thumbnails, set dims to (0,0)
- if ((w > 0) && (h > 0)) {
- thumbnail = Bitmap.createBitmap(r.activity.getResources().getDisplayMetrics(),
- w, h, THUMBNAIL_FORMAT);
- thumbnail.eraseColor(0);
- }
- }
-
- if (thumbnail != null) {
- Canvas cv = mThumbnailCanvas;
- if (cv == null) {
- mThumbnailCanvas = cv = new Canvas();
+ // The window is now visible if it has been added, we are not
+ // simply finishing, and we are not starting another activity.
+ if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
+ if (r.newConfig != null) {
+ performConfigurationChangedForActivity(r, r.newConfig);
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
+ + r.activity.mCurrentConfig);
}
-
- cv.setBitmap(thumbnail);
- if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
- mAvailThumbnailBitmap = thumbnail;
- thumbnail = null;
+ r.newConfig = null;
+ }
+ if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
+ WindowManager.LayoutParams l = r.window.getAttributes();
+ if ((l.softInputMode
+ & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
+ != forwardBit) {
+ l.softInputMode = (l.softInputMode
+ & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
+ | forwardBit;
+ if (r.activity.mVisibleFromClient) {
+ ViewManager wm = a.getWindowManager();
+ View decor = r.window.getDecorView();
+ wm.updateViewLayout(decor, l);
}
- cv.setBitmap(null);
}
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to create thumbnail of "
- + r.intent.getComponent().toShortString()
- + ": " + e.toString(), e);
+ r.activity.mVisibleFromServer = true;
+ mNumVisibleActivities++;
+ if (r.activity.mVisibleFromClient) {
+ r.activity.makeVisible();
}
- thumbnail = null;
}
- return thumbnail;
+ r.nextIdle = mNewActivities;
+ mNewActivities = r;
+ if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
+ Looper.myQueue().addIdleHandler(new Idler());
}
@Override
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
- int configChanges, boolean dontReport, PendingTransactionActions pendingActions) {
+ int configChanges, PendingTransactionActions pendingActions, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
if (userLeaving) {
@@ -3969,22 +3947,12 @@ public final class ActivityThread extends ClientTransactionHandler {
}
r.activity.mConfigChangeFlags |= configChanges;
- performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity",
- pendingActions);
+ performPauseActivity(r, finished, reason, pendingActions);
// Make sure any pending writes are now committed.
if (r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
-
- // Tell the activity manager we have paused.
- if (!dontReport) {
- try {
- ActivityManager.getService().activityPaused(token);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
mSomeActivitiesChanged = true;
}
}
@@ -3993,16 +3961,18 @@ public final class ActivityThread extends ClientTransactionHandler {
mInstrumentation.callActivityOnUserLeaving(r.activity);
}
- final Bundle performPauseActivity(IBinder token, boolean finished,
- boolean saveState, String reason, PendingTransactionActions pendingActions) {
+ final Bundle performPauseActivity(IBinder token, boolean finished, String reason,
+ PendingTransactionActions pendingActions) {
ActivityClientRecord r = mActivities.get(token);
- return r != null
- ? performPauseActivity(r, finished, saveState, reason, pendingActions)
- : null;
+ return r != null ? performPauseActivity(r, finished, reason, pendingActions) : null;
}
- private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, boolean saveState,
- String reason, PendingTransactionActions pendingActions) {
+ /**
+ * Pause the activity.
+ * @return Saved instance state for pre-Honeycomb apps if it was saved, {@code null} otherwise.
+ */
+ private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason,
+ PendingTransactionActions pendingActions) {
if (r.paused) {
if (r.activity.mFinished) {
// If we are finishing, we won't call onResume() in certain cases.
@@ -4019,9 +3989,10 @@ public final class ActivityThread extends ClientTransactionHandler {
r.activity.mFinished = true;
}
- // Next have the activity save its current state and managed dialogs...
- if (!r.activity.mFinished && saveState) {
- callCallActivityOnSaveInstanceState(r);
+ // Pre-Honeycomb apps always save their state before pausing
+ final boolean shouldSaveState = !r.activity.mFinished && r.isPreHoneycomb();
+ if (shouldSaveState) {
+ callActivityOnSaveInstanceState(r);
}
performPauseActivityIfNeeded(r, reason);
@@ -4048,7 +4019,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- return !r.activity.mFinished && saveState ? r.state : null;
+ return shouldSaveState ? r.state : null;
}
private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {
@@ -4060,8 +4031,6 @@ public final class ActivityThread extends ClientTransactionHandler {
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(),
- r.activity.getComponentName().getClassName(), reason);
if (!r.activity.mCalled) {
throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
+ " did not call through to super.onPause()");
@@ -4077,9 +4046,11 @@ public final class ActivityThread extends ClientTransactionHandler {
r.setState(ON_PAUSE);
}
+ /** Called from {@link LocalActivityManager}. */
final void performStopActivity(IBinder token, boolean saveState, String reason) {
ActivityClientRecord r = mActivities.get(token);
- performStopActivityInner(r, null, false, saveState, reason);
+ performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState,
+ false /* finalStateRequest */, reason);
}
private static final class ProviderRefCount {
@@ -4111,9 +4082,16 @@ public final class ActivityThread extends ClientTransactionHandler {
* it the result when it is done, but the window may still be visible.
* For the client, we want to call onStop()/onStart() to indicate when
* the activity's UI visibility changes.
+ * @param r Target activity client record.
+ * @param info Action that will report activity stop to server.
+ * @param keepShown Flag indicating whether the activity is still shown.
+ * @param saveState Flag indicating whether the activity state should be saved.
+ * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
+ * request for a transaction.
+ * @param reason Reason for performing this operation.
*/
private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
- boolean saveState, String reason) {
+ boolean saveState, boolean finalStateRequest, String reason) {
if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
if (r != null) {
if (!keepShown && r.stopped) {
@@ -4123,11 +4101,13 @@ public final class ActivityThread extends ClientTransactionHandler {
// if the activity isn't resumed.
return;
}
- RuntimeException e = new RuntimeException(
- "Performing stop of activity that is already stopped: "
- + r.intent.getComponent().toShortString());
- Slog.e(TAG, e.getMessage(), e);
- Slog.e(TAG, r.getStateString());
+ if (!finalStateRequest) {
+ final RuntimeException e = new RuntimeException(
+ "Performing stop of activity that is already stopped: "
+ + r.intent.getComponent().toShortString());
+ Slog.e(TAG, e.getMessage(), e);
+ Slog.e(TAG, r.getStateString());
+ }
}
// One must first be paused before stopped...
@@ -4149,30 +4129,45 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- // Next have the activity save its current state and managed dialogs...
- if (!r.activity.mFinished && saveState) {
- if (r.state == null) {
- callCallActivityOnSaveInstanceState(r);
- }
+ if (!keepShown) {
+ callActivityOnStop(r, saveState, reason);
}
+ }
+ }
- if (!keepShown) {
- try {
- // Now we are idle.
- r.activity.performStop(false /*preserveWindow*/);
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to stop activity "
+ /**
+ * Calls {@link Activity#onStop()} and {@link Activity#onSaveInstanceState(Bundle)}, and updates
+ * the client record's state.
+ * All calls to stop an activity must be done through this method to make sure that
+ * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call.
+ */
+ private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
+ // Before P onSaveInstanceState was called before onStop, starting with P it's
+ // called after. Before Honeycomb state was always saved before onPause.
+ final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
+ && !r.isPreHoneycomb();
+ final boolean isPreP = r.isPreP();
+ if (shouldSaveState && isPreP) {
+ callActivityOnSaveInstanceState(r);
+ }
+
+ try {
+ r.activity.performStop(false /*preserveWindow*/, reason);
+ } catch (SuperNotCalledException e) {
+ throw e;
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Unable to stop activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
- }
- }
- r.setState(ON_STOP);
- EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(),
- r.activity.getComponentName().getClassName(), reason);
}
}
+ r.setState(ON_STOP);
+
+ if (shouldSaveState && !isPreP) {
+ callActivityOnSaveInstanceState(r);
+ }
}
private void updateVisibility(ActivityClientRecord r, boolean show) {
@@ -4205,12 +4200,13 @@ public final class ActivityThread extends ClientTransactionHandler {
@Override
public void handleStopActivity(IBinder token, boolean show, int configChanges,
- PendingTransactionActions pendingActions) {
+ PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
- performStopActivityInner(r, stopInfo, show, true, "handleStopActivity");
+ performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
+ reason);
if (localLOGV) Slog.v(
TAG, "Finishing stop of " + r + ": show=" + show
@@ -4245,7 +4241,7 @@ public final class ActivityThread extends ClientTransactionHandler {
public void performRestartActivity(IBinder token, boolean start) {
ActivityClientRecord r = mActivities.get(token);
if (r.stopped) {
- r.activity.performRestart(start);
+ r.activity.performRestart(start, "performRestartActivity");
if (start) {
r.setState(ON_START);
}
@@ -4262,13 +4258,14 @@ public final class ActivityThread extends ClientTransactionHandler {
}
if (!show && !r.stopped) {
- performStopActivityInner(r, null, show, false, "handleWindowVisibility");
+ performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */,
+ false /* finalStateRequest */, "handleWindowVisibility");
} else if (show && r.stopped) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
- r.activity.performRestart(true /* start */);
+ r.activity.performRestart(true /* start */, "handleWindowVisibility");
r.setState(ON_START);
}
if (r.activity.mDecor != null) {
@@ -4292,24 +4289,7 @@ public final class ActivityThread extends ClientTransactionHandler {
if (sleeping) {
if (!r.stopped && !r.isPreHoneycomb()) {
- if (!r.activity.mFinished && r.state == null) {
- callCallActivityOnSaveInstanceState(r);
- }
-
- try {
- // Now we are idle.
- r.activity.performStop(false /*preserveWindow*/);
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to stop activity "
- + r.intent.getComponent().toShortString()
- + ": " + e.toString(), e);
- }
- }
- r.setState(ON_STOP);
- EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(),
- r.activity.getComponentName().getClassName(), "sleeping");
+ callActivityOnStop(r, true /* saveState */, "sleeping");
}
// Make sure any pending writes are now committed.
@@ -4325,7 +4305,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
} else {
if (r.stopped && r.activity.mVisibleFromServer) {
- r.activity.performRestart(true /* start */);
+ r.activity.performRestart(true /* start */, "handleSleeping");
r.setState(ON_START);
}
}
@@ -4345,29 +4325,25 @@ public final class ActivityThread extends ClientTransactionHandler {
View.mDebugViewAttributes = debugViewAttributes;
// request all activities to relaunch for the changes to take place
- requestRelaunchAllActivities();
+ relaunchAllActivities();
}
}
- private void requestRelaunchAllActivities() {
+ private void relaunchAllActivities() {
for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
final Activity activity = entry.getValue().activity;
if (!activity.mFinished) {
- try {
- ActivityManager.getService().requestActivityRelaunch(entry.getKey());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ handleRelaunchActivityLocally(entry.getKey());
}
}
}
private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
- LoadedApk apk = peekLoadedApk(data.pkg, false);
+ LoadedApk apk = peekPackageInfo(data.pkg, false);
if (apk != null) {
apk.setCompatibilityInfo(data.info);
}
- apk = peekLoadedApk(data.pkg, true);
+ apk = peekPackageInfo(data.pkg, true);
if (apk != null) {
apk.setCompatibilityInfo(data.info);
}
@@ -4437,7 +4413,7 @@ public final class ActivityThread extends ClientTransactionHandler {
checkAndBlockForNetworkAccess();
deliverResults(r, results);
if (resumed) {
- r.activity.performResume(false);
+ r.activity.performResume(false, "handleSendResult");
r.activity.mTemporaryPause = false;
}
}
@@ -4445,7 +4421,7 @@ public final class ActivityThread extends ClientTransactionHandler {
/** Core implementation of activity destroy call. */
ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
- int configChanges, boolean getNonConfigInstance) {
+ int configChanges, boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = mActivities.get(token);
Class<? extends Activity> activityClass = null;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
@@ -4459,21 +4435,7 @@ public final class ActivityThread extends ClientTransactionHandler {
performPauseActivityIfNeeded(r, "destroy");
if (!r.stopped) {
- try {
- r.activity.performStop(r.mPreserveWindow);
- } catch (SuperNotCalledException e) {
- throw e;
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to stop activity "
- + safeToComponentShortString(r.intent)
- + ": " + e.toString(), e);
- }
- }
- r.setState(ON_STOP);
- EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(),
- r.activity.getComponentName().getClassName(), "destroy");
+ callActivityOnStop(r, false /* saveState */, "destroy");
}
if (getNonConfigInstance) {
try {
@@ -4511,6 +4473,12 @@ public final class ActivityThread extends ClientTransactionHandler {
r.setState(ON_DESTROY);
}
mActivities.remove(token);
+ mRecentDestroyedActivities.add(0, new DestroyedActivityInfo(token.hashCode(), reason));
+
+ final int recentDestroyedActivitiesSize = mRecentDestroyedActivities.size();
+ if (recentDestroyedActivitiesSize > MAX_DESTROYED_ACTIVITIES) {
+ mRecentDestroyedActivities.remove(recentDestroyedActivitiesSize - 1);
+ }
StrictMode.decrementExpectedActivityCount(activityClass);
return r;
}
@@ -4522,9 +4490,9 @@ public final class ActivityThread extends ClientTransactionHandler {
@Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
- boolean getNonConfigInstance) {
+ boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
- configChanges, getNonConfigInstance);
+ configChanges, getNonConfigInstance, reason);
if (r != null) {
cleanUpPendingRemoveWindows(r, finishing);
WindowManager wm = r.activity.getWindowManager();
@@ -4592,15 +4560,12 @@ public final class ActivityThread extends ClientTransactionHandler {
mSomeActivitiesChanged = true;
}
- /**
- * @param preserveWindow Whether the activity should try to reuse the window it created,
- * including the decor view after the relaunch.
- */
- public final void requestRelaunchActivity(IBinder token,
+ @Override
+ public ActivityClientRecord prepareRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config,
- Configuration overrideConfig, boolean fromServer, boolean preserveWindow) {
+ int configChanges, MergedConfiguration config, boolean preserveWindow) {
ActivityClientRecord target = null;
+ boolean scheduleRelaunch = false;
synchronized (mResourcesManager) {
for (int i=0; i<mRelaunchingActivities.size(); i++) {
@@ -4622,57 +4587,31 @@ public final class ActivityThread extends ClientTransactionHandler {
r.pendingIntents = pendingNewIntents;
}
}
-
- // For each relaunch request, activity manager expects an answer
- if (!r.onlyLocalRequest && fromServer) {
- try {
- ActivityManager.getService().activityRelaunched(token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
break;
}
}
if (target == null) {
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null, fromServer:"
- + fromServer);
+ if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null");
target = new ActivityClientRecord();
target.token = token;
target.pendingResults = pendingResults;
target.pendingIntents = pendingNewIntents;
target.mPreserveWindow = preserveWindow;
- if (!fromServer) {
- final ActivityClientRecord existing = mActivities.get(token);
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + existing);
- if (existing != null) {
- if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: paused= "
- + existing.paused);;
- target.startsNotResumed = existing.paused;
- target.overrideConfig = existing.overrideConfig;
- }
- target.onlyLocalRequest = true;
- }
mRelaunchingActivities.add(target);
- sendMessage(H.RELAUNCH_ACTIVITY, target);
- }
-
- if (fromServer) {
- target.startsNotResumed = notResumed;
- target.onlyLocalRequest = false;
- }
- if (config != null) {
- target.createdConfig = config;
- }
- if (overrideConfig != null) {
- target.overrideConfig = overrideConfig;
+ scheduleRelaunch = true;
}
+ target.createdConfig = config.getGlobalConfiguration();
+ target.overrideConfig = config.getOverrideConfiguration();
target.pendingConfigChanges |= configChanges;
}
+
+ return scheduleRelaunch ? target : null;
}
- private void handleRelaunchActivity(ActivityClientRecord tmp) {
+ @Override
+ public void handleRelaunchActivity(ActivityClientRecord tmp,
+ PendingTransactionActions pendingActions) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
@@ -4741,18 +4680,10 @@ public final class ActivityThread extends ClientTransactionHandler {
ActivityClientRecord r = mActivities.get(tmp.token);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
if (r == null) {
- if (!tmp.onlyLocalRequest) {
- try {
- ActivityManager.getService().activityRelaunched(tmp.token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
return;
}
r.activity.mConfigChangeFlags |= configChanges;
- r.onlyLocalRequest = tmp.onlyLocalRequest;
r.mPreserveWindow = tmp.mPreserveWindow;
r.activity.mChangingConfigurations = true;
@@ -4769,70 +4700,113 @@ public final class ActivityThread extends ClientTransactionHandler {
// preserved by the server, so we want to notify it that we are preparing to replace
// everything
try {
- if (r.mPreserveWindow || r.onlyLocalRequest) {
+ if (r.mPreserveWindow) {
WindowManagerGlobal.getWindowSession().prepareToReplaceWindows(
- r.token, !r.onlyLocalRequest);
+ r.token, true /* childrenOnly */);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+ pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
+
+ if (pendingActions != null) {
+ // Only report a successful relaunch to WindowManager.
+ pendingActions.setReportRelaunchToWindowManager(true);
+ }
+ }
+
+ /** Performs the activity relaunch locally vs. requesting from system-server. */
+ void handleRelaunchActivityLocally(IBinder token) {
+ if (Looper.myLooper() != getLooper()) {
+ throw new IllegalStateException("Must be called from main thread");
+ }
+
+ final ActivityClientRecord r = mActivities.get(token);
+ if (r == null) {
+ return;
+ }
+
+ final int prevState = r.getLifecycleState();
+
+ if (prevState < ON_RESUME) {
+ Log.w(TAG, "Activity needs to be already resumed in other to be relaunched.");
+ return;
+ }
+
+
+ // Initialize a relaunch request.
+ final MergedConfiguration mergedConfiguration = new MergedConfiguration(
+ r.createdConfig != null ? r.createdConfig : mConfiguration,
+ r.overrideConfig);
+ final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
+ null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */,
+ mergedConfiguration, r.mPreserveWindow);
+ // Make sure to match the existing lifecycle state in the end of the transaction.
+ final ActivityLifecycleItem lifecycleRequest =
+ TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
+ // Schedule the transaction.
+ final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
+ transaction.addCallback(activityRelaunchItem);
+ transaction.setLifecycleStateRequest(lifecycleRequest);
+ executeTransaction(transaction);
+ }
+
+ private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
+ PendingTransactionActions pendingActions, boolean startsNotResumed,
+ Configuration overrideConfig, String reason) {
// Need to ensure state is saved.
if (!r.paused) {
- performPauseActivity(r.token, false, r.isPreHoneycomb(), "handleRelaunchActivity",
- null /* pendingActions */);
+ performPauseActivity(r, false, reason, null /* pendingActions */);
}
- if (r.state == null && !r.stopped && !r.isPreHoneycomb()) {
- callCallActivityOnSaveInstanceState(r);
+ if (!r.stopped) {
+ callActivityOnStop(r, true /* saveState */, reason);
}
- handleDestroyActivity(r.token, false, configChanges, true);
+ handleDestroyActivity(r.token, false, configChanges, true, reason);
r.activity = null;
r.window = null;
r.hideForNow = false;
r.nextIdle = null;
// Merge any pending results and pending intents; don't just replace them
- if (tmp.pendingResults != null) {
+ if (pendingResults != null) {
if (r.pendingResults == null) {
- r.pendingResults = tmp.pendingResults;
+ r.pendingResults = pendingResults;
} else {
- r.pendingResults.addAll(tmp.pendingResults);
+ r.pendingResults.addAll(pendingResults);
}
}
- if (tmp.pendingIntents != null) {
+ if (pendingIntents != null) {
if (r.pendingIntents == null) {
- r.pendingIntents = tmp.pendingIntents;
+ r.pendingIntents = pendingIntents;
} else {
- r.pendingIntents.addAll(tmp.pendingIntents);
+ r.pendingIntents.addAll(pendingIntents);
}
}
- r.startsNotResumed = tmp.startsNotResumed;
- r.overrideConfig = tmp.overrideConfig;
+ r.startsNotResumed = startsNotResumed;
+ r.overrideConfig = overrideConfig;
- // 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 {
- ActivityManager.getService().activityRelaunched(r.token);
- if (r.window != null) {
- r.window.reportActivityRelaunched();
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ @Override
+ public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) {
+ try {
+ ActivityManager.getService().activityRelaunched(token);
+ final ActivityClientRecord r = mActivities.get(token);
+ if (pendingActions.shouldReportRelaunchToWindowManager() && r != null
+ && r.window != null) {
+ r.window.reportActivityRelaunched();
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
- private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) {
+ private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
r.state = new Bundle();
r.state.setAllowFds(false);
if (r.isPersistable()) {
@@ -4861,7 +4835,7 @@ public final class ActivityThread extends ClientTransactionHandler {
if (a != null) {
Configuration thisConfig = applyConfigCompatMainThread(
mCurDefaultDisplayDpi, newConfig,
- ar.loadedApk.getCompatibilityInfo());
+ ar.packageInfo.getCompatibilityInfo());
if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
// If the activity is currently resumed, its configuration
// needs to change right now.
@@ -5114,6 +5088,8 @@ public final class ActivityThread extends ClientTransactionHandler {
// as that method uses the same check on the activity config override as well.
final boolean equivalent = config != null && mConfiguration != null
&& (0 == mConfiguration.diffPublicOnly(config));
+ final Theme systemTheme = getSystemContext().getTheme();
+ final Theme systemUiTheme = getSystemUiContext().getTheme();
synchronized (mResourcesManager) {
if (mPendingConfiguration != null) {
@@ -5146,12 +5122,10 @@ public final class ActivityThread extends ClientTransactionHandler {
configDiff = mConfiguration.updateFrom(config);
config = applyCompatConfiguration(mCurDefaultDisplayDpi);
- final Theme systemTheme = getSystemContext().getTheme();
if ((systemTheme.getChangingConfigurations() & configDiff) != 0) {
systemTheme.rebase();
}
- final Theme systemUiTheme = getSystemUiContext().getTheme();
if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) {
systemUiTheme.rebase();
}
@@ -5218,7 +5192,7 @@ public final class ActivityThread extends ClientTransactionHandler {
newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1;
handleConfigurationChanged(newConfig, null);
- requestRelaunchAllActivities();
+ relaunchAllActivities();
}
static void freeTextLayoutCachesIfNeeded(int configDiff) {
@@ -5347,7 +5321,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
- boolean hasLoadedApk = false;
+ boolean hasPkgInfo = false;
switch (cmd) {
case ApplicationThreadConstants.PACKAGE_REMOVED:
case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL:
@@ -5358,14 +5332,14 @@ public final class ActivityThread extends ClientTransactionHandler {
}
synchronized (mResourcesManager) {
for (int i = packages.length - 1; i >= 0; i--) {
- if (!hasLoadedApk) {
+ if (!hasPkgInfo) {
WeakReference<LoadedApk> ref = mPackages.get(packages[i]);
if (ref != null && ref.get() != null) {
- hasLoadedApk = true;
+ hasPkgInfo = true;
} else {
ref = mResourcePackages.get(packages[i]);
if (ref != null && ref.get() != null) {
- hasLoadedApk = true;
+ hasPkgInfo = true;
}
}
}
@@ -5385,21 +5359,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 loadedApk = ref != null ? ref.get() : null;
- if (loadedApk != null) {
- hasLoadedApk = true;
+ LoadedApk pkgInfo = ref != null ? ref.get() : null;
+ if (pkgInfo != null) {
+ hasPkgInfo = true;
} else {
ref = mResourcePackages.get(packages[i]);
- loadedApk = ref != null ? ref.get() : null;
- if (loadedApk != null) {
- hasLoadedApk = true;
+ pkgInfo = ref != null ? ref.get() : null;
+ if (pkgInfo != null) {
+ hasPkgInfo = true;
}
}
// If the package is being replaced, yet it still has a valid
// LoadedApk object, the package was updated with _DONT_KILL.
// Adjust it's internal references to the application info and
// resources.
- if (loadedApk != null) {
+ if (pkgInfo != null) {
try {
final String packageName = packages[i];
final ApplicationInfo aInfo =
@@ -5413,13 +5387,13 @@ public final class ActivityThread extends ClientTransactionHandler {
if (ar.activityInfo.applicationInfo.packageName
.equals(packageName)) {
ar.activityInfo.applicationInfo = aInfo;
- ar.loadedApk = loadedApk;
+ ar.packageInfo = pkgInfo;
}
}
}
final List<String> oldPaths =
sPackageManager.getPreviousCodePaths(packageName);
- loadedApk.updateApplicationInfo(aInfo, oldPaths);
+ pkgInfo.updateApplicationInfo(aInfo, oldPaths);
} catch (RemoteException e) {
}
}
@@ -5428,7 +5402,7 @@ public final class ActivityThread extends ClientTransactionHandler {
break;
}
}
- ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasLoadedApk);
+ ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo);
}
final void handleLowMemory() {
@@ -5610,6 +5584,13 @@ public final class ActivityThread extends ClientTransactionHandler {
Message.updateCheckRecycle(data.appInfo.targetSdkVersion);
+ // Prior to P, internal calls to decode Bitmaps used BitmapFactory,
+ // which may scale up to account for density. In P, we switched to
+ // ImageDecoder, which skips the upscale to save memory. ImageDecoder
+ // needs to still scale up in older apps, in case they rely on the
+ // size of the Bitmap without considering its density.
+ ImageDecoder.sApiLevel = data.appInfo.targetSdkVersion;
+
/*
* Before spawning a new process, reset the time zone to be the system time zone.
* This needs to be done because the system time zone could have changed after the
@@ -5636,10 +5617,10 @@ public final class ActivityThread extends ClientTransactionHandler {
applyCompatConfiguration(mCurDefaultDisplayDpi);
}
- data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo);
+ data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
if (agent != null) {
- handleAttachAgent(agent, data.loadedApk);
+ handleAttachAgent(agent, data.info);
}
/**
@@ -5684,7 +5665,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.loadedApk.getPackageName()
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ " is waiting for the debugger on port 8100...");
IActivityManager mgr = ActivityManager.getService();
@@ -5703,7 +5684,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
} else {
- Slog.w(TAG, "Application " + data.loadedApk.getPackageName()
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ " can be debugged on port 8100...");
}
}
@@ -5711,6 +5692,7 @@ public final class ActivityThread extends ClientTransactionHandler {
// Allow application-generated systrace messages if we're debuggable.
boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
Trace.setAppTracingAllowed(isAppDebuggable);
+ ThreadedRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
if (isAppDebuggable && data.enableBinderTracking) {
Binder.enableTracing();
}
@@ -5751,14 +5733,14 @@ public final class ActivityThread extends ClientTransactionHandler {
mInstrumentationAppDir = ii.sourceDir;
mInstrumentationSplitAppDirs = ii.splitSourceDirs;
mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
- mInstrumentedAppDir = data.loadedApk.getAppDir();
- mInstrumentedSplitAppDirs = data.loadedApk.getSplitAppDirs();
- mInstrumentedLibDir = data.loadedApk.getLibDir();
+ mInstrumentedAppDir = data.info.getAppDir();
+ mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
+ mInstrumentedLibDir = data.info.getLibDir();
} else {
ii = null;
}
- final ContextImpl appContext = ContextImpl.createAppContext(this, data.loadedApk);
+ final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
updateLocaleListFromAppContext(appContext,
mResourcesManager.getConfiguration().getLocales());
@@ -5769,6 +5751,8 @@ public final class ActivityThread extends ClientTransactionHandler {
} finally {
StrictMode.setThreadPolicyMask(oldMask);
}
+ } else {
+ ThreadedRenderer.setIsolatedProcess(true);
}
// If we use profiles, setup the dex reporter to notify package manager
@@ -5802,9 +5786,9 @@ public final class ActivityThread extends ClientTransactionHandler {
}
ii.copyTo(instrApp);
instrApp.initForUser(UserHandle.myUserId());
- final LoadedApk loadedApk = getLoadedApk(instrApp, data.compatInfo,
+ final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
- final ContextImpl instrContext = ContextImpl.createAppContext(this, loadedApk);
+ final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
final ClassLoader cl = instrContext.getClassLoader();
@@ -5849,7 +5833,11 @@ 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.loadedApk.makeApplication(data.restrictedBackupMode, null);
+ app = data.info.makeApplication(data.restrictedBackupMode, null);
+
+ // Propagate autofill compat state
+ app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
+
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
@@ -5893,21 +5881,23 @@ public final class ActivityThread extends ClientTransactionHandler {
// Preload fonts resources
FontsContract.setApplicationContextForResources(appContext);
- try {
- final ApplicationInfo info =
- getPackageManager().getApplicationInfo(
- data.appInfo.packageName,
- PackageManager.GET_META_DATA /*flags*/,
- UserHandle.myUserId());
- if (info.metaData != null) {
- final int preloadedFontsResource = info.metaData.getInt(
- ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
- if (preloadedFontsResource != 0) {
- data.loadedApk.getResources().preloadFonts(preloadedFontsResource);
+ if (!Process.isIsolated()) {
+ try {
+ final ApplicationInfo info =
+ getPackageManager().getApplicationInfo(
+ data.appInfo.packageName,
+ PackageManager.GET_META_DATA /*flags*/,
+ UserHandle.myUserId());
+ if (info.metaData != null) {
+ final int preloadedFontsResource = info.metaData.getInt(
+ ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
+ if (preloadedFontsResource != 0) {
+ data.info.getResources().preloadFonts(preloadedFontsResource);
+ }
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
}
}
@@ -6361,12 +6351,12 @@ public final class ActivityThread extends ClientTransactionHandler {
try {
final java.lang.ClassLoader cl = c.getClassLoader();
- LoadedApk loadedApk = peekLoadedApk(ai.packageName, true);
- if (loadedApk == null) {
+ LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
+ if (packageInfo == null) {
// System startup case.
- loadedApk = getSystemContext().mLoadedApk;
+ packageInfo = getSystemContext().mPackageInfo;
}
- localProvider = loadedApk.getAppFactory()
+ localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
if (provider == null) {
@@ -6515,8 +6505,8 @@ public final class ActivityThread extends ClientTransactionHandler {
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
ContextImpl context = ContextImpl.createAppContext(
- this, getSystemContext().mLoadedApk);
- mInitialApplication = context.mLoadedApk.makeApplication(true, null);
+ this, getSystemContext().mPackageInfo);
+ mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
diff --git a/android/app/ActivityView.java b/android/app/ActivityView.java
index 5d0143a5..7032a2fe 100644
--- a/android/app/ActivityView.java
+++ b/android/app/ActivityView.java
@@ -27,6 +27,7 @@ import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -308,8 +309,14 @@ public class ActivityView extends ViewGroup {
return;
}
- mInputForwarder = InputManager.getInstance().createInputForwarder(
- mVirtualDisplay.getDisplay().getDisplayId());
+ final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ wm.dontOverrideDisplayInfo(displayId);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ mInputForwarder = InputManager.getInstance().createInputForwarder(displayId);
mTaskStackListener = new TaskBackgroundChangeListener();
try {
mActivityManager.registerTaskStackListener(mTaskStackListener);
diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java
index 382719b4..124f9338 100644
--- a/android/app/AlarmManager.java
+++ b/android/app/AlarmManager.java
@@ -178,6 +178,7 @@ public class AlarmManager {
public static final int FLAG_IDLE_UNTIL = 1<<4;
private final IAlarmManager mService;
+ private final Context mContext;
private final String mPackageName;
private final boolean mAlwaysExact;
private final int mTargetSdkVersion;
@@ -265,6 +266,7 @@ public class AlarmManager {
AlarmManager(IAlarmManager service, Context ctx) {
mService = service;
+ mContext = ctx;
mPackageName = ctx.getPackageName();
mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
@@ -1028,7 +1030,7 @@ public class AlarmManager {
* @see #ACTION_NEXT_ALARM_CLOCK_CHANGED
*/
public AlarmClockInfo getNextAlarmClock() {
- return getNextAlarmClock(UserHandle.myUserId());
+ return getNextAlarmClock(mContext.getUserId());
}
/**
diff --git a/android/app/AppComponentFactory.java b/android/app/AppComponentFactory.java
index 4df73799..cfaeec90 100644
--- a/android/app/AppComponentFactory.java
+++ b/android/app/AppComponentFactory.java
@@ -36,6 +36,10 @@ 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.
+ * <p>
+ * This method is only intended to provide a hook for instantiation. It does not provide
+ * earlier access to the Application object. The returned object will not be initialized
+ * as a Context yet and should not be used to interact with other android APIs.
*
* @param cl The default classloader to use for instantiation.
* @param className The class to be instantiated.
@@ -50,6 +54,10 @@ public class AppComponentFactory {
* 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.
+ * <p>
+ * This method is only intended to provide a hook for instantiation. It does not provide
+ * earlier access to the Activity object. The returned object will not be initialized
+ * as a Context yet and should not be used to interact with other android APIs.
*
* @param cl The default classloader to use for instantiation.
* @param className The class to be instantiated.
@@ -80,6 +88,10 @@ public class AppComponentFactory {
* 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.
+ * <p>
+ * This method is only intended to provide a hook for instantiation. It does not provide
+ * earlier access to the Service object. The returned object will not be initialized
+ * as a Context yet and should not be used to interact with other android APIs.
*
* @param cl The default classloader to use for instantiation.
* @param className The class to be instantiated.
@@ -95,6 +107,10 @@ public class AppComponentFactory {
* 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.
+ * <p>
+ * This method is only intended to provide a hook for instantiation. It does not provide
+ * earlier access to the ContentProvider object. The returned object will not be initialized
+ * with a Context yet and should not be used to interact with other android APIs.
*
* @param cl The default classloader to use for instantiation.
* @param className The class to be instantiated.
@@ -108,5 +124,5 @@ public class AppComponentFactory {
/**
* @hide
*/
- public static AppComponentFactory DEFAULT = new AppComponentFactory();
+ public static final AppComponentFactory DEFAULT = new AppComponentFactory();
}
diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java
index e923fb21..ab0001ca 100644
--- a/android/app/AppOpsManager.java
+++ b/android/app/AppOpsManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -30,12 +31,13 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
+import com.android.internal.app.IAppOpsActiveCallback;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
@@ -74,8 +76,9 @@ public class AppOpsManager {
final Context mContext;
final IAppOpsService mService;
- final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers
- = new ArrayMap<OnOpChangedListener, IAppOpsCallback>();
+ final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers = new ArrayMap<>();
+ final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers =
+ new ArrayMap<>();
static IBinder sToken;
@@ -164,12 +167,14 @@ public class AppOpsManager {
/** @hide */
public static final int OP_WRITE_SETTINGS = 23;
/** @hide Required to draw on top of other apps. */
+ @TestApi
public static final int OP_SYSTEM_ALERT_WINDOW = 24;
/** @hide */
public static final int OP_ACCESS_NOTIFICATIONS = 25;
/** @hide */
public static final int OP_CAMERA = 26;
/** @hide */
+ @TestApi
public static final int OP_RECORD_AUDIO = 27;
/** @hide */
public static final int OP_PLAY_AUDIO = 28;
@@ -265,8 +270,12 @@ public class AppOpsManager {
public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73;
/** @hide Continue handover of a call from another app */
public static final int OP_ACCEPT_HANDOVER = 74;
+ /** @hide Create and Manage IPsec Tunnels */
+ public static final int OP_MANAGE_IPSEC_TUNNELS = 75;
+ /** @hide Any app start foreground service. */
+ public static final int OP_START_FOREGROUND = 76;
/** @hide */
- public static final int _NUM_OP = 75;
+ public static final int _NUM_OP = 77;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -494,13 +503,20 @@ public class AppOpsManager {
public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
/** @hide */
@SystemApi @TestApi
- public static final String OPSTR_CHANGE_WIFI_STATE = "change_wifi_state";
+ public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages";
/** @hide */
@SystemApi @TestApi
- public static final String OPSTR_REQUEST_DELETE_PACKAGES = "request_delete_packages";
+ public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE =
+ "android:bind_accessibility_service";
/** @hide */
@SystemApi @TestApi
- public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "bind_accessibility_service";
+ public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_START_FOREGROUND = "android:start_foreground";
// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
@@ -543,13 +559,13 @@ public class AppOpsManager {
OP_CAMERA,
// Body sensors
OP_BODY_SENSORS,
- OP_REQUEST_DELETE_PACKAGES,
// APPOP PERMISSIONS
OP_ACCESS_NOTIFICATIONS,
OP_SYSTEM_ALERT_WINDOW,
OP_WRITE_SETTINGS,
OP_REQUEST_INSTALL_PACKAGES,
+ OP_START_FOREGROUND,
};
/**
@@ -636,6 +652,8 @@ public class AppOpsManager {
OP_REQUEST_DELETE_PACKAGES,
OP_BIND_ACCESSIBILITY_SERVICE,
OP_ACCEPT_HANDOVER,
+ OP_MANAGE_IPSEC_TUNNELS,
+ OP_START_FOREGROUND,
};
/**
@@ -717,6 +735,8 @@ public class AppOpsManager {
OPSTR_REQUEST_DELETE_PACKAGES,
OPSTR_BIND_ACCESSIBILITY_SERVICE,
OPSTR_ACCEPT_HANDOVER,
+ OPSTR_MANAGE_IPSEC_TUNNELS,
+ OPSTR_START_FOREGROUND,
};
/**
@@ -799,6 +819,8 @@ public class AppOpsManager {
"REQUEST_DELETE_PACKAGES",
"BIND_ACCESSIBILITY_SERVICE",
"ACCEPT_HANDOVER",
+ "MANAGE_IPSEC_TUNNELS",
+ "START_FOREGROUND",
};
/**
@@ -881,6 +903,8 @@ public class AppOpsManager {
Manifest.permission.REQUEST_DELETE_PACKAGES,
Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
Manifest.permission.ACCEPT_HANDOVER,
+ null, // no permission for OP_MANAGE_IPSEC_TUNNELS
+ Manifest.permission.FOREGROUND_SERVICE,
};
/**
@@ -964,6 +988,8 @@ public class AppOpsManager {
null, // REQUEST_DELETE_PACKAGES
null, // OP_BIND_ACCESSIBILITY_SERVICE
null, // ACCEPT_HANDOVER
+ null, // MANAGE_IPSEC_TUNNELS
+ null, // START_FOREGROUND
};
/**
@@ -1046,6 +1072,8 @@ public class AppOpsManager {
false, // OP_REQUEST_DELETE_PACKAGES
false, // OP_BIND_ACCESSIBILITY_SERVICE
false, // ACCEPT_HANDOVER
+ false, // MANAGE_IPSEC_HANDOVERS
+ false, // START_FOREGROUND
};
/**
@@ -1121,12 +1149,14 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE
AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
- AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
+ 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
AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER
+ AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS
+ AppOpsManager.MODE_ALLOWED, // OP_START_FOREGROUND
};
/**
@@ -1212,6 +1242,8 @@ public class AppOpsManager {
false, // OP_REQUEST_DELETE_PACKAGES
false, // OP_BIND_ACCESSIBILITY_SERVICE
false, // ACCEPT_HANDOVER
+ false, // MANAGE_IPSEC_TUNNELS
+ false, // START_FOREGROUND
};
/**
@@ -1533,6 +1565,24 @@ public class AppOpsManager {
}
/**
+ * Callback for notification of changes to operation active state.
+ *
+ * @hide
+ */
+ @TestApi
+ public interface OnOpActiveChangedListener {
+ /**
+ * Called when the active state of an app op changes.
+ *
+ * @param code The op code.
+ * @param uid The UID performing the operation.
+ * @param packageName The package performing the operation.
+ * @param active Whether the operation became active or inactive.
+ */
+ void onOpActiveChanged(int code, int uid, String packageName, boolean active);
+ }
+
+ /**
* Callback for notification of changes to operation state.
* This allows you to see the raw op codes instead of strings.
* @hide
@@ -1587,6 +1637,7 @@ public class AppOpsManager {
* @param mode The app op mode to set.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setUidMode(int code, int uid, int mode) {
try {
mService.setUidMode(code, uid, mode);
@@ -1606,7 +1657,7 @@ public class AppOpsManager {
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS)
+ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setUidMode(String appOp, int uid, int mode) {
try {
mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
@@ -1638,6 +1689,7 @@ public class AppOpsManager {
/** @hide */
@TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
@@ -1647,6 +1699,27 @@ public class AppOpsManager {
}
/**
+ * Change the operating mode for the given op in the given app package. You must pass
+ * in both the uid and name of the application whose mode is being modified; if these
+ * do not match, the modification will not be applied.
+ *
+ * @param op The operation to modify. One of the OPSTR_* constants.
+ * @param uid The user id of the application whose mode will be changed.
+ * @param packageName The name of the application package name whose mode will
+ * be changed.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+ public void setMode(String op, int uid, String packageName, int mode) {
+ try {
+ mService.setMode(strOpToOp(op), uid, packageName, mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set a non-persisted restriction on an audio operation at a stream-level.
* Restrictions are temporary additional constraints imposed on top of the persisted rules
* defined by {@link #setMode}.
@@ -1657,6 +1730,7 @@ public class AppOpsManager {
* @param exceptionPackages Optional list of packages to exclude from the restriction.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setRestriction(int code, @AttributeUsage int usage, int mode,
String[] exceptionPackages) {
try {
@@ -1668,9 +1742,10 @@ public class AppOpsManager {
}
/** @hide */
+ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void resetAllModes() {
try {
- mService.resetAllModes(UserHandle.myUserId(), null);
+ mService.resetAllModes(mContext.getUserId(), null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1696,6 +1771,8 @@ public class AppOpsManager {
/**
* Monitor for changes to the operating mode for the given op in the given app package.
+ * You can watch op changes only for your UID.
+ *
* @param op The operation to monitor, one of OPSTR_*.
* @param packageName The name of the application to monitor.
* @param callback Where to report changes.
@@ -1707,11 +1784,16 @@ public class AppOpsManager {
/**
* Monitor for changes to the operating mode for the given op in the given app package.
+ *
+ * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+ * you can watch changes only for your UID.
+ *
* @param op The operation to monitor, one of OP_*.
* @param packageName The name of the application to monitor.
* @param callback Where to report changes.
* @hide
*/
+ @RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true)
public void startWatchingMode(int op, String packageName, final OnOpChangedListener callback) {
synchronized (mModeWatchers) {
IAppOpsCallback cb = mModeWatchers.get(callback);
@@ -1753,6 +1835,80 @@ public class AppOpsManager {
}
}
+ /**
+ * Start watching for changes to the active state of app ops. An app op may be
+ * long running and it has a clear start and stop delimiters. If an op is being
+ * started or stopped by any package you will get a callback. To change the
+ * watched ops for a registered callback you need to unregister and register it
+ * again.
+ *
+ * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+ * you can watch changes only for your UID.
+ *
+ * @param ops The ops to watch.
+ * @param callback Where to report changes.
+ *
+ * @see #isOperationActive(int, int, String)
+ * @see #stopWatchingActive(OnOpActiveChangedListener)
+ * @see #startOp(int, int, String)
+ * @see #finishOp(int, int, String)
+ *
+ * @hide
+ */
+ @TestApi
+ // TODO: Uncomment below annotation once b/73559440 is fixed
+ // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
+ public void startWatchingActive(@NonNull int[] ops,
+ @NonNull OnOpActiveChangedListener callback) {
+ Preconditions.checkNotNull(ops, "ops cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ IAppOpsActiveCallback cb;
+ synchronized (mActiveWatchers) {
+ cb = mActiveWatchers.get(callback);
+ if (cb != null) {
+ return;
+ }
+ cb = new IAppOpsActiveCallback.Stub() {
+ @Override
+ public void opActiveChanged(int op, int uid, String packageName, boolean active) {
+ callback.onOpActiveChanged(op, uid, packageName, active);
+ }
+ };
+ mActiveWatchers.put(callback, cb);
+ }
+ try {
+ mService.startWatchingActive(ops, cb);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stop watching for changes to the active state of an app op. An app op may be
+ * long running and it has a clear start and stop delimiters. Unregistering a
+ * non-registered callback has no effect.
+ *
+ * @see #isOperationActive#(int, int, String)
+ * @see #startWatchingActive(int[], OnOpActiveChangedListener)
+ * @see #startOp(int, int, String)
+ * @see #finishOp(int, int, String)
+ *
+ * @hide
+ */
+ @TestApi
+ public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
+ synchronized (mActiveWatchers) {
+ final IAppOpsActiveCallback cb = mActiveWatchers.get(callback);
+ if (cb != null) {
+ try {
+ mService.stopWatchingActive(cb);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
private String buildSecurityExceptionMsg(int op, int uid, String packageName) {
return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op];
}
@@ -1991,15 +2147,11 @@ public class AppOpsManager {
* @hide
*/
public int noteOp(int op, int uid, String packageName) {
- try {
- int mode = mService.noteOperation(op, uid, packageName);
- if (mode == MODE_ERRORED) {
- throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
- }
- return mode;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ final int mode = noteOpNoThrow(op, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
+ return mode;
}
/**
@@ -2078,6 +2230,11 @@ public class AppOpsManager {
}
}
+ /** @hide */
+ public int startOp(int op) {
+ return startOp(op, Process.myUid(), mContext.getOpPackageName());
+ }
+
/**
* Report that an application has started executing a long-running operation. Note that you
* must pass in both the uid and name of the application to be checked; this function will
@@ -2086,6 +2243,7 @@ public class AppOpsManager {
* the current time and the operation will be marked as "running". In this case you must
* later call {@link #finishOp(int, int, String)} to report when the application is no
* longer performing the operation.
+ *
* @param op The operation to start. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
@@ -2096,15 +2254,34 @@ public class AppOpsManager {
* @hide
*/
public int startOp(int op, int uid, String packageName) {
- try {
- int mode = mService.startOperation(getToken(mService), op, uid, packageName);
- if (mode == MODE_ERRORED) {
- throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
- }
- return mode;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ return startOp(op, uid, packageName, false);
+ }
+
+ /**
+ * Report that an application has started executing a long-running operation. Similar
+ * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT}
+ * the operation should succeed since the caller has performed its standard permission
+ * checks which passed and would perform the protected operation for this mode.
+ *
+ * @param op The operation to start. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
+ * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+ *
+ * @throws SecurityException If the app has been configured to crash on this op or
+ * the package is not in the passed in UID.
+ *
+ * @hide
+ */
+ public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
+ final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
+ return mode;
}
/**
@@ -2113,18 +2290,32 @@ public class AppOpsManager {
* @hide
*/
public int startOpNoThrow(int op, int uid, String packageName) {
+ return startOpNoThrow(op, uid, packageName, false);
+ }
+
+ /**
+ * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a
+ * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @param op The operation to start. One of the OP_* constants.
+ * @param uid The user id of the application attempting to perform the operation.
+ * @param packageName The name of the application attempting to perform the operation.
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+ * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+ * causing the app to crash).
+ * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+ *
+ * @hide
+ */
+ public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
try {
- return mService.startOperation(getToken(mService), op, uid, packageName);
+ return mService.startOperation(getToken(mService), op, uid, packageName,
+ startIfModeDefault);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- /** @hide */
- public int startOp(int op) {
- return startOp(op, Process.myUid(), mContext.getOpPackageName());
- }
-
/**
* Report that an application is no longer performing an operation that had previously
* been started with {@link #startOp(int, int, String)}. There is no validation of input
@@ -2145,7 +2336,21 @@ public class AppOpsManager {
finishOp(op, Process.myUid(), mContext.getOpPackageName());
}
- /** @hide */
+ /**
+ * Checks whether the given op for a UID and package is active.
+ *
+ * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+ * you can query only for your UID.
+ *
+ * @see #startWatchingActive(int[], OnOpActiveChangedListener)
+ * @see #stopWatchingMode(OnOpChangedListener)
+ * @see #finishOp(int)
+ * @see #startOp(int)
+ *
+ * @hide */
+ @TestApi
+ // TODO: Uncomment below annotation once b/73559440 is fixed
+ // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
public boolean isOperationActive(int code, int uid, String packageName) {
try {
return mService.isOperationActive(code, uid, packageName);
diff --git a/android/app/Application.java b/android/app/Application.java
index 5822f5c8..4531f53b 100644
--- a/android/app/Application.java
+++ b/android/app/Application.java
@@ -16,8 +16,6 @@
package android.app;
-import java.util.ArrayList;
-
import android.annotation.CallSuper;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
@@ -26,6 +24,10 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import java.util.ArrayList;
/**
* Base class for maintaining global application state. You can provide your own
@@ -45,6 +47,7 @@ import android.os.Bundle;
* </p>
*/
public class Application extends ContextWrapper implements ComponentCallbacks2 {
+ private static final String TAG = "Application";
private ArrayList<ComponentCallbacks> mComponentCallbacks =
new ArrayList<ComponentCallbacks>();
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
@@ -86,11 +89,21 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
/**
* Called when the application is starting, before any activity, service,
* or receiver objects (excluding content providers) have been created.
- * Implementations should be as quick as possible (for example using
+ *
+ * <p>Implementations should be as quick as possible (for example using
* lazy initialization of state) since the time spent in this function
* directly impacts the performance of starting the first activity,
- * service, or receiver in a process.
- * If you override this method, be sure to call super.onCreate().
+ * service, or receiver in a process.</p>
+ *
+ * <p>If you override this method, be sure to call {@code super.onCreate()}.</p>
+ *
+ * <p class="note">Be aware that direct boot may also affect callback order on
+ * Android {@link android.os.Build.VERSION_CODES#N} and later devices.
+ * Until the user unlocks the device, only direct boot aware components are
+ * allowed to run. You should consider that all direct boot unaware
+ * components, including such {@link android.content.ContentProvider}, are
+ * disabled until user unlock happens, especially when component callback
+ * order matters.</p>
*/
@CallSuper
public void onCreate() {
@@ -180,6 +193,16 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
+ /**
+ * Returns the name of the current process. A package's default process name
+ * is the same as its package name. Non-default processes will look like
+ * "$PACKAGE_NAME:$NAME", where $NAME corresponds to an android:process
+ * attribute within AndroidManifest.xml.
+ */
+ public static String getProcessName() {
+ return ActivityThread.currentProcessName();
+ }
+
// ------------------ Internal API ------------------
/**
@@ -187,7 +210,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
*/
/* package */ final void attach(Context context) {
attachBaseContext(context);
- mLoadedApk = ContextImpl.getImpl(context).mLoadedApk;
+ mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
/* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) {
@@ -289,4 +312,47 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
}
-} \ No newline at end of file
+
+ /** @hide */
+ @Override
+ public AutofillManager.AutofillClient getAutofillClient() {
+ final AutofillManager.AutofillClient client = super.getAutofillClient();
+ if (client != null) {
+ return client;
+ }
+ if (android.view.autofill.Helper.sVerbose) {
+ Log.v(TAG, "getAutofillClient(): null on super, trying to find activity thread");
+ }
+ // Okay, ppl use the application context when they should not. This breaks
+ // autofill among other things. We pick the focused activity since autofill
+ // interacts only with the currently focused activity and we need the fill
+ // client only if a call comes from the focused activity. Sigh...
+ final ActivityThread activityThread = ActivityThread.currentActivityThread();
+ if (activityThread == null) {
+ return null;
+ }
+ final int activityCount = activityThread.mActivities.size();
+ for (int i = 0; i < activityCount; i++) {
+ final ActivityThread.ActivityClientRecord record =
+ activityThread.mActivities.valueAt(i);
+ if (record == null) {
+ continue;
+ }
+ final Activity activity = record.activity;
+ if (activity == null) {
+ continue;
+ }
+ if (activity.getWindow().getDecorView().hasFocus()) {
+ if (android.view.autofill.Helper.sVerbose) {
+ Log.v(TAG, "getAutofillClient(): found activity for " + this + ": " + activity);
+ }
+ return activity;
+ }
+ }
+ if (android.view.autofill.Helper.sVerbose) {
+ Log.v(TAG, "getAutofillClient(): none of the " + activityCount + " activities on "
+ + this + " have focus");
+ }
+ return null;
+ }
+}
diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java
index cc68c051..a68136b5 100644
--- a/android/app/ApplicationPackageManager.java
+++ b/android/app/ApplicationPackageManager.java
@@ -20,6 +20,7 @@ import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
+import android.annotation.UserIdInt;
import android.annotation.XmlRes;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -69,6 +70,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -140,6 +142,11 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
+ public int getUserId() {
+ return mContext.getUserId();
+ }
+
+ @Override
public PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException {
return getPackageInfoAsUser(packageName, flags, mContext.getUserId());
@@ -1026,19 +1033,25 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
- public ResolveInfo resolveService(Intent intent, int flags) {
+ public ResolveInfo resolveServiceAsUser(Intent intent, @ResolveInfoFlags int flags,
+ @UserIdInt int userId) {
try {
return mPM.resolveService(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags,
- mContext.getUserId());
+ userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
+ public ResolveInfo resolveService(Intent intent, int flags) {
+ return resolveServiceAsUser(intent, flags, mContext.getUserId());
+ }
+
+ @Override
@SuppressWarnings("unchecked")
public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
try {
@@ -1349,11 +1362,10 @@ public class ApplicationPackageManager extends PackageManager {
if (badgeColor == null) {
return null;
}
- badgeColor.setTint(getUserBadgeColor(user));
Drawable badgeForeground = getDrawableForDensity(
com.android.internal.R.drawable.ic_corp_badge_case, density);
- Drawable badge = new LayerDrawable(
- new Drawable[] {badgeColor, badgeForeground });
+ badgeForeground.setTint(getUserBadgeColor(user));
+ Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground });
return badge;
}
@@ -1409,7 +1421,7 @@ public class ApplicationPackageManager extends PackageManager {
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
- mContext.mLoadedApk);
+ mContext.mPackageInfo);
if (r != null) {
return r;
}
@@ -2140,16 +2152,43 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
- public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
- int userId) {
+ public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
+ PersistableBundle appExtras, PersistableBundle launcherExtras,
+ String dialogMessage) {
+ // TODO (b/75332201): Pass in the dialogMessage and use it in the interceptor dialog
try {
- return mPM.setPackagesSuspendedAsUser(packageNames, suspended, userId);
+ return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
+ launcherExtras, mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
+ public PersistableBundle getSuspendedPackageAppExtras(String packageName) {
+ try {
+ return mPM.getSuspendedPackageAppExtras(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public Bundle getSuspendedPackageAppExtras() {
+ final PersistableBundle extras = getSuspendedPackageAppExtras(mContext.getOpPackageName());
+ return extras != null ? new Bundle(extras.deepCopy()) : null;
+ }
+
+ @Override
+ public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras) {
+ try {
+ mPM.setSuspendedPackageAppExtras(packageName, appExtras, mContext.getUserId());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public boolean isPackageSuspendedForUser(String packageName, int userId) {
try {
return mPM.isPackageSuspendedForUser(packageName, userId);
@@ -2160,6 +2199,17 @@ public class ApplicationPackageManager extends PackageManager {
/** @hide */
@Override
+ public boolean isPackageSuspended(String packageName) {
+ return isPackageSuspendedForUser(packageName, mContext.getUserId());
+ }
+
+ @Override
+ public boolean isPackageSuspended() {
+ return isPackageSuspendedForUser(mContext.getOpPackageName(), mContext.getUserId());
+ }
+
+ /** @hide */
+ @Override
public void setApplicationCategoryHint(String packageName, int categoryHint) {
try {
mPM.setApplicationCategoryHint(packageName, categoryHint,
@@ -2797,4 +2847,22 @@ public class ApplicationPackageManager extends PackageManager {
return mArtManager;
}
}
+
+ @Override
+ public String getSystemTextClassifierPackageName() {
+ try {
+ return mPM.getSystemTextClassifierPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public boolean isPackageStateProtected(String packageName, int userId) {
+ try {
+ return mPM.isPackageStateProtected(packageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/android/app/ClientTransactionHandler.java b/android/app/ClientTransactionHandler.java
index 0f66652a..0639b000 100644
--- a/android/app/ClientTransactionHandler.java
+++ b/android/app/ClientTransactionHandler.java
@@ -17,11 +17,14 @@ package android.app;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PendingTransactionActions;
+import android.app.servertransaction.TransactionExecutor;
import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
+import android.util.MergedConfiguration;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
import java.io.PrintWriter;
@@ -42,6 +45,23 @@ public abstract class ClientTransactionHandler {
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}
+ /**
+ * Execute transaction immediately without scheduling it. This is used for local requests, so
+ * it will also recycle the transaction.
+ */
+ @VisibleForTesting
+ public void executeTransaction(ClientTransaction transaction) {
+ transaction.preExecute(this);
+ getTransactionExecutor().execute(transaction);
+ transaction.recycle();
+ }
+
+ /**
+ * Get the {@link TransactionExecutor} that will be performing lifecycle transitions and
+ * callbacks for activities.
+ */
+ abstract TransactionExecutor getTransactionExecutor();
+
abstract void sendMessage(int what, Object obj);
@@ -60,19 +80,36 @@ public abstract class ClientTransactionHandler {
/** Destroy the activity. */
public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
- boolean getNonConfigInstance);
+ boolean getNonConfigInstance, String reason);
/** Pause the activity. */
public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
- int configChanges, boolean dontReport, PendingTransactionActions pendingActions);
+ int configChanges, PendingTransactionActions pendingActions, String reason);
- /** Resume the activity. */
- public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
- String reason);
+ /**
+ * Resume the activity.
+ * @param token Target activity token.
+ * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
+ * request for a transaction.
+ * @param isForward Flag indicating if next transition is forward.
+ * @param reason Reason for performing this operation.
+ */
+ public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest,
+ boolean isForward, String reason);
- /** Stop the activity. */
+ /**
+ * Stop the activity.
+ * @param token Target activity token.
+ * @param show Flag indicating whether activity is still shown.
+ * @param configChanges Activity configuration changes.
+ * @param pendingActions Pending actions to be used on this or later stages of activity
+ * transaction.
+ * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
+ * request for a transaction.
+ * @param reason Reason for performing this operation.
+ */
public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
- PendingTransactionActions pendingActions);
+ PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
/** Report that activity was stopped to server. */
public abstract void reportStop(PendingTransactionActions pendingActions);
@@ -111,7 +148,7 @@ public abstract class ClientTransactionHandler {
PendingTransactionActions pendingActions);
/** Get package info. */
- public abstract LoadedApk getLoadedApkNoCheck(ApplicationInfo ai,
+ public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo);
/** Deliver app configuration change notification. */
@@ -124,6 +161,39 @@ public abstract class ClientTransactionHandler {
public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token);
/**
+ * Prepare activity relaunch to update internal bookkeeping. This is used to track multiple
+ * relaunch and config update requests.
+ * @param token Activity token.
+ * @param pendingResults Activity results to be delivered.
+ * @param pendingNewIntents New intent messages to be delivered.
+ * @param configChanges Mask of configuration changes that have occurred.
+ * @param config New configuration applied to the activity.
+ * @param preserveWindow Whether the activity should try to reuse the window it created,
+ * including the decor view after the relaunch.
+ * @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during
+ * relaunch, or {@code null} if relaunch cancelled.
+ */
+ public abstract ActivityThread.ActivityClientRecord prepareRelaunchActivity(IBinder token,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ int configChanges, MergedConfiguration config, boolean preserveWindow);
+
+ /**
+ * Perform activity relaunch.
+ * @param r Activity client record prepared for relaunch.
+ * @param pendingActions Pending actions to be used on later stages of activity transaction.
+ * */
+ public abstract void handleRelaunchActivity(ActivityThread.ActivityClientRecord r,
+ PendingTransactionActions pendingActions);
+
+ /**
+ * Report that relaunch request was handled.
+ * @param token Target activity token.
+ * @param pendingActions Pending actions initialized on earlier stages of activity transaction.
+ * Used to check if we should report relaunch to WM.
+ * */
+ public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions);
+
+ /**
* Debugging output.
* @param pw {@link PrintWriter} to write logs to.
* @param prefix Prefix to prepend to output.
diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java
index 4914ffaf..71b88fa4 100644
--- a/android/app/ContextImpl.java
+++ b/android/app/ContextImpl.java
@@ -18,6 +18,7 @@ package android.app;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -91,6 +92,7 @@ import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
@@ -159,12 +161,12 @@ class ContextImpl extends Context {
private ArrayMap<String, File> mSharedPrefsPaths;
final @NonNull ActivityThread mMainThread;
- final @NonNull LoadedApk mLoadedApk;
+ final @NonNull LoadedApk mPackageInfo;
private @Nullable ClassLoader mClassLoader;
private final @Nullable IBinder mActivityToken;
- private final @Nullable UserHandle mUser;
+ private final @NonNull UserHandle mUser;
private final ApplicationContentResolver mContentResolver;
@@ -187,6 +189,7 @@ class ContextImpl extends Context {
private @Nullable String mSplitName = null;
private AutofillClient mAutofillClient = null;
+ private boolean mIsAutofillCompatEnabled;
private final Object mSync = new Object();
@@ -206,6 +209,17 @@ class ContextImpl extends Context {
// The system service cache for the system services that are cached per-ContextImpl.
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
+ static final int STATE_UNINITIALIZED = 0;
+ static final int STATE_INITIALIZING = 1;
+ static final int STATE_READY = 2;
+
+ /**
+ * Initialization state for each service. Any of {@link #STATE_UNINITIALIZED},
+ * {@link #STATE_INITIALIZING} or {@link #STATE_READY},
+ */
+ final AtomicInteger[] mServiceInitializationStateArray =
+ SystemServiceRegistry.createServiceInitializationStateArray();
+
static ContextImpl getImpl(Context context) {
Context nextContext;
while ((context instanceof ContextWrapper) &&
@@ -257,8 +271,8 @@ class ContextImpl extends Context {
@Override
public Context getApplicationContext() {
- return (mLoadedApk != null) ?
- mLoadedApk.getApplication() : mMainThread.getApplication();
+ return (mPackageInfo != null) ?
+ mPackageInfo.getApplication() : mMainThread.getApplication();
}
@Override
@@ -302,15 +316,15 @@ class ContextImpl extends Context {
@Override
public ClassLoader getClassLoader() {
- return mClassLoader != null ? mClassLoader : (mLoadedApk != null ? mLoadedApk.getClassLoader() : ClassLoader.getSystemClassLoader());
+ return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
}
@Override
public String getPackageName() {
- if (mLoadedApk != null) {
- return mLoadedApk.getPackageName();
+ if (mPackageInfo != null) {
+ return mPackageInfo.getPackageName();
}
- // No mLoadedApk means this is a Context for the system itself,
+ // No mPackageInfo means this is a Context for the system itself,
// and this here is its name.
return "android";
}
@@ -329,24 +343,24 @@ class ContextImpl extends Context {
@Override
public ApplicationInfo getApplicationInfo() {
- if (mLoadedApk != null) {
- return mLoadedApk.getApplicationInfo();
+ if (mPackageInfo != null) {
+ return mPackageInfo.getApplicationInfo();
}
throw new RuntimeException("Not supported in system context");
}
@Override
public String getPackageResourcePath() {
- if (mLoadedApk != null) {
- return mLoadedApk.getResDir();
+ if (mPackageInfo != null) {
+ return mPackageInfo.getResDir();
}
throw new RuntimeException("Not supported in system context");
}
@Override
public String getPackageCodePath() {
- if (mLoadedApk != null) {
- return mLoadedApk.getAppDir();
+ if (mPackageInfo != null) {
+ return mPackageInfo.getAppDir();
}
throw new RuntimeException("Not supported in system context");
}
@@ -356,7 +370,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 (mLoadedApk.getApplicationInfo().targetSdkVersion <
+ if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
@@ -408,6 +422,7 @@ class ContextImpl extends Context {
return sp;
}
+ @GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
@@ -913,14 +928,14 @@ class ContextImpl extends Context {
/** @hide */
@Override
- public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivities() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
+ " Is this really what you want?");
}
- mMainThread.getInstrumentation().execStartActivitiesAsUser(
+ return mMainThread.getInstrumentation().execStartActivitiesAsUser(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intents, options, userHandle.getIdentifier());
}
@@ -1104,11 +1119,11 @@ class ContextImpl extends Context {
warnIfCallingFromSystemProcess();
IIntentReceiver rd = null;
if (resultReceiver != null) {
- if (mLoadedApk != null) {
+ if (mPackageInfo != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = mLoadedApk.getReceiverDispatcher(
+ rd = mPackageInfo.getReceiverDispatcher(
resultReceiver, getOuterContext(), scheduler,
mMainThread.getInstrumentation(), false);
} else {
@@ -1208,11 +1223,11 @@ class ContextImpl extends Context {
Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
IIntentReceiver rd = null;
if (resultReceiver != null) {
- if (mLoadedApk != null) {
+ if (mPackageInfo != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = mLoadedApk.getReceiverDispatcher(
+ rd = mPackageInfo.getReceiverDispatcher(
resultReceiver, getOuterContext(), scheduler,
mMainThread.getInstrumentation(), false);
} else {
@@ -1262,11 +1277,11 @@ class ContextImpl extends Context {
warnIfCallingFromSystemProcess();
IIntentReceiver rd = null;
if (resultReceiver != null) {
- if (mLoadedApk != null) {
+ if (mPackageInfo != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = mLoadedApk.getReceiverDispatcher(
+ rd = mPackageInfo.getReceiverDispatcher(
resultReceiver, getOuterContext(), scheduler,
mMainThread.getInstrumentation(), false);
} else {
@@ -1344,11 +1359,11 @@ class ContextImpl extends Context {
Bundle initialExtras) {
IIntentReceiver rd = null;
if (resultReceiver != null) {
- if (mLoadedApk != null) {
+ if (mPackageInfo != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = mLoadedApk.getReceiverDispatcher(
+ rd = mPackageInfo.getReceiverDispatcher(
resultReceiver, getOuterContext(), scheduler,
mMainThread.getInstrumentation(), false);
} else {
@@ -1425,11 +1440,11 @@ class ContextImpl extends Context {
Handler scheduler, Context context, int flags) {
IIntentReceiver rd = null;
if (receiver != null) {
- if (mLoadedApk != null && context != null) {
+ if (mPackageInfo != null && context != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = mLoadedApk.getReceiverDispatcher(
+ rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
@@ -1456,8 +1471,8 @@ class ContextImpl extends Context {
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
- if (mLoadedApk != null) {
- IIntentReceiver rd = mLoadedApk.forgetReceiverDispatcher(
+ if (mPackageInfo != null) {
+ IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher(
getOuterContext(), receiver);
try {
ActivityManager.getService().unregisterReceiver(rd);
@@ -1565,8 +1580,7 @@ class ContextImpl extends Context {
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
warnIfCallingFromSystemProcess();
- return bindServiceCommon(service, conn, flags, mMainThread.getHandler(),
- Process.myUserHandle());
+ return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
}
/** @hide */
@@ -1590,7 +1604,7 @@ class ContextImpl extends Context {
@Override
public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler,
int flags) {
- return mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags);
+ return mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
}
/** @hide */
@@ -1612,16 +1626,16 @@ class ContextImpl extends Context {
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
- if (mLoadedApk != null) {
- sd = mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags);
+ if (mPackageInfo != null) {
+ sd = mPackageInfo.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 && mLoadedApk != null
- && mLoadedApk.getApplicationInfo().targetSdkVersion
+ if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null
+ && mPackageInfo.getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
flags |= BIND_WAIVE_PRIORITY;
}
@@ -1645,8 +1659,8 @@ class ContextImpl extends Context {
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
- if (mLoadedApk != null) {
- IServiceConnection sd = mLoadedApk.forgetServiceDispatcher(
+ if (mPackageInfo != null) {
+ IServiceConnection sd = mPackageInfo.forgetServiceDispatcher(
getOuterContext(), conn);
try {
ActivityManager.getService().unbindService(sd);
@@ -1699,6 +1713,9 @@ class ContextImpl extends Context {
Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
return PackageManager.PERMISSION_GRANTED;
}
+ Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " does not hold "
+ + permission);
+ return PackageManager.PERMISSION_DENIED;
}
try {
@@ -1991,20 +2008,40 @@ 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 loadedApk = mMainThread.getLoadedApk(application,
- mResources.getCompatibilityInfo(),
+ LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE);
- if (loadedApk != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken,
+ if (pi != null) {
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,
new UserHandle(UserHandle.getUserId(application.uid)), flags, null);
final int displayId = mDisplay != null
? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null,
+ c.setResources(createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo()));
if (c.mResources != null) {
return c;
@@ -2018,8 +2055,7 @@ class ContextImpl extends Context {
@Override
public Context createPackageContext(String packageName, int flags)
throws NameNotFoundException {
- return createPackageContextAsUser(packageName, flags,
- mUser != null ? mUser : Process.myUserHandle());
+ return createPackageContextAsUser(packageName, flags, mUser);
}
@Override
@@ -2028,21 +2064,20 @@ 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, mLoadedApk, null, mActivityToken, user,
+ return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user,
flags, null);
}
- LoadedApk loadedApk = mMainThread.getLoadedApkForPackageName(packageName,
- mResources.getCompatibilityInfo(),
+ LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
- if (loadedApk != null) {
- ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, user,
+ if (pi != null) {
+ ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,
flags, null);
final int displayId = mDisplay != null
? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null,
+ c.setResources(createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo()));
if (c.mResources != null) {
return c;
@@ -2056,21 +2091,30 @@ class ContextImpl extends Context {
@Override
public Context createContextForSplit(String splitName) throws NameNotFoundException {
- if (!mLoadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) {
+ if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
// All Splits are always loaded.
return this;
}
- final ClassLoader classLoader = mLoadedApk.getSplitClassLoader(splitName);
+ final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
+ final String[] paths = mPackageInfo.getSplitPaths(splitName);
- final ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, splitName,
+ final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName,
mActivityToken, mUser, mFlags, classLoader);
final int displayId = mDisplay != null
? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- context.setResources(mLoadedApk.getOrCreateResourcesForSplit(splitName,
- mActivityToken, displayId));
+ context.setResources(ResourcesManager.getInstance().getResources(
+ mActivityToken,
+ mPackageInfo.getResDir(),
+ paths,
+ mPackageInfo.getOverlayDirs(),
+ mPackageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId,
+ null,
+ mPackageInfo.getCompatibilityInfo(),
+ classLoader));
return context;
}
@@ -2080,11 +2124,11 @@ class ContextImpl extends Context {
throw new IllegalArgumentException("overrideConfiguration must not be null");
}
- ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName,
+ ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
mActivityToken, mUser, mFlags, mClassLoader);
final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
- context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId,
+ context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
return context;
}
@@ -2095,11 +2139,11 @@ class ContextImpl extends Context {
throw new IllegalArgumentException("display must not be null");
}
- ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName,
+ ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
mActivityToken, mUser, mFlags, mClassLoader);
final int displayId = display.getDisplayId();
- context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId,
+ context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
context.mDisplay = display;
return context;
@@ -2109,7 +2153,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, mLoadedApk, mSplitName, mActivityToken, mUser,
+ return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser,
flags, mClassLoader);
}
@@ -2117,7 +2161,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, mLoadedApk, mSplitName, mActivityToken, mUser,
+ return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser,
flags, mClassLoader);
}
@@ -2166,14 +2210,14 @@ class ContextImpl extends Context {
@Override
public File getDataDir() {
- if (mLoadedApk != null) {
+ if (mPackageInfo != null) {
File res = null;
if (isCredentialProtectedStorage()) {
- res = mLoadedApk.getCredentialProtectedDataDirFile();
+ res = mPackageInfo.getCredentialProtectedDataDirFile();
} else if (isDeviceProtectedStorage()) {
- res = mLoadedApk.getDeviceProtectedDataDirFile();
+ res = mPackageInfo.getDeviceProtectedDataDirFile();
} else {
- res = mLoadedApk.getDataDirFile();
+ res = mPackageInfo.getDataDirFile();
}
if (res != null) {
@@ -2207,6 +2251,12 @@ class ContextImpl extends Context {
/** {@hide} */
@Override
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ /** {@hide} */
+ @Override
public int getUserId() {
return mUser.getIdentifier();
}
@@ -2223,11 +2273,24 @@ class ContextImpl extends Context {
mAutofillClient = client;
}
+ /** @hide */
+ @Override
+ public boolean isAutofillCompatibilityEnabled() {
+ return mIsAutofillCompatEnabled;
+ }
+
+ /** @hide */
+ @TestApi
+ @Override
+ public void setAutofillCompatibilityEnabled(boolean autofillCompatEnabled) {
+ mIsAutofillCompatEnabled = autofillCompatEnabled;
+ }
+
static ContextImpl createSystemContext(ActivityThread mainThread) {
- LoadedApk loadedApk = new LoadedApk(mainThread);
- ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0,
+ LoadedApk packageInfo = new LoadedApk(mainThread);
+ ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
- context.setResources(loadedApk.getResources());
+ context.setResources(packageInfo.getResources());
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetrics());
return context;
@@ -2238,35 +2301,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 loadedApk = systemContext.mLoadedApk;
- ContextImpl context = new ContextImpl(null, systemContext.mMainThread, loadedApk, null,
+ final LoadedApk packageInfo = systemContext.mPackageInfo;
+ ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null,
null, null, 0, null);
- context.setResources(loadedApk.createResources(null, null, Display.DEFAULT_DISPLAY, null,
- loadedApk.getCompatibilityInfo()));
+ context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null,
+ packageInfo.getCompatibilityInfo()));
return context;
}
- 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,
+ 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,
null);
- context.setResources(loadedApk.getResources());
+ context.setResources(packageInfo.getResources());
return context;
}
static ContextImpl createActivityContext(ActivityThread mainThread,
- LoadedApk loadedApk, ActivityInfo activityInfo, IBinder activityToken, int displayId,
+ LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
- if (loadedApk == null) throw new IllegalArgumentException("loadedApk");
+ if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
- String[] splitDirs = loadedApk.getSplitResDirs();
- ClassLoader classLoader = loadedApk.getClassLoader();
+ String[] splitDirs = packageInfo.getSplitResDirs();
+ ClassLoader classLoader = packageInfo.getClassLoader();
- if (loadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) {
+ if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
try {
- classLoader = loadedApk.getSplitClassLoader(activityInfo.splitName);
- splitDirs = loadedApk.getSplitPaths(activityInfo.splitName);
+ classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
+ splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
} catch (NameNotFoundException e) {
// Nothing above us can handle a NameNotFoundException, better crash.
throw new RuntimeException(e);
@@ -2275,14 +2338,14 @@ class ContextImpl extends Context {
}
}
- ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName,
+ ContextImpl context = new ContextImpl(null, mainThread, packageInfo, 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)
- ? loadedApk.getCompatibilityInfo()
+ ? packageInfo.getCompatibilityInfo()
: CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
@@ -2290,10 +2353,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,
- loadedApk.getResDir(),
+ packageInfo.getResDir(),
splitDirs,
- loadedApk.getOverlayDirs(),
- loadedApk.getApplicationInfo().sharedLibraryFiles,
+ packageInfo.getOverlayDirs(),
+ packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
@@ -2304,7 +2367,7 @@ class ContextImpl extends Context {
}
private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
- @NonNull LoadedApk loadedApk, @Nullable String splitName,
+ @NonNull LoadedApk packageInfo, @Nullable String splitName,
@Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
@Nullable ClassLoader classLoader) {
mOuterContext = this;
@@ -2313,10 +2376,10 @@ class ContextImpl extends Context {
// location for application.
if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE
| Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) {
- final File dataDir = loadedApk.getDataDirFile();
- if (Objects.equals(dataDir, loadedApk.getCredentialProtectedDataDirFile())) {
+ final File dataDir = packageInfo.getDataDirFile();
+ if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) {
flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
- } else if (Objects.equals(dataDir, loadedApk.getDeviceProtectedDataDirFile())) {
+ } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) {
flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
}
}
@@ -2330,7 +2393,7 @@ class ContextImpl extends Context {
}
mUser = user;
- mLoadedApk = loadedApk;
+ mPackageInfo = packageInfo;
mSplitName = splitName;
mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
@@ -2341,8 +2404,8 @@ class ContextImpl extends Context {
setResources(container.mResources);
mDisplay = container.mDisplay;
} else {
- mBasePackageName = loadedApk.mPackageName;
- ApplicationInfo ainfo = loadedApk.getApplicationInfo();
+ mBasePackageName = packageInfo.mPackageName;
+ ApplicationInfo ainfo = packageInfo.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
@@ -2354,7 +2417,7 @@ class ContextImpl extends Context {
}
}
- mContentResolver = new ApplicationContentResolver(this, mainThread, user);
+ mContentResolver = new ApplicationContentResolver(this, mainThread);
}
void setResources(Resources r) {
@@ -2365,7 +2428,7 @@ class ContextImpl extends Context {
}
void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
- mLoadedApk.installSystemApplicationInfo(info, classLoader);
+ mPackageInfo.installSystemApplicationInfo(info, classLoader);
}
final void scheduleFinalCleanup(String who, String what) {
@@ -2374,7 +2437,7 @@ class ContextImpl extends Context {
final void performFinalCleanup(String who, String what) {
//Log.i(TAG, "Cleanup up context: " + this);
- mLoadedApk.removeContextRegistrations(getOuterContext(), who, what);
+ mPackageInfo.removeContextRegistrations(getOuterContext(), who, what);
}
final Context getReceiverRestrictedContext() {
@@ -2470,13 +2533,10 @@ class ContextImpl extends Context {
private static final class ApplicationContentResolver extends ContentResolver {
private final ActivityThread mMainThread;
- private final UserHandle mUser;
- public ApplicationContentResolver(
- Context context, ActivityThread mainThread, UserHandle user) {
+ public ApplicationContentResolver(Context context, ActivityThread mainThread) {
super(context);
mMainThread = Preconditions.checkNotNull(mainThread);
- mUser = Preconditions.checkNotNull(user);
}
@Override
@@ -2522,7 +2582,7 @@ class ContextImpl extends Context {
/** @hide */
protected int resolveUserIdFromAuthority(String auth) {
- return ContentProvider.getUserIdFromAuthority(auth, mUser.getIdentifier());
+ return ContentProvider.getUserIdFromAuthority(auth, getUserId());
}
}
}
diff --git a/android/app/Dialog.java b/android/app/Dialog.java
index 2b648ea6..e4a05834 100644
--- a/android/app/Dialog.java
+++ b/android/app/Dialog.java
@@ -291,7 +291,10 @@ public class Dialog implements DialogInterface, Window.Callback,
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
- mDecor.setVisibility(View.VISIBLE);
+ if (mDecor.getVisibility() != View.VISIBLE) {
+ mDecor.setVisibility(View.VISIBLE);
+ sendShowMessage();
+ }
}
return;
}
@@ -318,16 +321,20 @@ public class Dialog implements DialogInterface, Window.Callback,
}
WindowManager.LayoutParams l = mWindow.getAttributes();
+ boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
- WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
- nl.copyFrom(l);
- nl.softInputMode |=
+ l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
- l = nl;
+ restoreSoftInputMode = true;
}
mWindowManager.addView(mDecor, l);
+ if (restoreSoftInputMode) {
+ l.softInputMode &=
+ ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+ }
+
mShowing = true;
sendShowMessage();
@@ -609,18 +616,19 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* A key was pressed down.
+ * <p>
+ * If the focused view didn't want this event, this method is called.
+ * <p>
+ * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
+ * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
+ * KEYCODE_ESCAPE} to later handle them in {@link #onKeyUp}.
*
- * <p>If the focused view didn't want this event, this method is called.
- *
- * <p>The default implementation consumed the KEYCODE_BACK to later
- * handle it in {@link #onKeyUp}.
- *
* @see #onKeyUp
* @see android.view.KeyEvent
*/
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
event.startTracking();
return true;
}
@@ -640,16 +648,18 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* A key was released.
- *
- * <p>The default implementation handles KEYCODE_BACK to close the
- * dialog.
+ * <p>
+ * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
+ * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
+ * KEYCODE_ESCAPE} to close the dialog.
*
* @see #onKeyDown
- * @see KeyEvent
+ * @see android.view.KeyEvent
*/
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+ if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
+ && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
diff --git a/android/app/EphemeralResolverService.java b/android/app/EphemeralResolverService.java
deleted file mode 100644
index 427a0386..00000000
--- a/android/app/EphemeralResolverService.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2015 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.SystemApi;
-import android.app.Service;
-import android.app.InstantAppResolverService.InstantAppResolutionCallback;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.EphemeralResolveInfo;
-import android.content.pm.InstantAppResolveInfo;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Base class for implementing the resolver service.
- * @hide
- * @removed
- * @deprecated use InstantAppResolverService instead
- */
-@Deprecated
-@SystemApi
-public abstract class EphemeralResolverService extends InstantAppResolverService {
- private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
- private static final String TAG = "PackageManager";
-
- /**
- * Called to retrieve resolve info for ephemeral applications.
- *
- * @param digestPrefix The hash prefix of the ephemeral's domain.
- * @param prefixMask A mask that was applied to each digest prefix. This should
- * be used when comparing against the digest prefixes as all bits might
- * not be set.
- * @deprecated use {@link #onGetEphemeralResolveInfo(int[])} instead
- */
- @Deprecated
- public abstract List<EphemeralResolveInfo> onEphemeralResolveInfoList(
- int digestPrefix[], int prefix);
-
- /**
- * Called to retrieve resolve info for ephemeral applications.
- *
- * @param digestPrefix The hash prefix of the ephemeral's domain.
- */
- public List<EphemeralResolveInfo> onGetEphemeralResolveInfo(int digestPrefix[]) {
- return onEphemeralResolveInfoList(digestPrefix, 0xFFFFF000);
- }
-
- /**
- * Called to retrieve intent filters for ephemeral applications.
- *
- * @param hostName The name of the host to get intent filters for.
- */
- public EphemeralResolveInfo onGetEphemeralIntentFilter(String hostName) {
- throw new IllegalStateException("Must define");
- }
-
- @Override
- public Looper getLooper() {
- return super.getLooper();
- }
-
- @Override
- void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
- InstantAppResolutionCallback callback) {
- if (DEBUG_EPHEMERAL) {
- Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;"
- + " prefix: " + Arrays.toString(digestPrefix));
- }
- final List<EphemeralResolveInfo> response = onGetEphemeralResolveInfo(digestPrefix);
- final int responseSize = response == null ? 0 : response.size();
- final List<InstantAppResolveInfo> resultList = new ArrayList<>(responseSize);
- for (int i = 0; i < responseSize; i++) {
- resultList.add(response.get(i).getInstantAppResolveInfo());
- }
- callback.onInstantAppResolveInfo(resultList);
- }
-
- @Override
- void _onGetInstantAppIntentFilter(int[] digestPrefix, String token,
- String hostName, InstantAppResolutionCallback callback) {
- if (DEBUG_EPHEMERAL) {
- Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;"
- + " prefix: " + Arrays.toString(digestPrefix));
- }
- final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName);
- final List<InstantAppResolveInfo> resultList = new ArrayList<>(1);
- resultList.add(response.getInstantAppResolveInfo());
- callback.onInstantAppResolveInfo(resultList);
- }
-}
diff --git a/android/app/Fragment.java b/android/app/Fragment.java
index 4ff07f2e..8e8270a0 100644
--- a/android/app/Fragment.java
+++ b/android/app/Fragment.java
@@ -2311,7 +2311,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* transaction have called {@link #startPostponedEnterTransition()}.
* <p>
* This method should be called before being added to the FragmentTransaction or
- * in {@link #onCreate(Bundle), {@link #onAttach(Context)}, or
+ * in {@link #onCreate(Bundle)}, {@link #onAttach(Context)}, or
* {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}}.
* {@link #startPostponedEnterTransition()} must be called to allow the Fragment to
* start the transitions.
diff --git a/android/app/GrantedUriPermission.java b/android/app/GrantedUriPermission.java
new file mode 100644
index 00000000..9e84fe13
--- /dev/null
+++ b/android/app/GrantedUriPermission.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.UriPermission;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents an {@link UriPermission} granted to a package.
+ *
+ * {@hide}
+ */
+public class GrantedUriPermission implements Parcelable {
+
+ public final Uri uri;
+ public final String packageName;
+
+ public GrantedUriPermission(@NonNull Uri uri, @Nullable String packageName) {
+ this.uri = uri;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return packageName + ":" + uri;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(uri, flags);
+ out.writeString(packageName);
+ }
+
+ public static final Parcelable.Creator<GrantedUriPermission> CREATOR =
+ new Parcelable.Creator<GrantedUriPermission>() {
+ @Override
+ public GrantedUriPermission createFromParcel(Parcel in) {
+ return new GrantedUriPermission(in);
+ }
+
+ @Override
+ public GrantedUriPermission[] newArray(int size) {
+ return new GrantedUriPermission[size];
+ }
+ };
+
+ private GrantedUriPermission(Parcel in) {
+ uri = in.readParcelable(null);
+ packageName = in.readString();
+ }
+}
diff --git a/android/app/InstantAppResolverService.java b/android/app/InstantAppResolverService.java
index c5dc86c7..58d0aaf5 100644
--- a/android/app/InstantAppResolverService.java
+++ b/android/app/InstantAppResolverService.java
@@ -17,7 +17,6 @@
package android.app;
import android.annotation.SystemApi;
-import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.InstantAppResolveInfo;
@@ -35,6 +34,7 @@ import android.util.Slog;
import com.android.internal.os.SomeArgs;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -43,7 +43,7 @@ import java.util.List;
*/
@SystemApi
public abstract class InstantAppResolverService extends Service {
- private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+ private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE;
private static final String TAG = "PackageManager";
/** @hide */
@@ -53,23 +53,89 @@ public abstract class InstantAppResolverService extends Service {
Handler mHandler;
/**
- * Called to retrieve resolve info for instant applications.
+ * Called to retrieve resolve info for instant applications immediately.
*
* @param digestPrefix The hash prefix of the instant app's domain.
+ * @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String,
+ * InstantAppResolutionCallback)}
*/
+ @Deprecated
public void onGetInstantAppResolveInfo(
int digestPrefix[], String token, InstantAppResolutionCallback callback) {
- throw new IllegalStateException("Must define");
+ throw new IllegalStateException("Must define onGetInstantAppResolveInfo");
}
/**
- * Called to retrieve intent filters for instant applications.
+ * Called to retrieve intent filters for instant applications from potentially expensive
+ * sources.
*
* @param digestPrefix The hash prefix of the instant app's domain.
+ * @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String,
+ * InstantAppResolutionCallback)}
*/
+ @Deprecated
public void onGetInstantAppIntentFilter(
int digestPrefix[], String token, InstantAppResolutionCallback callback) {
- throw new IllegalStateException("Must define");
+ throw new IllegalStateException("Must define onGetInstantAppIntentFilter");
+ }
+
+ /**
+ * Called to retrieve resolve info for instant applications immediately. The response will be
+ * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
+ * in response to this method may be partial to request a second phase of resolution which will
+ * result in a subsequent call to
+ * {@link #onGetInstantAppIntentFilter(Intent, int[], String, InstantAppResolutionCallback)}
+ *
+ *
+ * @param sanitizedIntent The sanitized {@link Intent} used for resolution. A sanitized Intent
+ * is an intent with potential PII removed from the original intent.
+ * Fields removed include extras and the host + path of the data, if
+ * defined.
+ * @param hostDigestPrefix The hash prefix of the instant app's domain.
+ * @param token A unique identifier that will be provided in calls to
+ * {@link #onGetInstantAppIntentFilter(Intent, int[], String,
+ * InstantAppResolutionCallback)}
+ * and provided to the installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN} to
+ * tie a single launch together.
+ * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+ *
+ * @see InstantAppResolveInfo
+ */
+ public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix,
+ String token, InstantAppResolutionCallback callback) {
+ // if not overridden, forward to old methods and filter out non-web intents
+ if (sanitizedIntent.isWebIntent()) {
+ onGetInstantAppResolveInfo(hostDigestPrefix, token, callback);
+ } else {
+ callback.onInstantAppResolveInfo(Collections.emptyList());
+ }
+ }
+
+ /**
+ * Called to retrieve intent filters for potentially matching instant applications. Unlike
+ * {@link #onGetInstantAppResolveInfo(Intent, int[], String, InstantAppResolutionCallback)},
+ * the response may take as long as necessary to respond. All {@link InstantAppResolveInfo}s
+ * provided in response to this method must be completely populated.
+ *
+ * @param sanitizedIntent The sanitized {@link Intent} used for resolution.
+ * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is
+ * defined.
+ * @param token A unique identifier that was provided in
+ * {@link #onGetInstantAppResolveInfo(Intent, int[], String,
+ * InstantAppResolutionCallback)}
+ * and provided to the currently visible installer via
+ * {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
+ * @param callback The {@link InstantAppResolutionCallback} to provide results to
+ */
+ public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix,
+ String token, InstantAppResolutionCallback callback) {
+ Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden");
+ // if not overridden, forward to old methods and filter out non-web intents
+ if (sanitizedIntent.isWebIntent()) {
+ onGetInstantAppIntentFilter(hostDigestPrefix, token, callback);
+ } else {
+ callback.onInstantAppResolveInfo(Collections.emptyList());
+ }
}
/**
@@ -89,33 +155,33 @@ public abstract class InstantAppResolverService extends Service {
public final IBinder onBind(Intent intent) {
return new IInstantAppResolver.Stub() {
@Override
- public void getInstantAppResolveInfoList(
- int digestPrefix[], String token, int sequence, IRemoteCallback callback) {
- if (DEBUG_EPHEMERAL) {
+ public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
+ String token, int sequence, IRemoteCallback callback) {
+ if (DEBUG_INSTANT) {
Slog.v(TAG, "[" + token + "] Phase1 called; posting");
}
final SomeArgs args = SomeArgs.obtain();
args.arg1 = callback;
args.arg2 = digestPrefix;
args.arg3 = token;
- mHandler.obtainMessage(
- ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args)
- .sendToTarget();
+ args.arg4 = sanitizedIntent;
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
+ sequence, 0, args).sendToTarget();
}
@Override
- public void getInstantAppIntentFilterList(
- int digestPrefix[], String token, String hostName, IRemoteCallback callback) {
- if (DEBUG_EPHEMERAL) {
+ public void getInstantAppIntentFilterList(Intent sanitizedIntent,
+ int[] digestPrefix, String token, IRemoteCallback callback) {
+ if (DEBUG_INSTANT) {
Slog.v(TAG, "[" + token + "] Phase2 called; posting");
}
final SomeArgs args = SomeArgs.obtain();
args.arg1 = callback;
args.arg2 = digestPrefix;
args.arg3 = token;
- args.arg4 = hostName;
- mHandler.obtainMessage(
- ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget();
+ args.arg4 = sanitizedIntent;
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
+ callback).sendToTarget();
}
};
}
@@ -142,29 +208,9 @@ public abstract class InstantAppResolverService extends Service {
}
}
- @Deprecated
- void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
- InstantAppResolutionCallback callback) {
- if (DEBUG_EPHEMERAL) {
- Slog.d(TAG, "[" + token + "] Phase1 request;"
- + " prefix: " + Arrays.toString(digestPrefix));
- }
- onGetInstantAppResolveInfo(digestPrefix, token, callback);
- }
- @Deprecated
- void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName,
- InstantAppResolutionCallback callback) {
- if (DEBUG_EPHEMERAL) {
- Slog.d(TAG, "[" + token + "] Phase2 request;"
- + " prefix: " + Arrays.toString(digestPrefix));
- }
- onGetInstantAppIntentFilter(digestPrefix, token, callback);
- }
-
private final class ServiceHandler extends Handler {
public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1;
public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2;
-
public ServiceHandler(Looper looper) {
super(looper, null /*callback*/, true /*async*/);
}
@@ -179,9 +225,13 @@ public abstract class InstantAppResolverService extends Service {
final IRemoteCallback callback = (IRemoteCallback) args.arg1;
final int[] digestPrefix = (int[]) args.arg2;
final String token = (String) args.arg3;
+ final Intent intent = (Intent) args.arg4;
final int sequence = message.arg1;
- _onGetInstantAppResolveInfo(
- digestPrefix, token,
+ if (DEBUG_INSTANT) {
+ Slog.d(TAG, "[" + token + "] Phase1 request;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
+ onGetInstantAppResolveInfo(intent, digestPrefix, token,
new InstantAppResolutionCallback(sequence, callback));
} break;
@@ -190,9 +240,12 @@ public abstract class InstantAppResolverService extends Service {
final IRemoteCallback callback = (IRemoteCallback) args.arg1;
final int[] digestPrefix = (int[]) args.arg2;
final String token = (String) args.arg3;
- final String hostName = (String) args.arg4;
- _onGetInstantAppIntentFilter(
- digestPrefix, token, hostName,
+ final Intent intent = (Intent) args.arg4;
+ if (DEBUG_INSTANT) {
+ Slog.d(TAG, "[" + token + "] Phase2 request;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
+ onGetInstantAppIntentFilter(intent, digestPrefix, token,
new InstantAppResolutionCallback(-1 /*sequence*/, callback));
} break;
diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java
index 3c38a4ec..7f87814a 100644
--- a/android/app/Instrumentation.java
+++ b/android/app/Instrumentation.java
@@ -1216,10 +1216,10 @@ public class Instrumentation {
+ " disabling AppComponentFactory", new Throwable());
return AppComponentFactory.DEFAULT;
}
- LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true);
+ LoadedApk apk = mThread.peekPackageInfo(pkg, true);
// This is in the case of starting up "android".
- if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk;
- return loadedApk.getAppFactory();
+ if (apk == null) apk = mThread.getSystemContext().mPackageInfo;
+ return apk.getAppFactory();
}
private void prePerformCreate(Activity activity) {
@@ -1679,7 +1679,7 @@ public class Instrumentation {
public void execStartActivities(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options) {
execStartActivitiesAsUser(who, contextThread, token, target, intents, options,
- UserHandle.myUserId());
+ who.getUserId());
}
/**
@@ -1688,9 +1688,13 @@ public class Instrumentation {
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
*
+ * @return The corresponding flag {@link ActivityManager#START_CANCELED},
+ * {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was
+ * successful.
+ *
* {@hide}
*/
- public void execStartActivitiesAsUser(Context who, IBinder contextThread,
+ public int execStartActivitiesAsUser(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options,
int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
@@ -1705,11 +1709,11 @@ public class Instrumentation {
}
if (result != null) {
am.mHits++;
- return;
+ return ActivityManager.START_CANCELED;
} else if (am.match(who, null, intents[0])) {
am.mHits++;
if (am.isBlocking()) {
- return;
+ return ActivityManager.START_CANCELED;
}
break;
}
@@ -1727,6 +1731,7 @@ public class Instrumentation {
.startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
token, options, userId);
checkStartActivityResult(result, intents[0]);
+ return result;
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
@@ -1874,8 +1879,8 @@ public class Instrumentation {
*/
public ActivityResult execStartActivityAsCaller(
Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options, IBinder permissionToken,
- boolean ignoreTargetSecurity, int userId) {
+ Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity,
+ int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1906,8 +1911,7 @@ public class Instrumentation {
.startActivityAsCaller(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
- requestCode, 0, null, options, permissionToken,
- ignoreTargetSecurity, userId);
+ requestCode, 0, null, options, ignoreTargetSecurity, userId);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java
index 553099f2..4a3fcaf3 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -407,7 +407,7 @@ public class KeyguardManager {
* password.
*/
public boolean isDeviceLocked() {
- return isDeviceLocked(UserHandle.myUserId());
+ return isDeviceLocked(mContext.getUserId());
}
/**
@@ -432,7 +432,7 @@ public class KeyguardManager {
* @return true if a PIN, pattern or password was set.
*/
public boolean isDeviceSecure() {
- return isDeviceSecure(UserHandle.myUserId());
+ return isDeviceSecure(mContext.getUserId());
}
/**
diff --git a/android/app/LoadedApk.java b/android/app/LoadedApk.java
index 26f49808..fc7d9a55 100644
--- a/android/app/LoadedApk.java
+++ b/android/app/LoadedApk.java
@@ -28,14 +28,13 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.dex.ArtManager;
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;
-import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
@@ -49,13 +48,15 @@ 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;
@@ -218,7 +219,7 @@ public final class LoadedApk {
}
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
- if (appInfo.appComponentFactory != null) {
+ if (appInfo.appComponentFactory != null && cl != null) {
try {
return (AppComponentFactory) cl.loadClass(appInfo.appComponentFactory)
.newInstance();
@@ -612,6 +613,7 @@ public final class LoadedApk {
} else {
mClassLoader = ClassLoader.getSystemClassLoader();
}
+ mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
return;
}
@@ -686,6 +688,7 @@ public final class LoadedApk {
librarySearchPath, libraryPermittedPath, mBaseClassLoader,
null /* classLoaderName */);
StrictMode.setThreadPolicy(oldPolicy);
+ mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
}
return;
@@ -713,6 +716,7 @@ public final class LoadedApk {
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader,
mApplicationInfo.classLoaderName);
+ mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
StrictMode.setThreadPolicy(oldPolicy);
// Setup the class loader paths for profiling.
@@ -749,13 +753,6 @@ public final class LoadedApk {
}
}
- // Keep in sync with installd (frameworks/native/cmds/installd/commands.cpp).
- private static File getPrimaryProfileFile(String packageName) {
- File profileDir = Environment.getDataProfilesDePackageDirectory(
- UserHandle.myUserId(), packageName);
- return new File(profileDir, "primary.prof");
- }
-
private void setupJitProfileSupport() {
if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
return;
@@ -783,10 +780,12 @@ public final class LoadedApk {
return;
}
- final File profileFile = getPrimaryProfileFile(mPackageName);
-
- VMRuntime.registerAppInfo(profileFile.getPath(),
- codePaths.toArray(new String[codePaths.size()]));
+ for (int i = codePaths.size() - 1; i >= 0; i--) {
+ String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1];
+ String profileFile = ArtManager.getCurrentProfilePath(
+ mPackageName, UserHandle.myUserId(), splitName);
+ VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)});
+ }
// Register the app data directory with the reporter. It will
// help deciding whether or not a dex file is the primary apk or a
@@ -967,78 +966,14 @@ 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) {
diff --git a/android/app/LocalActivityManager.java b/android/app/LocalActivityManager.java
index 998ac5f2..e297719f 100644
--- a/android/app/LocalActivityManager.java
+++ b/android/app/LocalActivityManager.java
@@ -16,6 +16,8 @@
package android.app;
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.servertransaction.PendingTransactionActions;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Binder;
@@ -141,6 +143,21 @@ public class LocalActivityManager {
}
r.window = r.activity.getWindow();
r.instanceState = null;
+
+ final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+ final PendingTransactionActions pendingActions;
+
+ if (!r.activity.mFinished) {
+ // This matches pending actions set in ActivityThread#handleLaunchActivity
+ pendingActions = new PendingTransactionActions();
+ pendingActions.setOldState(clientRecord.state);
+ pendingActions.setRestoreInstanceState(true);
+ pendingActions.setCallOnPostCreate(true);
+ } else {
+ pendingActions = null;
+ }
+
+ mActivityThread.handleStartActivity(clientRecord, pendingActions);
r.curState = STARTED;
if (desiredState == RESUMED) {
@@ -207,8 +224,8 @@ 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", null /* pendingActions */);
+ final Bundle instanceState = mActivityThread.performPauseActivity(r, finishing,
+ "performPause", null /* pendingActions */);
if (needState) {
r.instanceState = instanceState;
}
@@ -363,7 +380,7 @@ public class LocalActivityManager {
}
if (localLOGV) Log.v(TAG, r.id + ": destroying");
mActivityThread.performDestroyActivity(r, finish, 0 /* configChanges */,
- false /* getNonConfigInstance */);
+ false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
r.activity = null;
r.window = null;
if (finish) {
@@ -628,7 +645,7 @@ public class LocalActivityManager {
LocalActivityRecord r = mActivityArray.get(i);
if (localLOGV) Log.v(TAG, r.id + ": destroying");
mActivityThread.performDestroyActivity(r, finishing, 0 /* configChanges */,
- false /* getNonConfigInstance */);
+ false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
}
mActivities.clear();
mActivityArray.clear();
diff --git a/android/app/Notification.java b/android/app/Notification.java
index d6fddfca..4326ee3e 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -89,6 +89,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -200,6 +201,16 @@ public class Notification implements Parcelable
*/
private static final int MAX_REPLY_HISTORY = 5;
+
+ /**
+ * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
+ * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
+ *
+ * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
+ * sends messages.</p>
+ */
+ public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
+
/**
* A timestamp related to this notification, in milliseconds since the epoch.
*
@@ -350,6 +361,23 @@ public class Notification implements Parcelable
@Deprecated
public RemoteViews headsUpContentView;
+ private boolean mUsesStandardHeader;
+
+ private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>();
+ static {
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_base);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_ambient_header);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_header);
+ STANDARD_LAYOUTS.add(R.layout.notification_template_material_ambient);
+ }
+
/**
* A large bitmap to be shown in the notification content area.
*
@@ -483,7 +511,7 @@ public class Notification implements Parcelable
* </ul>
* <p>
* Since hardware varies, you are not guaranteed that any of the values
- * you pass are honored exactly. Use the system defaults (TODO) if possible
+ * you pass are honored exactly. Use the system defaults if possible
* because they will be set to values that work on any given hardware.
* <p>
* The alpha channel must be set for forward compatibility.
@@ -736,6 +764,11 @@ public class Notification implements Parcelable
public static final String CATEGORY_CALL = "call";
/**
+ * Notification category: map turn-by-turn navigation.
+ */
+ public static final String CATEGORY_NAVIGATION = "navigation";
+
+ /**
* Notification category: incoming direct message (SMS, instant message, etc.).
*/
public static final String CATEGORY_MESSAGE = "msg";
@@ -808,6 +841,27 @@ public class Notification implements Parcelable
public static final String CATEGORY_REMINDER = "reminder";
/**
+ * Notification category: extreme car emergencies.
+ * @hide
+ */
+ @SystemApi
+ public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
+
+ /**
+ * Notification category: car warnings.
+ * @hide
+ */
+ @SystemApi
+ public static final String CATEGORY_CAR_WARNING = "car_warning";
+
+ /**
+ * Notification category: general car system information.
+ * @hide
+ */
+ @SystemApi
+ public static final String CATEGORY_CAR_INFORMATION = "car_information";
+
+ /**
* One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
* that best describes this Notification. May be used by the system for ranking and filtering.
*/
@@ -1045,11 +1099,10 @@ public class Notification implements Parcelable
/**
* {@link #extras} key: A
* {@link android.content.ContentUris content URI} pointing to an image that can be displayed
- * in the background when the notification is selected. The URI must point to an image stream
- * suitable for passing into
+ * in the background when the notification is selected. Used on television platforms.
+ * The URI must point to an image stream suitable for passing into
* {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream)
- * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider
- * URI used for this purpose must require no permissions to read the image data.
+ * BitmapFactory.decodeStream}; all other content types will be ignored.
*/
public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
@@ -1166,11 +1219,11 @@ public class Notification implements Parcelable
public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName";
/**
- * This is set on the notification shown by the activity manager about all apps
- * running in the background. It indicates that the notification should be shown
- * only if any of the given apps do not already have a {@link #FLAG_FOREGROUND_SERVICE}
- * notification currently visible to the user. This is a string array of all
- * package names of the apps.
+ * This is set on the notifications shown by system_server about apps running foreground
+ * services. It indicates that the notification should be shown
+ * only if any of the given apps do not already have a properly tagged
+ * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user.
+ * This is a string array of all package names of the apps.
* @hide
*/
public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps";
@@ -1323,6 +1376,10 @@ public class Notification implements Parcelable
*/
public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
+ /**
+ * {@code SemanticAction}: Call a contact, group, etc.
+ */
+ public static final int SEMANTIC_ACTION_CALL = 10;
private final Bundle mExtras;
private Icon mIcon;
@@ -1812,6 +1869,7 @@ public class Notification implements Parcelable
* @param label the label to display while the action is being prepared to execute
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setInProgressLabel(CharSequence label) {
mInProgressLabel = label;
return this;
@@ -1823,6 +1881,7 @@ public class Notification implements Parcelable
*
* @return the label to display while the action is being prepared to execute
*/
+ @Deprecated
public CharSequence getInProgressLabel() {
return mInProgressLabel;
}
@@ -1834,6 +1893,7 @@ public class Notification implements Parcelable
* @param label the label to confirm the action should be executed
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setConfirmLabel(CharSequence label) {
mConfirmLabel = label;
return this;
@@ -1845,6 +1905,7 @@ public class Notification implements Parcelable
*
* @return the label to confirm the action should be executed
*/
+ @Deprecated
public CharSequence getConfirmLabel() {
return mConfirmLabel;
}
@@ -1856,6 +1917,7 @@ public class Notification implements Parcelable
* @param label the label to display to cancel the action
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setCancelLabel(CharSequence label) {
mCancelLabel = label;
return this;
@@ -1867,6 +1929,7 @@ public class Notification implements Parcelable
*
* @return the label to display to cancel the action
*/
+ @Deprecated
public CharSequence getCancelLabel() {
return mCancelLabel;
}
@@ -1937,7 +2000,8 @@ public class Notification implements Parcelable
SEMANTIC_ACTION_MUTE,
SEMANTIC_ACTION_UNMUTE,
SEMANTIC_ACTION_THUMBS_UP,
- SEMANTIC_ACTION_THUMBS_DOWN
+ SEMANTIC_ACTION_THUMBS_DOWN,
+ SEMANTIC_ACTION_CALL
})
@Retention(RetentionPolicy.SOURCE)
public @interface SemanticAction {}
@@ -2514,6 +2578,8 @@ public class Notification implements Parcelable
}
parcel.writeInt(mGroupAlertBehavior);
+
+ // mUsesStandardHeader is not written because it should be recomputed in listeners
}
/**
@@ -2534,6 +2600,80 @@ public class Notification implements Parcelable
};
/**
+ * @hide
+ */
+ public static boolean areActionsVisiblyDifferent(Notification first, Notification second) {
+ Notification.Action[] firstAs = first.actions;
+ Notification.Action[] secondAs = second.actions;
+ if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) {
+ return true;
+ }
+ if (firstAs != null && secondAs != null) {
+ if (firstAs.length != secondAs.length) {
+ return true;
+ }
+ for (int i = 0; i < firstAs.length; i++) {
+ if (!Objects.equals(firstAs[i].title, secondAs[i].title)) {
+ return true;
+ }
+ RemoteInput[] firstRs = firstAs[i].getRemoteInputs();
+ RemoteInput[] secondRs = secondAs[i].getRemoteInputs();
+ if (firstRs == null) {
+ firstRs = new RemoteInput[0];
+ }
+ if (secondRs == null) {
+ secondRs = new RemoteInput[0];
+ }
+ if (firstRs.length != secondRs.length) {
+ return true;
+ }
+ for (int j = 0; j < firstRs.length; j++) {
+ if (!Objects.equals(firstRs[j].getLabel(), secondRs[j].getLabel())) {
+ return true;
+ }
+ CharSequence[] firstCs = firstRs[j].getChoices();
+ CharSequence[] secondCs = secondRs[j].getChoices();
+ if (firstCs == null) {
+ firstCs = new CharSequence[0];
+ }
+ if (secondCs == null) {
+ secondCs = new CharSequence[0];
+ }
+ if (firstCs.length != secondCs.length) {
+ return true;
+ }
+ for (int k = 0; k < firstCs.length; k++) {
+ if (!Objects.equals(firstCs[k], secondCs[k])) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
+ if (first.getStyle() == null) {
+ return second.getStyle() != null;
+ }
+ if (second.getStyle() == null) {
+ return true;
+ }
+ return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle());
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean areRemoteViewsChanged(Builder first, Builder second) {
+ return !first.usesStandardHeader() || !second.usesStandardHeader();
+ }
+
+ /**
* 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
@@ -2978,7 +3118,6 @@ public class Notification implements Parcelable
private int mActionBarColor = COLOR_INVALID;
private int mBackgroundColor = COLOR_INVALID;
private int mForegroundColor = COLOR_INVALID;
- private int mBackgroundColorHint = COLOR_INVALID;
/**
* A temporary location where actions are stored. If != null the view originally has action
* but doesn't have any for this inflation.
@@ -3974,6 +4113,13 @@ public class Notification implements Parcelable
}
/**
+ * Returns the style set by {@link #setStyle(Style)}.
+ */
+ public Style getStyle() {
+ return mStyle;
+ }
+
+ /**
* Specify the value of {@link #visibility}.
*
* @return The same Builder.
@@ -4072,6 +4218,25 @@ public class Notification implements Parcelable
}
}
+ /**
+ * @hide
+ */
+ public boolean usesStandardHeader() {
+ if (mN.mUsesStandardHeader) {
+ return true;
+ }
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
+ if (mN.contentView == null && mN.bigContentView == null) {
+ return true;
+ }
+ }
+ boolean contentViewUsesHeader = mN.contentView == null
+ || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId());
+ boolean bigContentViewUsesHeader = mN.bigContentView == null
+ || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId());
+ return contentViewUsesHeader && bigContentViewUsesHeader;
+ }
+
private void resetStandardTemplate(RemoteViews contentView) {
resetNotificationHeader(contentView);
resetContentMargins(contentView);
@@ -4103,6 +4268,7 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.time, View.GONE);
contentView.setImageViewIcon(R.id.profile_badge, null);
contentView.setViewVisibility(R.id.profile_badge, View.GONE);
+ mN.mUsesStandardHeader = false;
}
private void resetContentMargins(RemoteViews contentView) {
@@ -4220,8 +4386,7 @@ public class Notification implements Parcelable
backgroundColor);
mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(mContext,
backgroundColor);
- if (backgroundColor != COLOR_DEFAULT
- && (mBackgroundColorHint != COLOR_INVALID || isColorized())) {
+ if (backgroundColor != COLOR_DEFAULT && isColorized()) {
mPrimaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
mPrimaryTextColor, backgroundColor, 4.5);
mSecondaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
@@ -4424,24 +4589,17 @@ public class Notification implements Parcelable
bindProfileBadge(contentView);
}
bindExpandButton(contentView);
+ mN.mUsesStandardHeader = true;
}
private void bindExpandButton(RemoteViews contentView) {
- int color = getPrimaryHighlightColor();
+ int color = isColorized() ? getPrimaryTextColor() : getSecondaryTextColor();
contentView.setDrawableTint(R.id.expand_button, false, color,
PorterDuff.Mode.SRC_ATOP);
contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
color);
}
- /**
- * @return the color that is used as the first primary highlight color. This is applied
- * in several places like the action buttons or the app name in the header.
- */
- private int getPrimaryHighlightColor() {
- return isColorized() ? getPrimaryTextColor() : resolveContrastColor();
- }
-
private void bindHeaderChronometerAndTime(RemoteViews contentView) {
if (showsTimeOrChronometer()) {
contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
@@ -4538,7 +4696,7 @@ public class Notification implements Parcelable
setTextViewColorPrimary(contentView, R.id.app_name_text);
} else {
contentView.setTextColor(R.id.app_name_text,
- ambient ? resolveAmbientColor() : resolveContrastColor());
+ ambient ? resolveAmbientColor() : getSecondaryTextColor());
}
}
@@ -4573,7 +4731,8 @@ public class Notification implements Parcelable
big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
big.setTextViewText(R.id.notification_material_reply_text_3, null);
- big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
+ big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
+ R.dimen.notification_content_margin);
}
private RemoteViews applyStandardTemplateWithActions(int layoutId) {
@@ -4594,23 +4753,19 @@ public class Notification implements Parcelable
if (N > 0) {
big.setViewVisibility(R.id.actions_container, View.VISIBLE);
big.setViewVisibility(R.id.actions, View.VISIBLE);
- if (p.ambient) {
- big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT);
- } else if (isColorized()) {
- big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor());
- } else {
- big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor(
- R.color.notification_action_list));
- }
- big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
- R.dimen.notification_action_list_height);
+ big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
for (int i=0; i<N; i++) {
Action action = mActions.get(i);
- validRemoteInput |= hasValidRemoteInput(action);
+ boolean actionHasValidInput = hasValidRemoteInput(action);
+ validRemoteInput |= actionHasValidInput;
final RemoteViews button = generateActionButton(action, emphazisedMode,
i % 2 != 0, p.ambient);
+ if (actionHasValidInput) {
+ // Clear the drawable
+ button.setInt(R.id.action0, "setBackgroundResource", 0);
+ }
big.addView(R.id.actions, button);
}
} else {
@@ -5069,7 +5224,14 @@ public class Notification implements Parcelable
private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
boolean ambient) {
boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
- int color = ambient ? resolveAmbientColor() : getPrimaryHighlightColor();
+ int color;
+ if (ambient) {
+ color = resolveAmbientColor();
+ } else if (isColorized()) {
+ color = getPrimaryTextColor();
+ } else {
+ color = resolveContrastColor();
+ }
if (colorable) {
contentView.setDrawableTint(R.id.icon, false, color,
PorterDuff.Mode.SRC_ATOP);
@@ -5105,14 +5267,11 @@ public class Notification implements Parcelable
}
int color;
- int background = mBackgroundColorHint;
- if (mBackgroundColorHint == COLOR_INVALID) {
- background = mContext.getColor(
- com.android.internal.R.color.notification_material_background_color);
- }
+ int background = mContext.getColor(
+ com.android.internal.R.color.notification_material_background_color);
if (mN.color == COLOR_DEFAULT) {
ensureColors();
- color = mSecondaryTextColor;
+ color = NotificationColorUtil.resolveDefaultColor(mContext, background);
} else {
color = NotificationColorUtil.resolveContrastColor(mContext, mN.color,
background, mInNightMode);
@@ -5207,6 +5366,7 @@ public class Notification implements Parcelable
if (mStyle != null) {
mStyle.reduceImageSizes(mContext);
mStyle.purgeResources();
+ mStyle.validate(mContext);
mStyle.buildStyled(mN);
}
mN.reduceImageSizes(mContext);
@@ -5351,8 +5511,7 @@ public class Notification implements Parcelable
if (isColorized()) {
return mBackgroundColor != COLOR_INVALID ? mBackgroundColor : mN.color;
} else {
- return mBackgroundColorHint != COLOR_INVALID ? mBackgroundColorHint
- : COLOR_DEFAULT;
+ return COLOR_DEFAULT;
}
}
@@ -5389,18 +5548,6 @@ public class Notification implements Parcelable
}
/**
- * Sets the background color for this notification to be a different one then the default.
- * This is mainly used to calculate contrast and won't necessarily be applied to the
- * background.
- *
- * @hide
- */
- public void setBackgroundColorHint(int backgroundColor) {
- mBackgroundColorHint = backgroundColor;
- }
-
-
- /**
* Forces all styled remoteViews to be built from scratch and not use any cached
* RemoteViews.
* This is needed for legacy apps that are baking in their remoteviews into the
@@ -5411,6 +5558,24 @@ public class Notification implements Parcelable
public void setRebuildStyledRemoteViews(boolean rebuild) {
mRebuildStyledRemoteViews = rebuild;
}
+
+ /**
+ * Get the text that should be displayed in the statusBar when heads upped. This is
+ * usually just the app name, but may be different depending on the style.
+ *
+ * @param publicMode If true, return a text that is safe to display in public.
+ *
+ * @hide
+ */
+ public CharSequence getHeadsUpStatusBarText(boolean publicMode) {
+ if (mStyle != null && !publicMode) {
+ CharSequence text = mStyle.getHeadsUpStatusBarText();
+ if (!TextUtils.isEmpty(text)) {
+ return text;
+ }
+ }
+ return loadHeaderAppName();
+ }
}
/**
@@ -5776,6 +5941,28 @@ public class Notification implements Parcelable
*/
public void reduceImageSizes(Context context) {
}
+
+ /**
+ * Validate that this style was properly composed. This is called at build time.
+ * @hide
+ */
+ public void validate(Context context) {
+ }
+
+ /**
+ * @hide
+ */
+ public abstract boolean areNotificationsVisiblyDifferent(Style other);
+
+ /**
+ * @return the the text that should be displayed in the statusBar when heads-upped.
+ * If {@code null} is returned, the default implementation will be used.
+ *
+ * @hide
+ */
+ public CharSequence getHeadsUpStatusBarText() {
+ return null;
+ }
}
/**
@@ -5829,6 +6016,13 @@ public class Notification implements Parcelable
}
/**
+ * @hide
+ */
+ public Bitmap getBigPicture() {
+ return mPicture;
+ }
+
+ /**
* Provide the bitmap to be used as the payload for the BigPicture notification.
*/
public BigPictureStyle bigPicture(Bitmap b) {
@@ -5968,6 +6162,18 @@ public class Notification implements Parcelable
public boolean hasSummaryInHeader() {
return false;
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ BigPictureStyle otherS = (BigPictureStyle) other;
+ return !Objects.equals(getBigPicture(), otherS.getBigPicture());
+ }
}
/**
@@ -6031,6 +6237,13 @@ public class Notification implements Parcelable
/**
* @hide
*/
+ public CharSequence getBigText() {
+ return mBigText;
+ }
+
+ /**
+ * @hide
+ */
public void addExtras(Bundle extras) {
super.addExtras(extras);
@@ -6100,6 +6313,18 @@ public class Notification implements Parcelable
return contentView;
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ BigTextStyle newS = (BigTextStyle) other;
+ return !Objects.equals(getBigText(), newS.getBigText());
+ }
+
static void applyBigTextContentView(Builder builder,
RemoteViews contentView, CharSequence bigTextText) {
contentView.setTextViewText(R.id.big_text, builder.processTextSpans(bigTextText));
@@ -6166,13 +6391,41 @@ public class Notification implements Parcelable
* @param user Required - The person displayed for any messages that are sent by the
* user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
* who don't have a Person associated with it will be displayed as if they were sent
- * by this user. The user also needs to have a valid name associated with it.
+ * by this user. The user also needs to have a valid name associated with it, which will
+ * be enforced starting in Android P.
*/
public MessagingStyle(@NonNull Person user) {
mUser = user;
- if (user == null || user.getName() == null) {
- throw new RuntimeException("user must be valid and have a name");
+ }
+
+ /**
+ * Validate that this style was properly composed. This is called at build time.
+ * @hide
+ */
+ @Override
+ public void validate(Context context) {
+ super.validate(context);
+ if (context.getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) {
+ throw new RuntimeException("User must be valid and have a name.");
+ }
+ }
+
+ /**
+ * @return the the text that should be displayed in the statusBar when heads upped.
+ * If {@code null} is returned, the default implementation will be used.
+ *
+ * @hide
+ */
+ @Override
+ public CharSequence getHeadsUpStatusBarText() {
+ CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
+ ? super.mBigContentTitle
+ : mConversationTitle;
+ if (!TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) {
+ return conversationTitle;
}
+ return null;
}
/**
@@ -6425,12 +6678,64 @@ public class Notification implements Parcelable
public RemoteViews makeContentView(boolean increasedHeight) {
mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
+ RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
mBuilder.mActions = mBuilder.mOriginalActions;
mBuilder.mOriginalActions = null;
return remoteViews;
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ MessagingStyle newS = (MessagingStyle) other;
+ List<MessagingStyle.Message> oldMs = getMessages();
+ List<MessagingStyle.Message> newMs = newS.getMessages();
+
+ if (oldMs == null) {
+ oldMs = new ArrayList<>();
+ }
+ if (newMs == null) {
+ newMs = new ArrayList<>();
+ }
+
+ int n = oldMs.size();
+ if (n != newMs.size()) {
+ return true;
+ }
+ for (int i = 0; i < n; i++) {
+ MessagingStyle.Message oldM = oldMs.get(i);
+ MessagingStyle.Message newM = newMs.get(i);
+ if (!Objects.equals(oldM.getText(), newM.getText())) {
+ return true;
+ }
+ if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) {
+ return true;
+ }
+ CharSequence oldSender = oldM.getSenderPerson() == null ? oldM.getSender()
+ : oldM.getSenderPerson().getName();
+ CharSequence newSender = newM.getSenderPerson() == null ? newM.getSender()
+ : newM.getSenderPerson().getName();
+ if (!Objects.equals(oldSender, newSender)) {
+ return true;
+ }
+
+ String oldKey = oldM.getSenderPerson() == null
+ ? null : oldM.getSenderPerson().getKey();
+ String newKey = newM.getSenderPerson() == null
+ ? null : newM.getSenderPerson().getKey();
+ if (!Objects.equals(oldKey, newKey)) {
+ return true;
+ }
+ // Other fields (like timestamp) intentionally excluded
+ }
+ return false;
+ }
+
private Message findLatestIncomingMessage() {
return findLatestIncomingMessage(mMessages);
}
@@ -6460,11 +6765,11 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
- return makeBigContentView(false /* showRightIcon */);
+ return makeMessagingView(false /* isCollapsed */);
}
@NonNull
- private RemoteViews makeBigContentView(boolean showRightIcon) {
+ private RemoteViews makeMessagingView(boolean isCollapsed) {
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
@@ -6475,21 +6780,24 @@ public class Notification implements Parcelable
nameReplacement = conversationTitle;
conversationTitle = null;
}
+ boolean hideLargeIcon = !isCollapsed || isOneToOne;
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
- .hideLargeIcon(!showRightIcon || isOneToOne)
+ .hideLargeIcon(hideLargeIcon)
.headerTextSecondary(conversationTitle)
- .alwaysShowReply(showRightIcon));
+ .alwaysShowReply(isCollapsed));
addExtras(mBuilder.mN.extras);
// also update the end margin if there is an image
int endMargin = R.dimen.notification_content_margin_end;
- if (mBuilder.mN.hasLargeIcon() && showRightIcon) {
+ if (isCollapsed) {
endMargin = R.dimen.notification_content_plus_picture_margin_end;
}
contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
mBuilder.resolveContrastColor());
+ contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
+ isCollapsed);
contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
mBuilder.mN.mLargeIcon);
contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
@@ -6556,7 +6864,7 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
+ RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
return remoteViews;
}
@@ -6859,6 +7167,13 @@ public class Notification implements Parcelable
/**
* @hide
*/
+ public ArrayList<CharSequence> getLines() {
+ return mTexts;
+ }
+
+ /**
+ * @hide
+ */
public void addExtras(Bundle extras) {
super.addExtras(extras);
@@ -6937,6 +7252,18 @@ public class Notification implements Parcelable
return contentView;
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ InboxStyle newS = (InboxStyle) other;
+ return !Objects.equals(getLines(), newS.getLines());
+ }
+
private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first) {
int endMargin = 0;
if (first) {
@@ -7100,6 +7427,18 @@ public class Notification implements Parcelable
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ // All fields to compare are on the Notification object
+ return false;
+ }
+
private RemoteViews generateMediaActionButton(Action action, int color) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
@@ -7140,8 +7479,7 @@ public class Notification implements Parcelable
}
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
- final RemoteViews button = generateMediaActionButton(action,
- getPrimaryHighlightColor());
+ final RemoteViews button = generateMediaActionButton(action, getActionColor());
view.addView(com.android.internal.R.id.media_actions, button);
}
}
@@ -7155,8 +7493,9 @@ public class Notification implements Parcelable
return view;
}
- private int getPrimaryHighlightColor() {
- return mBuilder.getPrimaryHighlightColor();
+ private int getActionColor() {
+ return mBuilder.isColorized() ? mBuilder.getPrimaryTextColor()
+ : mBuilder.resolveContrastColor();
}
private RemoteViews makeMediaBigContentView() {
@@ -7176,7 +7515,7 @@ public class Notification implements Parcelable
big.removeAllViews(com.android.internal.R.id.media_actions);
for (int i = 0; i < actionCount; i++) {
final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i),
- getPrimaryHighlightColor());
+ getActionColor());
big.addView(com.android.internal.R.id.media_actions, button);
}
}
@@ -7309,6 +7648,18 @@ public class Notification implements Parcelable
}
remoteViews.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ // Comparison done for all custom RemoteViews, independent of style
+ return false;
+ }
}
/**
@@ -7399,6 +7750,18 @@ public class Notification implements Parcelable
return makeBigContentViewWithCustomContent(customRemoteView);
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+ // Comparison done for all custom RemoteViews, independent of style
+ return false;
+ }
+
private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id,
RemoteViews customContent) {
if (customContent != null) {
@@ -7422,6 +7785,8 @@ public class Notification implements Parcelable
@Nullable private Icon mIcon;
@Nullable private String mUri;
@Nullable private String mKey;
+ private boolean mBot;
+ private boolean mImportant;
protected Person(Parcel in) {
mName = in.readCharSequence();
@@ -7430,6 +7795,8 @@ public class Notification implements Parcelable
}
mUri = in.readString();
mKey = in.readString();
+ mImportant = in.readBoolean();
+ mBot = in.readBoolean();
}
/**
@@ -7506,6 +7873,27 @@ public class Notification implements Parcelable
return this;
}
+ /**
+ * Sets whether this is an important person. Use this method to denote users who frequently
+ * interact with the user of this device, when it is not possible to refer to the user
+ * by {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
+ *
+ * @param isImportant {@code true} if this is an important person, {@code false} otherwise.
+ */
+ public Person setImportant(boolean isImportant) {
+ mImportant = isImportant;
+ return this;
+ }
+
+ /**
+ * Sets whether this person is a machine rather than a human.
+ *
+ * @param isBot {@code true} if this person is a machine, {@code false} otherwise.
+ */
+ public Person setBot(boolean isBot) {
+ mBot = isBot;
+ return this;
+ }
/**
* @return the uri provided for this person or {@code null} if no Uri was provided
@@ -7540,6 +7928,20 @@ public class Notification implements Parcelable
}
/**
+ * @return whether this Person is a machine.
+ */
+ public boolean isBot() {
+ return mBot;
+ }
+
+ /**
+ * @return whether this Person is important.
+ */
+ public boolean isImportant() {
+ return mImportant;
+ }
+
+ /**
* @return the URI associated with this person, or "name:mName" otherwise
* @hide
*/
@@ -7569,6 +7971,8 @@ public class Notification implements Parcelable
}
dest.writeString(mUri);
dest.writeString(mKey);
+ dest.writeBoolean(mImportant);
+ dest.writeBoolean(mBot);
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@@ -8049,6 +8453,7 @@ public class Notification implements Parcelable
/**
* Set an icon that goes with the content of this notification.
*/
+ @Deprecated
public WearableExtender setContentIcon(int icon) {
mContentIcon = icon;
return this;
@@ -8057,6 +8462,7 @@ public class Notification implements Parcelable
/**
* Get an icon that goes with the content of this notification.
*/
+ @Deprecated
public int getContentIcon() {
return mContentIcon;
}
@@ -8067,6 +8473,7 @@ public class Notification implements Parcelable
* {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
* @see #setContentIcon
*/
+ @Deprecated
public WearableExtender setContentIconGravity(int contentIconGravity) {
mContentIconGravity = contentIconGravity;
return this;
@@ -8078,6 +8485,7 @@ public class Notification implements Parcelable
* {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
* @see #getContentIcon
*/
+ @Deprecated
public int getContentIconGravity() {
return mContentIconGravity;
}
@@ -8125,6 +8533,7 @@ public class Notification implements Parcelable
* {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
* The default value is {@link android.view.Gravity#BOTTOM}.
*/
+ @Deprecated
public WearableExtender setGravity(int gravity) {
mGravity = gravity;
return this;
@@ -8136,6 +8545,7 @@ public class Notification implements Parcelable
* {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
* The default value is {@link android.view.Gravity#BOTTOM}.
*/
+ @Deprecated
public int getGravity() {
return mGravity;
}
@@ -8149,6 +8559,7 @@ public class Notification implements Parcelable
* documentation for the preset in question. See also
* {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
*/
+ @Deprecated
public WearableExtender setCustomSizePreset(int sizePreset) {
mCustomSizePreset = sizePreset;
return this;
@@ -8162,6 +8573,7 @@ public class Notification implements Parcelable
* using {@link #setDisplayIntent}. Check the documentation for the preset in question.
* See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
*/
+ @Deprecated
public int getCustomSizePreset() {
return mCustomSizePreset;
}
@@ -8173,6 +8585,7 @@ public class Notification implements Parcelable
* {@link android.app.Notification.WearableExtender#setCustomSizePreset} and
* {@link #getCustomContentHeight}.
*/
+ @Deprecated
public WearableExtender setCustomContentHeight(int height) {
mCustomContentHeight = height;
return this;
@@ -8184,6 +8597,7 @@ public class Notification implements Parcelable
* using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
* {@link #setCustomContentHeight}.
*/
+ @Deprecated
public int getCustomContentHeight() {
return mCustomContentHeight;
}
@@ -8234,6 +8648,7 @@ public class Notification implements Parcelable
* @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setHintHideIcon(boolean hintHideIcon) {
setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
return this;
@@ -8244,6 +8659,7 @@ public class Notification implements Parcelable
* @return {@code true} if this icon should not be displayed, false otherwise.
* The default value is {@code false} if this was never set.
*/
+ @Deprecated
public boolean getHintHideIcon() {
return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
}
@@ -8253,6 +8669,7 @@ public class Notification implements Parcelable
* displayed, and other semantic content should be hidden. This hint is only applicable
* to sub-pages added using {@link #addPage}.
*/
+ @Deprecated
public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
return this;
@@ -8263,6 +8680,7 @@ public class Notification implements Parcelable
* displayed, and other semantic content should be hidden. This hint is only applicable
* to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}.
*/
+ @Deprecated
public boolean getHintShowBackgroundOnly() {
return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
}
@@ -8274,6 +8692,7 @@ public class Notification implements Parcelable
* @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setHintAvoidBackgroundClipping(
boolean hintAvoidBackgroundClipping) {
setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
@@ -8287,6 +8706,7 @@ public class Notification implements Parcelable
* @return {@code true} if it's ok if the background is clipped on the screen, false
* otherwise. The default value is {@code false} if this was never set.
*/
+ @Deprecated
public boolean getHintAvoidBackgroundClipping() {
return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
}
@@ -8298,6 +8718,7 @@ public class Notification implements Parcelable
* {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
* @return this object for method chaining
*/
+ @Deprecated
public WearableExtender setHintScreenTimeout(int timeout) {
mHintScreenTimeout = timeout;
return this;
@@ -8309,6 +8730,7 @@ public class Notification implements Parcelable
* @return the duration in milliseconds if > 0, or either one of the sentinel values
* {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
*/
+ @Deprecated
public int getHintScreenTimeout() {
return mHintScreenTimeout;
}
@@ -8845,6 +9267,7 @@ public class Notification implements Parcelable
private static final String EXTRA_CONTENT_INTENT = "content_intent";
private static final String EXTRA_DELETE_INTENT = "delete_intent";
private static final String EXTRA_CHANNEL_ID = "channel_id";
+ private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps";
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_ON_TV = 0x1;
@@ -8853,6 +9276,7 @@ public class Notification implements Parcelable
private String mChannelId;
private PendingIntent mContentIntent;
private PendingIntent mDeleteIntent;
+ private boolean mSuppressShowOverApps;
/**
* Create a {@link TvExtender} with default options.
@@ -8872,6 +9296,7 @@ public class Notification implements Parcelable
if (bundle != null) {
mFlags = bundle.getInt(EXTRA_FLAGS);
mChannelId = bundle.getString(EXTRA_CHANNEL_ID);
+ mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS);
mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT);
mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT);
}
@@ -8888,6 +9313,7 @@ public class Notification implements Parcelable
bundle.putInt(EXTRA_FLAGS, mFlags);
bundle.putString(EXTRA_CHANNEL_ID, mChannelId);
+ bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps);
if (mContentIntent != null) {
bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent);
}
@@ -8980,6 +9406,23 @@ public class Notification implements Parcelable
public PendingIntent getDeleteIntent() {
return mDeleteIntent;
}
+
+ /**
+ * Specifies whether this notification should suppress showing a message over top of apps
+ * outside of the launcher.
+ */
+ public TvExtender setSuppressShowOverApps(boolean suppress) {
+ mSuppressShowOverApps = suppress;
+ return this;
+ }
+
+ /**
+ * Returns true if this notification should not show messages over top of apps
+ * outside of the launcher.
+ */
+ public boolean getSuppressShowOverApps() {
+ return mSuppressShowOverApps;
+ }
}
/**
diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java
index 30f2697c..4a7cf623 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -463,7 +463,11 @@ public final class NotificationChannel implements Parcelable {
/**
* Returns the user specified importance e.g. {@link NotificationManager#IMPORTANCE_LOW} for
- * notifications posted to this channel.
+ * notifications posted to this channel. Note: This value might be >
+ * {@link NotificationManager#IMPORTANCE_NONE}, but notifications posted to this channel will
+ * not be shown to the user if the parent {@link NotificationChannelGroup} or app is blocked.
+ * See {@link NotificationChannelGroup#isBlocked()} and
+ * {@link NotificationManager#areNotificationsEnabled()}.
*/
public int getImportance() {
return mImportance;
diff --git a/android/app/NotificationChannelGroup.java b/android/app/NotificationChannelGroup.java
index 16166f7c..0fa3c7fa 100644
--- a/android/app/NotificationChannelGroup.java
+++ b/android/app/NotificationChannelGroup.java
@@ -145,7 +145,9 @@ public final class NotificationChannelGroup implements Parcelable {
/**
* Returns whether or not notifications posted to {@link NotificationChannel channels} belonging
- * to this group are blocked.
+ * to this group are blocked. This value is independent of
+ * {@link NotificationManager#areNotificationsEnabled()} and
+ * {@link NotificationChannel#getImportance()}.
*/
public boolean isBlocked() {
return mBlocked;
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index 49c03ab9..46d1264f 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -24,6 +24,7 @@ import android.annotation.TestApi;
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Icon;
import android.net.Uri;
@@ -98,7 +99,7 @@ public class NotificationManager {
* This broadcast is only sent to the app whose block state has changed.
*
* Input: nothing
- * Output: nothing
+ * Output: {@link #EXTRA_BLOCKED_STATE}
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_APP_BLOCK_STATE_CHANGED =
@@ -113,24 +114,31 @@ public class NotificationManager {
* This broadcast is only sent to the app that owns the channel that has changed.
*
* Input: nothing
- * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ * Output: {@link #EXTRA_NOTIFICATION_CHANNEL_ID}
+ * Output: {@link #EXTRA_BLOCKED_STATE}
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED =
"android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
/**
- * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
- * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the
- * object which has a new blocked state.
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} containing the id of the
+ * {@link NotificationChannel} which has a new blocked state.
*
- * The value will be the {@link NotificationChannel#getId()} of the channel for
- * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and
- * the {@link NotificationChannelGroup#getId()} of the group for
- * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}.
+ * The value will be the {@link NotificationChannel#getId()} of the channel.
*/
- public static final String EXTRA_BLOCK_STATE_CHANGED_ID =
- "android.app.extra.BLOCK_STATE_CHANGED_ID";
+ public static final String EXTRA_NOTIFICATION_CHANNEL_ID =
+ "android.app.extra.NOTIFICATION_CHANNEL_ID";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id
+ * of the {@link NotificationChannelGroup} which has a new blocked state.
+ *
+ * The value will be the {@link NotificationChannelGroup#getId()} of the group.
+ */
+ public static final String EXTRA_NOTIFICATION_CHANNEL_GROUP_ID =
+ "android.app.extra.NOTIFICATION_CHANNEL_GROUP_ID";
+
/**
* Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
@@ -142,7 +150,6 @@ public class NotificationManager {
*/
public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
-
/**
* Intent that is broadcast when a {@link NotificationChannelGroup} is
* {@link NotificationChannelGroup#isBlocked() blocked} or unblocked.
@@ -150,7 +157,8 @@ public class NotificationManager {
* This broadcast is only sent to the app that owns the channel group that has changed.
*
* Input: nothing
- * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ * Output: {@link #EXTRA_NOTIFICATION_CHANNEL_GROUP_ID}
+ * Output: {@link #EXTRA_BLOCKED_STATE}
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED =
@@ -343,6 +351,14 @@ public class NotificationManager {
* the same tag and id has already been posted by your application and has not yet been
* canceled, it will be replaced by the updated information.
*
+ * All {@link android.service.notification.NotificationListenerService listener services} will
+ * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris}
+ * provided on this notification or the
+ * {@link NotificationChannel} this notification is posted to using
+ * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the
+ * notification is canceled, or you can revoke permissions with
+ * {@link Context#revokeUriPermission(Uri, int)}.
+ *
* @param tag A string identifier for this notification. May be {@code null}.
* @param id An identifier for this notification. The pair (tag, id) must be unique
* within your application.
@@ -351,7 +367,7 @@ public class NotificationManager {
*/
public void notify(String tag, int id, Notification notification)
{
- notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
+ notifyAsUser(tag, id, notification, mContext.getUser());
}
/**
@@ -363,11 +379,13 @@ public class NotificationManager {
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
+
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
+
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
@@ -378,6 +396,7 @@ public class NotificationManager {
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
notification.reduceImageSizes(mContext);
+
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
boolean isLowRam = am.isLowRamDevice();
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
@@ -412,7 +431,7 @@ public class NotificationManager {
*/
public void cancel(String tag, int id)
{
- cancelAsUser(tag, id, new UserHandle(UserHandle.myUserId()));
+ cancelAsUser(tag, id, mContext.getUser());
}
/**
@@ -440,7 +459,7 @@ public class NotificationManager {
String pkg = mContext.getPackageName();
if (localLOGV) Log.v(TAG, pkg + ": cancelAll()");
try {
- service.cancelAllNotifications(pkg, UserHandle.myUserId());
+ service.cancelAllNotifications(pkg, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1012,12 +1031,18 @@ public class NotificationManager {
public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4;
/** Alarms are prioritized */
public static final int PRIORITY_CATEGORY_ALARMS = 1 << 5;
- /** Media, system, game (catch-all for non-never suppressible sounds) are prioritized */
- public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 1 << 6;
+ /** Media, game, voice navigation are prioritized */
+ public static final int PRIORITY_CATEGORY_MEDIA = 1 << 6;
+ /**System (catch-all for non-never suppressible sounds) are prioritized */
+ public static final int PRIORITY_CATEGORY_SYSTEM = 1 << 7;
- private static final int[] ALL_PRIORITY_CATEGORIES = {
+ /**
+ * @hide
+ */
+ public static final int[] ALL_PRIORITY_CATEGORIES = {
PRIORITY_CATEGORY_ALARMS,
- PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
+ PRIORITY_CATEGORY_MEDIA,
+ PRIORITY_CATEGORY_SYSTEM,
PRIORITY_CATEGORY_REMINDERS,
PRIORITY_CATEGORY_EVENTS,
PRIORITY_CATEGORY_MESSAGES,
@@ -1047,20 +1072,77 @@ public class NotificationManager {
* @hide
*/
public static final int SUPPRESSED_EFFECTS_UNSET = -1;
+
/**
* Whether notifications suppressed by DND should not interrupt visually (e.g. with
* notification lights or by turning the screen on) when the screen is off.
+ *
+ * @deprecated use {@link #SUPPRESSED_EFFECT_FULL_SCREEN_INTENT} and
+ * {@link #SUPPRESSED_EFFECT_AMBIENT} and {@link #SUPPRESSED_EFFECT_LIGHTS} individually.
*/
+ @Deprecated
public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1 << 0;
/**
* Whether notifications suppressed by DND should not interrupt visually when the screen
* is on (e.g. by peeking onto the screen).
+ *
+ * @deprecated use {@link #SUPPRESSED_EFFECT_PEEK}.
*/
+ @Deprecated
public static final int SUPPRESSED_EFFECT_SCREEN_ON = 1 << 1;
+ /**
+ * Whether {@link Notification#fullScreenIntent full screen intents} from
+ * notifications intercepted by DND are blocked.
+ */
+ public static final int SUPPRESSED_EFFECT_FULL_SCREEN_INTENT = 1 << 2;
+
+ /**
+ * Whether {@link NotificationChannel#shouldShowLights() notification lights} from
+ * notifications intercepted by DND are blocked.
+ */
+ public static final int SUPPRESSED_EFFECT_LIGHTS = 1 << 3;
+
+ /**
+ * Whether notifications intercepted by DND are prevented from peeking.
+ */
+ public static final int SUPPRESSED_EFFECT_PEEK = 1 << 4;
+
+ /**
+ * Whether notifications intercepted by DND are prevented from appearing in the status bar,
+ * on devices that support status bars.
+ */
+ public static final int SUPPRESSED_EFFECT_STATUS_BAR = 1 << 5;
+
+ /**
+ * Whether {@link NotificationChannel#canShowBadge() badges} from
+ * notifications intercepted by DND are blocked on devices that support badging.
+ */
+ public static final int SUPPRESSED_EFFECT_BADGE = 1 << 6;
+
+ /**
+ * Whether notification intercepted by DND are prevented from appearing on ambient displays
+ * on devices that support ambient display.
+ */
+ public static final int SUPPRESSED_EFFECT_AMBIENT = 1 << 7;
+
+ /**
+ * Whether notification intercepted by DND are prevented from appearing in notification
+ * list views like the notification shade or lockscreen on devices that support those
+ * views.
+ */
+ public static final int SUPPRESSED_EFFECT_NOTIFICATION_LIST = 1 << 8;
+
private static final int[] ALL_SUPPRESSED_EFFECTS = {
SUPPRESSED_EFFECT_SCREEN_OFF,
SUPPRESSED_EFFECT_SCREEN_ON,
+ SUPPRESSED_EFFECT_FULL_SCREEN_INTENT,
+ SUPPRESSED_EFFECT_LIGHTS,
+ SUPPRESSED_EFFECT_PEEK,
+ SUPPRESSED_EFFECT_STATUS_BAR,
+ SUPPRESSED_EFFECT_BADGE,
+ SUPPRESSED_EFFECT_AMBIENT,
+ SUPPRESSED_EFFECT_NOTIFICATION_LIST
};
/**
@@ -1072,6 +1154,12 @@ public class NotificationManager {
/**
* Constructs a policy for Do Not Disturb priority mode behavior.
*
+ * <p>
+ * Apps that target API levels below {@link Build.VERSION_CODES#P} cannot
+ * change user-designated values to allow or disallow
+ * {@link Policy#PRIORITY_CATEGORY_ALARMS}, {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and
+ * {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd.
+ *
* @param priorityCategories bitmask of categories of notifications that can bypass DND.
* @param priorityCallSenders which callers can bypass DND.
* @param priorityMessageSenders which message senders can bypass DND.
@@ -1084,6 +1172,26 @@ public class NotificationManager {
/**
* Constructs a policy for Do Not Disturb priority mode behavior.
*
+ * <p>
+ * Apps that target API levels below {@link Build.VERSION_CODES#P} cannot
+ * change user-designated values to allow or disallow
+ * {@link Policy#PRIORITY_CATEGORY_ALARMS}, {@link Policy#PRIORITY_CATEGORY_SYSTEM}, and
+ * {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd.
+ * <p>
+ * Additionally, apps that target API levels below {@link Build.VERSION_CODES#P} can
+ * only modify the {@link #SUPPRESSED_EFFECT_SCREEN_ON} and
+ * {@link #SUPPRESSED_EFFECT_SCREEN_OFF} bits of the suppressed visual effects field.
+ * All other suppressed effects will be ignored and reconstituted from the screen on
+ * and screen off values.
+ * <p>
+ * Apps that target {@link Build.VERSION_CODES#P} or above can set any
+ * suppressed visual effects. However, if any suppressed effects >
+ * {@link #SUPPRESSED_EFFECT_SCREEN_ON} are set, {@link #SUPPRESSED_EFFECT_SCREEN_ON}
+ * and {@link #SUPPRESSED_EFFECT_SCREEN_OFF} will be ignored and reconstituted from
+ * the more specific suppressed visual effect bits. Apps should migrate to targeting
+ * specific effects instead of the deprecated {@link #SUPPRESSED_EFFECT_SCREEN_ON} and
+ * {@link #SUPPRESSED_EFFECT_SCREEN_OFF} effects.
+ *
* @param priorityCategories bitmask of categories of notifications that can bypass DND.
* @param priorityCallSenders which callers can bypass DND.
* @param priorityMessageSenders which message senders can bypass DND.
@@ -1165,6 +1273,30 @@ public class NotificationManager {
}
}
+ /**
+ * @hide
+ */
+ public static int getAllSuppressedVisualEffects() {
+ int effects = 0;
+ for (int i = 0; i < ALL_SUPPRESSED_EFFECTS.length; i++) {
+ effects |= ALL_SUPPRESSED_EFFECTS[i];
+ }
+ return effects;
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean areAllVisualEffectsSuppressed(int effects) {
+ for (int i = 0; i < ALL_SUPPRESSED_EFFECTS.length; i++) {
+ final int effect = ALL_SUPPRESSED_EFFECTS[i];
+ if ((effects & effect) == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static String suppressedEffectsToString(int effects) {
if (effects <= 0) return "";
final StringBuilder sb = new StringBuilder();
@@ -1203,9 +1335,26 @@ public class NotificationManager {
private static String effectToString(int effect) {
switch (effect) {
- case SUPPRESSED_EFFECT_SCREEN_OFF: return "SUPPRESSED_EFFECT_SCREEN_OFF";
- case SUPPRESSED_EFFECT_SCREEN_ON: return "SUPPRESSED_EFFECT_SCREEN_ON";
- case SUPPRESSED_EFFECTS_UNSET: return "SUPPRESSED_EFFECTS_UNSET";
+ case SUPPRESSED_EFFECT_FULL_SCREEN_INTENT:
+ return "SUPPRESSED_EFFECT_FULL_SCREEN_INTENT";
+ case SUPPRESSED_EFFECT_LIGHTS:
+ return "SUPPRESSED_EFFECT_LIGHTS";
+ case SUPPRESSED_EFFECT_PEEK:
+ return "SUPPRESSED_EFFECT_PEEK";
+ case SUPPRESSED_EFFECT_STATUS_BAR:
+ return "SUPPRESSED_EFFECT_STATUS_BAR";
+ case SUPPRESSED_EFFECT_BADGE:
+ return "SUPPRESSED_EFFECT_BADGE";
+ case SUPPRESSED_EFFECT_AMBIENT:
+ return "SUPPRESSED_EFFECT_AMBIENT";
+ case SUPPRESSED_EFFECT_NOTIFICATION_LIST:
+ return "SUPPRESSED_EFFECT_NOTIFICATION_LIST";
+ case SUPPRESSED_EFFECT_SCREEN_OFF:
+ return "SUPPRESSED_EFFECT_SCREEN_OFF";
+ case SUPPRESSED_EFFECT_SCREEN_ON:
+ return "SUPPRESSED_EFFECT_SCREEN_ON";
+ case SUPPRESSED_EFFECTS_UNSET:
+ return "SUPPRESSED_EFFECTS_UNSET";
default: return "UNKNOWN_" + effect;
}
}
@@ -1218,8 +1367,8 @@ public class NotificationManager {
case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS";
case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS";
case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS";
- case PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER:
- return "PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER";
+ case PRIORITY_CATEGORY_MEDIA: return "PRIORITY_CATEGORY_MEDIA";
+ case PRIORITY_CATEGORY_SYSTEM: return "PRIORITY_CATEGORY_SYSTEM";
default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory;
}
}
@@ -1264,7 +1413,7 @@ public class NotificationManager {
final String pkg = mContext.getPackageName();
try {
final ParceledListSlice<StatusBarNotification> parceledList
- = service.getAppActiveNotifications(pkg, UserHandle.myUserId());
+ = service.getAppActiveNotifications(pkg, mContext.getUserId());
final List<StatusBarNotification> list = parceledList.getList();
return list.toArray(new StatusBarNotification[list.size()]);
} catch (RemoteException e) {
diff --git a/android/app/PendingIntent.java b/android/app/PendingIntent.java
index d6429ae9..315259bd 100644
--- a/android/app/PendingIntent.java
+++ b/android/app/PendingIntent.java
@@ -345,7 +345,7 @@ public final class PendingIntent implements Parcelable {
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
null, null, requestCode, new Intent[] { intent },
resolvedType != null ? new String[] { resolvedType } : null,
- flags, options, UserHandle.myUserId());
+ flags, options, context.getUserId());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -486,7 +486,7 @@ public final class PendingIntent implements Parcelable {
ActivityManager.getService().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
null, null, requestCode, intents, resolvedTypes, flags, options,
- UserHandle.myUserId());
+ context.getUserId());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -544,8 +544,7 @@ public final class PendingIntent implements Parcelable {
*/
public static PendingIntent getBroadcast(Context context, int requestCode,
Intent intent, @Flags int flags) {
- return getBroadcastAsUser(context, requestCode, intent, flags,
- new UserHandle(UserHandle.myUserId()));
+ return getBroadcastAsUser(context, requestCode, intent, flags, context.getUser());
}
/**
@@ -644,7 +643,7 @@ public final class PendingIntent implements Parcelable {
serviceKind, packageName,
null, null, requestCode, new Intent[] { intent },
resolvedType != null ? new String[] { resolvedType } : null,
- flags, null, UserHandle.myUserId());
+ flags, null, context.getUserId());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/android/app/PendingIntentPerfTest.java b/android/app/PendingIntentPerfTest.java
new file mode 100644
index 00000000..f8fd51d7
--- /dev/null
+++ b/android/app/PendingIntentPerfTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 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.content.Context;
+import android.content.Intent;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// Due to b/71353150, you might get "java.lang.AssertionError: Binder ProxyMap has too many
+// entries", but it's flaky. Adding "Runtime.getRuntime().gc()" between each iteration solves
+// the problem, but it doesn't seem like it's currently needed.
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PendingIntentPerfTest {
+
+ private Context mContext;
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private Intent mIntent;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mIntent = StubActivity.createLaunchIntent(mContext);
+ }
+
+ /**
+ * Benchmark time to create a PendingIntent.
+ */
+ @Test
+ public void create() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ state.resumeTiming();
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+ 0);
+
+ state.pauseTiming();
+ pendingIntent.cancel();
+ state.resumeTiming();
+ }
+ }
+
+ /**
+ * Benchmark time to create a PendingIntent with FLAG_CANCEL_CURRENT, already having an active
+ * PendingIntent.
+ */
+ @Test
+ public void createWithCancelFlag() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0,
+ mIntent, 0);
+ state.resumeTiming();
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ state.pauseTiming();
+ pendingIntent.cancel();
+ state.resumeTiming();
+ }
+ }
+
+ /**
+ * Benchmark time to create a PendingIntent with FLAG_UPDATE_CURRENT, already having an active
+ * PendingIntent.
+ */
+ @Test
+ public void createWithUpdateFlag() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final PendingIntent previousPendingIntent = PendingIntent.getActivity(mContext, 0,
+ mIntent, 0);
+ state.resumeTiming();
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, mIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ state.pauseTiming();
+ previousPendingIntent.cancel();
+ pendingIntent.cancel();
+ state.resumeTiming();
+ }
+ }
+
+ /**
+ * Benchmark time to cancel a PendingIntent.
+ */
+ @Test
+ public void cancel() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+ mIntent, 0);
+ state.resumeTiming();
+
+ pendingIntent.cancel();
+ }
+ }
+}
+
diff --git a/android/app/ProcessMemoryState.java b/android/app/ProcessMemoryState.java
new file mode 100644
index 00000000..39db16d1
--- /dev/null
+++ b/android/app/ProcessMemoryState.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The memory stats for a process.
+ * {@hide}
+ */
+public class ProcessMemoryState implements Parcelable {
+ public int uid;
+ public String processName;
+ public int oomScore;
+ public long pgfault;
+ public long pgmajfault;
+ public long rssInBytes;
+ public long cacheInBytes;
+ public long swapInBytes;
+
+ public ProcessMemoryState(int uid, String processName, int oomScore, long pgfault,
+ long pgmajfault, long rssInBytes, long cacheInBytes,
+ long swapInBytes) {
+ this.uid = uid;
+ this.processName = processName;
+ this.oomScore = oomScore;
+ this.pgfault = pgfault;
+ this.pgmajfault = pgmajfault;
+ this.rssInBytes = rssInBytes;
+ this.cacheInBytes = cacheInBytes;
+ this.swapInBytes = swapInBytes;
+ }
+
+ private ProcessMemoryState(Parcel in) {
+ uid = in.readInt();
+ processName = in.readString();
+ oomScore = in.readInt();
+ pgfault = in.readLong();
+ pgmajfault = in.readLong();
+ rssInBytes = in.readLong();
+ cacheInBytes = in.readLong();
+ swapInBytes = in.readLong();
+ }
+
+ public static final Creator<ProcessMemoryState> CREATOR = new Creator<ProcessMemoryState>() {
+ @Override
+ public ProcessMemoryState createFromParcel(Parcel in) {
+ return new ProcessMemoryState(in);
+ }
+
+ @Override
+ public ProcessMemoryState[] newArray(int size) {
+ return new ProcessMemoryState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeInt(uid);
+ parcel.writeString(processName);
+ parcel.writeInt(oomScore);
+ parcel.writeLong(pgfault);
+ parcel.writeLong(pgmajfault);
+ parcel.writeLong(rssInBytes);
+ parcel.writeLong(cacheInBytes);
+ parcel.writeLong(swapInBytes);
+ }
+}
diff --git a/android/app/ProfilerInfo.java b/android/app/ProfilerInfo.java
index 0ed1b082..6fbe9c6e 100644
--- a/android/app/ProfilerInfo.java
+++ b/android/app/ProfilerInfo.java
@@ -87,6 +87,15 @@ public class ProfilerInfo implements Parcelable {
}
/**
+ * Return a new ProfilerInfo instance, with fields populated from this object,
+ * and {@link agent} and {@link attachAgentDuringBind} as given.
+ */
+ public ProfilerInfo setAgent(String agent, boolean attachAgentDuringBind) {
+ return new ProfilerInfo(this.profileFile, this.profileFd, this.samplingInterval,
+ this.autoStopProfiler, this.streamingOutput, agent, attachAgentDuringBind);
+ }
+
+ /**
* Close profileFd, if it is open. The field will be null after a call to this function.
*/
public void closeFd() {
diff --git a/android/app/RemoteAction.java b/android/app/RemoteAction.java
index e7fe407b..47741c02 100644
--- a/android/app/RemoteAction.java
+++ b/android/app/RemoteAction.java
@@ -18,14 +18,9 @@ package android.app;
import android.annotation.NonNull;
import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.text.TextUtils;
-import android.util.Log;
import java.io.PrintWriter;
@@ -42,6 +37,7 @@ public final class RemoteAction implements Parcelable {
private final CharSequence mContentDescription;
private final PendingIntent mActionIntent;
private boolean mEnabled;
+ private boolean mShouldShowIcon;
RemoteAction(Parcel in) {
mIcon = Icon.CREATOR.createFromParcel(in);
@@ -49,6 +45,7 @@ public final class RemoteAction implements Parcelable {
mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mActionIntent = PendingIntent.CREATOR.createFromParcel(in);
mEnabled = in.readBoolean();
+ mShouldShowIcon = in.readBoolean();
}
public RemoteAction(@NonNull Icon icon, @NonNull CharSequence title,
@@ -62,6 +59,7 @@ public final class RemoteAction implements Parcelable {
mContentDescription = contentDescription;
mActionIntent = intent;
mEnabled = true;
+ mShouldShowIcon = true;
}
/**
@@ -79,6 +77,20 @@ public final class RemoteAction implements Parcelable {
}
/**
+ * Sets whether the icon should be shown.
+ */
+ public void setShouldShowIcon(boolean shouldShowIcon) {
+ mShouldShowIcon = shouldShowIcon;
+ }
+
+ /**
+ * Return whether the icon should be shown.
+ */
+ public boolean shouldShowIcon() {
+ return mShouldShowIcon;
+ }
+
+ /**
* Return an icon representing the action.
*/
public @NonNull Icon getIcon() {
@@ -125,6 +137,7 @@ public final class RemoteAction implements Parcelable {
TextUtils.writeToParcel(mContentDescription, out, flags);
mActionIntent.writeToParcel(out, flags);
out.writeBoolean(mEnabled);
+ out.writeBoolean(mShouldShowIcon);
}
public void dump(String prefix, PrintWriter pw) {
@@ -134,6 +147,7 @@ public final class RemoteAction implements Parcelable {
pw.print(" contentDescription=" + mContentDescription);
pw.print(" icon=" + mIcon);
pw.print(" action=" + mActionIntent.getIntent());
+ pw.print(" shouldShowIcon=" + mShouldShowIcon);
pw.println();
}
diff --git a/android/app/RemoteInput.java b/android/app/RemoteInput.java
index b7100e6f..6feb38e9 100644
--- a/android/app/RemoteInput.java
+++ b/android/app/RemoteInput.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.IntDef;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
@@ -25,6 +26,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -74,9 +77,14 @@ public final class RemoteInput implements Parcelable {
private static final String EXTRA_DATA_TYPE_RESULTS_DATA =
"android.remoteinput.dataTypeResultsData";
- /** Extra added to a clip data intent object identifying the source of the results. */
+ /** Extra added to a clip data intent object identifying the {@link Source} of the results. */
private static final String EXTRA_RESULTS_SOURCE = "android.remoteinput.resultsSource";
+ /** @hide */
+ @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_FREE_FORM_INPUT, SOURCE_CHOICE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Source {}
+
/** The user manually entered the data. */
public static final int SOURCE_FREE_FORM_INPUT = 0;
@@ -437,10 +445,9 @@ public final class RemoteInput implements Parcelable {
*
* @param intent The intent to add remote input source to. The {@link ClipData}
* field of the intent will be modified to contain the source.
- * field of the intent will be modified to contain the source.
* @param source The source of the results.
*/
- public static void setResultsSource(Intent intent, int source) {
+ public static void setResultsSource(Intent intent, @Source int source) {
Intent clipDataIntent = getClipDataIntentFromIntent(intent);
if (clipDataIntent == null) {
clipDataIntent = new Intent(); // First time we've added a result.
@@ -460,6 +467,7 @@ public final class RemoteInput implements Parcelable {
* @return The source of the results. If no source was set, {@link #SOURCE_FREE_FORM_INPUT} will
* be returned.
*/
+ @Source
public static int getResultsSource(Intent intent) {
Intent clipDataIntent = getClipDataIntentFromIntent(intent);
if (clipDataIntent == null) {
diff --git a/android/app/ResourcesManager.java b/android/app/ResourcesManager.java
index fb11272d..fc5ea660 100644
--- a/android/app/ResourcesManager.java
+++ b/android/app/ResourcesManager.java
@@ -21,6 +21,7 @@ import static android.app.ActivityThread.DEBUG_CONFIGURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.CompatResources;
import android.content.res.CompatibilityInfo;
@@ -34,6 +35,7 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.LruCache;
import android.util.Pair;
import android.util.Slog;
import android.view.Display;
@@ -41,9 +43,13 @@ import android.view.DisplayAdjustments;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -59,12 +65,7 @@ public class ResourcesManager {
* Predicate that returns true if a WeakReference is gc'ed.
*/
private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
- new Predicate<WeakReference<Resources>>() {
- @Override
- public boolean test(WeakReference<Resources> weakRef) {
- return weakRef == null || weakRef.get() == null;
- }
- };
+ weakRef -> weakRef == null || weakRef.get() == null;
/**
* The global compatibility settings.
@@ -89,6 +90,48 @@ public class ResourcesManager {
*/
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
+ private static class ApkKey {
+ public final String path;
+ public final boolean sharedLib;
+ public final boolean overlay;
+
+ ApkKey(String path, boolean sharedLib, boolean overlay) {
+ this.path = path;
+ this.sharedLib = sharedLib;
+ this.overlay = overlay;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + this.path.hashCode();
+ result = 31 * result + Boolean.hashCode(this.sharedLib);
+ result = 31 * result + Boolean.hashCode(this.overlay);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ApkKey)) {
+ return false;
+ }
+ ApkKey other = (ApkKey) obj;
+ return this.path.equals(other.path) && this.sharedLib == other.sharedLib
+ && this.overlay == other.overlay;
+ }
+ }
+
+ /**
+ * The ApkAssets we are caching and intend to hold strong references to.
+ */
+ private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15);
+
+ /**
+ * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
+ * in our LRU cache. Bonus resources :)
+ */
+ private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
+
/**
* Resources and base configuration override associated with an Activity.
*/
@@ -260,6 +303,43 @@ public class ResourcesManager {
}
}
+ private static String overlayPathToIdmapPath(String path) {
+ return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
+ }
+
+ private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
+ throws IOException {
+ final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
+ ApkAssets apkAssets = mLoadedApkAssets.get(newKey);
+ if (apkAssets != null) {
+ return apkAssets;
+ }
+
+ // Optimistically check if this ApkAssets exists somewhere else.
+ final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
+ if (apkAssetsRef != null) {
+ apkAssets = apkAssetsRef.get();
+ if (apkAssets != null) {
+ mLoadedApkAssets.put(newKey, apkAssets);
+ return apkAssets;
+ } else {
+ // Clean up the reference.
+ mCachedApkAssets.remove(newKey);
+ }
+ }
+
+ // We must load this from disk.
+ if (overlay) {
+ apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),
+ false /*system*/);
+ } else {
+ apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
+ }
+ mLoadedApkAssets.put(newKey, apkAssets);
+ mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
+ return apkAssets;
+ }
+
/**
* Creates an AssetManager from the paths within the ResourcesKey.
*
@@ -270,13 +350,16 @@ public class ResourcesManager {
*/
@VisibleForTesting
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
- AssetManager assets = new AssetManager();
+ final AssetManager.Builder builder = new AssetManager.Builder();
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
if (key.mResDir != null) {
- if (assets.addAssetPath(key.mResDir) == 0) {
+ try {
+ builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
+ false /*overlay*/));
+ } catch (IOException e) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
@@ -284,7 +367,10 @@ public class ResourcesManager {
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
- if (assets.addAssetPath(splitResDir) == 0) {
+ try {
+ builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
+ false /*overlay*/));
+ } catch (IOException e) {
Log.e(TAG, "failed to add split asset path " + splitResDir);
return null;
}
@@ -293,7 +379,14 @@ public class ResourcesManager {
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
- assets.addOverlayPath(idmapPath);
+ try {
+ builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
+ true /*overlay*/));
+ } catch (IOException e) {
+ Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+ // continue.
+ }
}
}
@@ -302,14 +395,73 @@ public class ResourcesManager {
if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
- if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
+ try {
+ builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
+ false /*overlay*/));
+ } catch (IOException e) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
+
+ // continue.
}
}
}
}
- return assets;
+
+ return builder.build();
+ }
+
+ private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
+ int count = 0;
+ for (WeakReference<T> ref : collection) {
+ final T value = ref != null ? ref.get() : null;
+ if (value != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * @hide
+ */
+ public void dump(String prefix, PrintWriter printWriter) {
+ synchronized (this) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ for (int i = 0; i < prefix.length() / 2; i++) {
+ pw.increaseIndent();
+ }
+
+ pw.println("ResourcesManager:");
+ pw.increaseIndent();
+ pw.print("cached apks: total=");
+ pw.print(mLoadedApkAssets.size());
+ pw.print(" created=");
+ pw.print(mLoadedApkAssets.createCount());
+ pw.print(" evicted=");
+ pw.print(mLoadedApkAssets.evictionCount());
+ pw.print(" hit=");
+ pw.print(mLoadedApkAssets.hitCount());
+ pw.print(" miss=");
+ pw.print(mLoadedApkAssets.missCount());
+ pw.print(" max=");
+ pw.print(mLoadedApkAssets.maxSize());
+ pw.println();
+
+ pw.print("total apks: ");
+ pw.println(countLiveReferences(mCachedApkAssets.values()));
+
+ pw.print("resources: ");
+
+ int references = countLiveReferences(mResourceReferences);
+ for (ActivityResources activityResources : mActivityResourceReferences.values()) {
+ references += countLiveReferences(activityResources.activityResources);
+ }
+ pw.println(references);
+
+ pw.print("resource impls: ");
+ pw.println(countLiveReferences(mResourceImpls.values()));
+ }
}
private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
@@ -630,28 +782,16 @@ public class ResourcesManager {
// We will create the ResourcesImpl object outside of holding this lock.
}
- }
-
- // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
- ResourcesImpl resourcesImpl = createResourcesImpl(key);
- if (resourcesImpl == null) {
- return null;
- }
- synchronized (this) {
- ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
- if (existingResourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
- + " new impl=" + resourcesImpl);
- }
- resourcesImpl.getAssets().close();
- resourcesImpl = existingResourcesImpl;
- } else {
- // Add this ResourcesImpl to the cache.
- mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+ // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
+ ResourcesImpl resourcesImpl = createResourcesImpl(key);
+ if (resourcesImpl == null) {
+ return null;
}
+ // Add this ResourcesImpl to the cache.
+ mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
diff --git a/android/app/SearchManager.java b/android/app/SearchManager.java
index ea990ad2..49faf402 100644
--- a/android/app/SearchManager.java
+++ b/android/app/SearchManager.java
@@ -33,7 +33,6 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
-import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -48,6 +47,9 @@ import java.util.List;
* and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
* {@link android.content.Intent Intent}.
*
+ * <p>
+ * {@link Configuration#UI_MODE_TYPE_WATCH} does not support this system service.
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using the search dialog and adding search
@@ -749,6 +751,8 @@ public class SearchManager
*
* <p>This function can be safely called at any time (even if no search is active.)
*
+ * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method.
+ *
* @see #startSearch
*/
public void stopSearch() {
@@ -799,6 +803,8 @@ public class SearchManager
/**
* Set or clear the callback that will be invoked whenever the search UI is dismissed.
*
+ * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method.
+ *
* @param listener The {@link OnDismissListener} to use, or null.
*/
public void setOnDismissListener(final OnDismissListener listener) {
@@ -808,6 +814,8 @@ public class SearchManager
/**
* Set or clear the callback that will be invoked whenever the search UI is canceled.
*
+ * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method.
+ *
* @param listener The {@link OnCancelListener} to use, or null.
*/
public void setOnCancelListener(OnCancelListener listener) {
diff --git a/android/app/Service.java b/android/app/Service.java
index 256c4793..ea0fd75b 100644
--- a/android/app/Service.java
+++ b/android/app/Service.java
@@ -471,14 +471,6 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* {@link #onStart} and returns either {@link #START_STICKY}
* or {@link #START_STICKY_COMPATIBILITY}.
*
- * <p>If you need your application to run on platform versions prior to API
- * level 5, you can use the following model to handle the older {@link #onStart}
- * callback in that case. The <code>handleCommand</code> method is implemented by
- * you as appropriate:
- *
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
- * start_compatibility}
- *
* <p class="caution">Note that the system calls this on your
* service's main thread. A service's main thread is the same
* thread where UI operations take place for Activities running in the
@@ -687,6 +679,10 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* {@link #startService(Intent)} first to tell the system it should keep the service running,
* and then use this method to tell it to keep it running harder.</p>
*
+ * <p>Apps targeting API {@link android.os.Build.VERSION_CODES#P} or later must request
+ * the permission {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use
+ * this API.</p>
+ *
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
* NotificationManager.notify(int, Notification)}; must not be 0.
diff --git a/android/app/StatsManager.java b/android/app/StatsManager.java
index 963fc776..4a6fa8c2 100644
--- a/android/app/StatsManager.java
+++ b/android/app/StatsManager.java
@@ -16,6 +16,7 @@
package android.app;
import android.Manifest;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.IBinder;
@@ -30,23 +31,36 @@ import android.util.Slog;
* @hide
*/
@SystemApi
-public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends.
+public final class StatsManager {
IStatsManager mService;
private static final String TAG = "StatsManager";
+ private static final boolean DEBUG = false;
- /** Long extra of uid that added the relevant stats config. */
- public static final String EXTRA_STATS_CONFIG_UID =
- "android.app.extra.STATS_CONFIG_UID";
- /** Long extra of the relevant stats config's configKey. */
- public static final String EXTRA_STATS_CONFIG_KEY =
- "android.app.extra.STATS_CONFIG_KEY";
- /** Long extra of the relevant statsd_config.proto's Subscription.id. */
+ /**
+ * Long extra of uid that added the relevant stats config.
+ */
+ public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
+ /**
+ * Long extra of the relevant stats config's configKey.
+ */
+ public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
+ /**
+ * Long extra of the relevant statsd_config.proto's Subscription.id.
+ */
public static final String EXTRA_STATS_SUBSCRIPTION_ID =
"android.app.extra.STATS_SUBSCRIPTION_ID";
- /** Long extra of the relevant statsd_config.proto's Subscription.rule_id. */
+ /**
+ * Long extra of the relevant statsd_config.proto's Subscription.rule_id.
+ */
public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
"android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
/**
+ * List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie.
+ * Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}.
+ */
+ public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES =
+ "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
+ /**
* Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
* information.
*/
@@ -54,6 +68,12 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem
"android.app.extra.STATS_DIMENSIONS_VALUE";
/**
+ * Broadcast Action: Statsd has started.
+ * Configurations and PendingIntents can now be sent to it.
+ */
+ public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
+
+ /**
* Constructor for StatsManagerClient.
*
* @hide
@@ -68,22 +88,20 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem
* @param configKey An arbitrary integer that allows clients to track the configuration.
* @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
* dependencies eg, conditions and matchers).
- * @param pkg The package name to receive the broadcast.
- * @param cls The name of the class that receives the broadcast.
* @return true if successful
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) {
+ public boolean addConfiguration(long configKey, byte[] config) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when adding configuration");
+ Slog.e(TAG, "Failed to find statsd when adding configuration");
return false;
}
- return service.addConfiguration(configKey, config, pkg, cls);
+ return service.addConfiguration(configKey, config);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when adding configuration");
+ Slog.e(TAG, "Failed to connect to statsd when adding configuration");
return false;
}
}
@@ -101,12 +119,12 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when removing configuration");
+ Slog.e(TAG, "Failed to find statsd when removing configuration");
return false;
}
return service.removeConfiguration(configKey);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connect to statsd when removing configuration");
+ Slog.e(TAG, "Failed to connect to statsd when removing configuration");
return false;
}
}
@@ -115,43 +133,40 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem
/**
* Set the PendingIntent to be used when broadcasting subscriber information to the given
* subscriberId within the given config.
- *
* <p>
* Suppose that the calling uid has added a config with key configKey, and that in this config
* it is specified that when a particular anomaly is detected, a broadcast should be sent to
* a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
* that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
* when the anomaly is detected.
- *
* <p>
* When statsd sends the broadcast, the PendingIntent will used to send an intent with
* information of
- * {@link #EXTRA_STATS_CONFIG_UID},
- * {@link #EXTRA_STATS_CONFIG_KEY},
- * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
- * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and
- * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
- *
+ * {@link #EXTRA_STATS_CONFIG_UID},
+ * {@link #EXTRA_STATS_CONFIG_KEY},
+ * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
+ * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID},
+ * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and
+ * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
* <p>
* This function can only be called by the owner (uid) of the config. It must be called each
* time statsd starts. The config must have been added first (via addConfiguration()).
*
- * @param configKey The integer naming the config to which this subscriber is attached.
- * @param subscriberId ID of the subscriber, as used in the config.
+ * @param configKey The integer naming the config to which this subscriber is attached.
+ * @param subscriberId ID of the subscriber, as used in the config.
* @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
* associated with the given subscriberId. May be null, in which case
* it undoes any previous setting of this subscriberId.
* @return true if successful
*/
@RequiresPermission(Manifest.permission.DUMP)
- public boolean setBroadcastSubscriber(long configKey,
- long subscriberId,
- PendingIntent pendingIntent) {
+ public boolean setBroadcastSubscriber(
+ long configKey, long subscriberId, PendingIntent pendingIntent) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber");
+ Slog.e(TAG, "Failed to find statsd when adding broadcast subscriber");
return false;
}
if (pendingIntent != null) {
@@ -162,7 +177,45 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem
return service.unsetBroadcastSubscriber(configKey, subscriberId);
}
} catch (RemoteException e) {
- Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+ Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Registers the operation that is called to retrieve the metrics data. This must be called
+ * each time statsd starts. The config must have been added first (via addConfiguration(),
+ * although addConfiguration could have been called on a previous boot). This operation allows
+ * statsd to send metrics data whenever statsd determines that the metrics in memory are
+ * approaching the memory limits. The fetch operation should call {@link #getData} to fetch the
+ * data, which also deletes the retrieved metrics from statsd's memory.
+ *
+ * @param configKey The integer naming the config to which this operation is attached.
+ * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
+ * associated with the given subscriberId. May be null, in which case
+ * it removes any associated pending intent with this configKey.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "Failed to find statsd when registering data listener.");
+ return false;
+ }
+ if (pendingIntent == null) {
+ return service.removeDataFetchOperation(configKey);
+ } else {
+ // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+ IBinder intentSender = pendingIntent.getTarget().asBinder();
+ return service.setDataFetchOperation(configKey, intentSender);
+ }
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to connect to statsd when registering data listener.");
return false;
}
}
@@ -173,20 +226,21 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem
* the retrieved metrics from statsd memory.
*
* @param configKey Configuration key to retrieve data from.
- * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
+ * @return Serialized ConfigMetricsReportList proto. Returns null on failure (eg, if statsd
+ * crashed).
*/
@RequiresPermission(Manifest.permission.DUMP)
- public byte[] getData(long configKey) {
+ public @Nullable byte[] getData(long configKey) {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when getting data");
+ Slog.e(TAG, "Failed to find statsd when getting data");
return null;
}
return service.getData(configKey);
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connecto statsd when getting data");
+ Slog.e(TAG, "Failed to connect to statsd when getting data");
return null;
}
}
@@ -197,20 +251,20 @@ public final class StatsManager extends android.util.StatsManager { // TODO: Rem
* the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
* This getter is not destructive and will not reset any metrics/counters.
*
- * @return Serialized StatsdStatsReport proto. Returns null on failure.
+ * @return Serialized StatsdStatsReport proto. Returns null on failure (eg, if statsd crashed).
*/
@RequiresPermission(Manifest.permission.DUMP)
- public byte[] getMetadata() {
+ public @Nullable byte[] getMetadata() {
synchronized (this) {
try {
IStatsManager service = getIStatsManagerLocked();
if (service == null) {
- Slog.d(TAG, "Failed to find statsd when getting metadata");
+ Slog.e(TAG, "Failed to find statsd when getting metadata");
return null;
}
return service.getMetadata();
} catch (RemoteException e) {
- Slog.d(TAG, "Failed to connecto statsd when getting metadata");
+ Slog.e(TAG, "Failed to connect to statsd when getting metadata");
return null;
}
}
diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java
index 85a9be35..b83b44d2 100644
--- a/android/app/StatusBarManager.java
+++ b/android/app/StatusBarManager.java
@@ -74,11 +74,12 @@ public class StatusBarManager {
public static final int DISABLE2_SYSTEM_ICONS = 1 << 1;
public static final int DISABLE2_NOTIFICATION_SHADE = 1 << 2;
public static final int DISABLE2_GLOBAL_ACTIONS = 1 << 3;
+ public static final int DISABLE2_ROTATE_SUGGESTIONS = 1 << 4;
public static final int DISABLE2_NONE = 0x00000000;
public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS
- | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS;
+ | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS | DISABLE2_ROTATE_SUGGESTIONS;
@IntDef(flag = true, prefix = { "DISABLE2_" }, value = {
DISABLE2_NONE,
@@ -86,7 +87,8 @@ public class StatusBarManager {
DISABLE2_QUICK_SETTINGS,
DISABLE2_SYSTEM_ICONS,
DISABLE2_NOTIFICATION_SHADE,
- DISABLE2_GLOBAL_ACTIONS
+ DISABLE2_GLOBAL_ACTIONS,
+ DISABLE2_ROTATE_SUGGESTIONS
})
@Retention(RetentionPolicy.SOURCE)
public @interface Disable2Flags {}
diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java
index 4310434c..1776eace 100644
--- a/android/app/SystemServiceRegistry.java
+++ b/android/app/SystemServiceRegistry.java
@@ -89,7 +89,6 @@ import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
-import android.net.wifi.IRttManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.IWifiScanner;
import android.net.wifi.RttManager;
@@ -116,7 +115,6 @@ import android.os.ISystemUpdateManager;
import android.os.IUserManager;
import android.os.IncidentManager;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RecoverySystem;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -162,6 +160,7 @@ import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Manages all of the system services that can be returned by {@link Context#getSystemService}.
@@ -638,13 +637,13 @@ final class SystemServiceRegistry {
registerService(Context.WIFI_RTT_SERVICE, RttManager.class,
new CachedServiceFetcher<RttManager>() {
- @Override
- public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_SERVICE);
- IRttManager service = IRttManager.Stub.asInterface(b);
- return new RttManager(ctx.getOuterContext(), service,
- ConnectivityThread.getInstanceLooper());
- }});
+ @Override
+ public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_RANGING_SERVICE);
+ IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+ return new RttManager(ctx.getOuterContext(),
+ new WifiRttManager(ctx.getOuterContext(), service));
+ }});
registerService(Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class,
new CachedServiceFetcher<WifiRttManager>() {
@@ -724,8 +723,9 @@ final class SystemServiceRegistry {
service = IPrintManager.Stub.asInterface(ServiceManager
.getServiceOrThrow(Context.PRINT_SERVICE));
}
- return new PrintManager(ctx.getOuterContext(), service, UserHandle.myUserId(),
- UserHandle.getAppId(Process.myUid()));
+ final int userId = ctx.getUserId();
+ final int appId = UserHandle.getAppId(ctx.getApplicationInfo().uid);
+ return new PrintManager(ctx.getOuterContext(), service, userId, appId);
}});
registerService(Context.COMPANION_DEVICE_SERVICE, CompanionDeviceManager.class,
@@ -780,12 +780,12 @@ final class SystemServiceRegistry {
}});
registerService(Context.TV_INPUT_SERVICE, TvInputManager.class,
- new StaticServiceFetcher<TvInputManager>() {
+ new CachedServiceFetcher<TvInputManager>() {
@Override
- public TvInputManager createService() throws ServiceNotFoundException {
+ public TvInputManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder iBinder = ServiceManager.getServiceOrThrow(Context.TV_INPUT_SERVICE);
ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder);
- return new TvInputManager(service, UserHandle.myUserId());
+ return new TvInputManager(service, ctx.getUserId());
}});
registerService(Context.NETWORK_SCORE_SERVICE, NetworkScoreManager.class,
@@ -993,6 +993,10 @@ final class SystemServiceRegistry {
return new Object[sServiceCacheSize];
}
+ public static AtomicInteger[] createServiceInitializationStateArray() {
+ return new AtomicInteger[sServiceCacheSize];
+ }
+
/**
* Gets a system service from a given context.
*/
@@ -1041,19 +1045,95 @@ final class SystemServiceRegistry {
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
+
+ // Fast path. If it's already cached, just return it.
+ Object service = cache[mCacheIndex];
+ if (service != null) {
+ return (T) service;
+ }
+
+ // Slow path.
+ final AtomicInteger[] gates = ctx.mServiceInitializationStateArray;
+ final AtomicInteger gate;
+
synchronized (cache) {
- // Fetch or create the service.
- Object service = cache[mCacheIndex];
- if (service == null) {
+ // See if it's cached or not again, with the lock held this time.
+ service = cache[mCacheIndex];
+ if (service != null) {
+ return (T) service;
+ }
+
+ // Not initialized yet. Create an atomic boolean to control which thread should
+ // instantiate the service.
+ if (gates[mCacheIndex] != null) {
+ gate = gates[mCacheIndex];
+ } else {
+ gate = new AtomicInteger(ContextImpl.STATE_UNINITIALIZED);
+ gates[mCacheIndex] = gate;
+ }
+ }
+
+ // Not cached yet.
+ //
+ // Note multiple threads can reach here for the same service on the same context
+ // concurrently.
+ //
+ // Now we're going to instantiate the service, but do so without the cache held;
+ // otherwise it could deadlock. (b/71882178)
+ //
+ // However we still don't want to instantiate the same service multiple times, so
+ // use the atomic integer to ensure only one thread will call createService().
+
+ if (gate.compareAndSet(
+ ContextImpl.STATE_UNINITIALIZED, ContextImpl.STATE_INITIALIZING)) {
+ try {
+ // This thread is the first one to get here. Instantiate the service
+ // *without* the cache lock held.
try {
service = createService(ctx);
- cache[mCacheIndex] = service;
+
+ synchronized (cache) {
+ cache[mCacheIndex] = service;
+ }
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
+ } finally {
+ // Tell the all other threads that the cache is ready now.
+ // (But it's still be null in case of ServiceNotFoundException.)
+ synchronized (gate) {
+ gate.set(ContextImpl.STATE_READY);
+ gate.notifyAll();
+ }
+ }
+ return (T) service;
+ }
+ // Other threads will wait on the gate lock.
+ synchronized (gate) {
+ boolean interrupted = false;
+
+ // Note: We check whether "state == STATE_READY", not
+ // "cache[mCacheIndex] != null", because "cache[mCacheIndex] == null"
+ // is still a valid outcome in the ServiceNotFoundException case.
+ while (gate.get() != ContextImpl.STATE_READY) {
+ try {
+ gate.wait();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "getService() interrupted");
+ interrupted = true;
+ }
}
- return (T)service;
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ // Now the first thread has initialized it.
+ // It may still be null if ServiceNotFoundException was thrown, but that shouldn't
+ // happen, so we'll just return null here in that case.
+ synchronized (cache) {
+ service = cache[mCacheIndex];
}
+ return (T) service;
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
diff --git a/android/app/TaskStackBuilder.java b/android/app/TaskStackBuilder.java
index bab993f8..b99b3274 100644
--- a/android/app/TaskStackBuilder.java
+++ b/android/app/TaskStackBuilder.java
@@ -213,13 +213,13 @@ public class TaskStackBuilder {
* Start the task stack constructed by this builder.
* @hide
*/
- public void startActivities(Bundle options, UserHandle userHandle) {
+ public int startActivities(Bundle options, UserHandle userHandle) {
if (mIntents.isEmpty()) {
throw new IllegalStateException(
"No intents added to TaskStackBuilder; cannot startActivities");
}
- mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle);
+ return mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle);
}
/**
@@ -230,7 +230,7 @@ public class TaskStackBuilder {
* Context.startActivity(Intent, Bundle)} for more details.
*/
public void startActivities(Bundle options) {
- startActivities(options, new UserHandle(UserHandle.myUserId()));
+ startActivities(options, mSourceContext.getUser());
}
/**
diff --git a/android/app/UiAutomation.java b/android/app/UiAutomation.java
index ba39740b..bd4933a2 100644
--- a/android/app/UiAutomation.java
+++ b/android/app/UiAutomation.java
@@ -47,13 +47,10 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
-import com.android.internal.util.CollectionUtils;
-
import libcore.io.IoUtils;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -583,8 +580,6 @@ public final class UiAutomation {
// Execute the command *without* the lock being held.
command.run();
- List<AccessibilityEvent> eventsReceived = Collections.emptyList();
-
// Acquire the lock and wait for the event.
try {
// Wait for the event.
@@ -605,14 +600,14 @@ public final class UiAutomation {
if (filter.accept(event)) {
return event;
}
- eventsReceived = CollectionUtils.add(eventsReceived, event);
+ event.recycle();
}
// Check if timed out and if not wait.
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
if (remainingTimeMillis <= 0) {
throw new TimeoutException("Expected event not received within: "
- + timeoutMillis + " ms, among " + eventsReceived);
+ + timeoutMillis + " ms.");
}
synchronized (mLock) {
if (mEventQueue.isEmpty()) {
@@ -625,10 +620,6 @@ public final class UiAutomation {
}
}
} finally {
- for (int i = 0; i < CollectionUtils.size(eventsReceived); i++) {
- AccessibilityEvent event = eventsReceived.get(i);
- event.recycle();
- }
synchronized (mLock) {
mWaitingForEventDelivery = false;
mEventQueue.clear();
@@ -885,16 +876,36 @@ public final class UiAutomation {
}
/**
- * Grants a runtime permission to a package for a user.
+ * Grants a runtime permission to a package.
* @param packageName The package to which to grant.
* @param permission The permission to grant.
- * @return Whether granting succeeded.
- *
+ * @throws SecurityException if unable to grant the permission.
+ */
+ public void grantRuntimePermission(String packageName, String permission) {
+ grantRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle());
+ }
+
+ /**
+ * @deprecated replaced by
+ * {@link #grantRuntimePermissionAsUser(String, String, UserHandle)}.
* @hide
*/
+ @Deprecated
@TestApi
public boolean grantRuntimePermission(String packageName, String permission,
UserHandle userHandle) {
+ grantRuntimePermissionAsUser(packageName, permission, userHandle);
+ return true;
+ }
+
+ /**
+ * Grants a runtime permission to a package for a user.
+ * @param packageName The package to which to grant.
+ * @param permission The permission to grant.
+ * @throws SecurityException if unable to grant the permission.
+ */
+ public void grantRuntimePermissionAsUser(String packageName, String permission,
+ UserHandle userHandle) {
synchronized (mLock) {
throwIfNotConnectedLocked();
}
@@ -905,25 +916,42 @@ public final class UiAutomation {
// Calling out without a lock held.
mUiAutomationConnection.grantRuntimePermission(packageName,
permission, userHandle.getIdentifier());
- // TODO: The package manager API should return boolean.
- return true;
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error granting runtime permission", re);
+ } catch (Exception e) {
+ throw new SecurityException("Error granting runtime permission", e);
}
- return false;
}
/**
- * Revokes a runtime permission from a package for a user.
- * @param packageName The package from which to revoke.
- * @param permission The permission to revoke.
- * @return Whether revoking succeeded.
- *
+ * Revokes a runtime permission from a package.
+ * @param packageName The package to which to grant.
+ * @param permission The permission to grant.
+ * @throws SecurityException if unable to revoke the permission.
+ */
+ public void revokeRuntimePermission(String packageName, String permission) {
+ revokeRuntimePermissionAsUser(packageName, permission, android.os.Process.myUserHandle());
+ }
+
+ /**
+ * @deprecated replaced by
+ * {@link #revokeRuntimePermissionAsUser(String, String, UserHandle)}.
* @hide
*/
+ @Deprecated
@TestApi
public boolean revokeRuntimePermission(String packageName, String permission,
UserHandle userHandle) {
+ revokeRuntimePermissionAsUser(packageName, permission, userHandle);
+ return true;
+ }
+
+ /**
+ * Revokes a runtime permission from a package.
+ * @param packageName The package to which to grant.
+ * @param permission The permission to grant.
+ * @throws SecurityException if unable to revoke the permission.
+ */
+ public void revokeRuntimePermissionAsUser(String packageName, String permission,
+ UserHandle userHandle) {
synchronized (mLock) {
throwIfNotConnectedLocked();
}
@@ -934,12 +962,9 @@ public final class UiAutomation {
// Calling out without a lock held.
mUiAutomationConnection.revokeRuntimePermission(packageName,
permission, userHandle.getIdentifier());
- // TODO: The package manager API should return boolean.
- return true;
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error revoking runtime permission", re);
+ } catch (Exception e) {
+ throw new SecurityException("Error granting runtime permission", e);
}
- return false;
}
/**
@@ -958,6 +983,7 @@ public final class UiAutomation {
synchronized (mLock) {
throwIfNotConnectedLocked();
}
+ warnIfBetterCommand(command);
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
@@ -1000,6 +1026,7 @@ public final class UiAutomation {
synchronized (mLock) {
throwIfNotConnectedLocked();
}
+ warnIfBetterCommand(command);
ParcelFileDescriptor source_read = null;
ParcelFileDescriptor sink_read = null;
@@ -1065,6 +1092,16 @@ public final class UiAutomation {
}
}
+ private void warnIfBetterCommand(String cmd) {
+ if (cmd.startsWith("pm grant ")) {
+ Log.w(LOG_TAG, "UiAutomation.grantRuntimePermission() "
+ + "is more robust and should be used instead of 'pm grant'");
+ } else if (cmd.startsWith("pm revoke ")) {
+ Log.w(LOG_TAG, "UiAutomation.revokeRuntimePermission() "
+ + "is more robust and should be used instead of 'pm revoke'");
+ }
+ }
+
private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
public IAccessibilityServiceClientImpl(Looper looper) {
diff --git a/android/app/WallpaperColors.java b/android/app/WallpaperColors.java
index a2864b9d..2d007adf 100644
--- a/android/app/WallpaperColors.java
+++ b/android/app/WallpaperColors.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -107,6 +108,11 @@ public final class WallpaperColors implements Parcelable {
* @param drawable Source where to extract from.
*/
public static WallpaperColors fromDrawable(Drawable drawable) {
+ if (drawable == null) {
+ throw new IllegalArgumentException("Drawable cannot be null");
+ }
+
+ Rect initialBounds = drawable.copyBounds();
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
@@ -126,6 +132,7 @@ public final class WallpaperColors implements Parcelable {
final WallpaperColors colors = WallpaperColors.fromBitmap(bitmap);
bitmap.recycle();
+ drawable.setBounds(initialBounds);
return colors;
}
@@ -137,6 +144,13 @@ public final class WallpaperColors implements Parcelable {
* @param bitmap Source where to extract from.
*/
public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) {
+ return fromBitmap(bitmap, false /* computeHints */);
+ }
+
+ /**
+ * @hide
+ */
+ public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap, boolean computeHints) {
if (bitmap == null) {
throw new IllegalArgumentException("Bitmap can't be null");
}
@@ -186,7 +200,7 @@ public final class WallpaperColors implements Parcelable {
}
}
- int hints = calculateDarkHints(bitmap);
+ int hints = computeHints ? calculateDarkHints(bitmap) : 0;
if (shouldRecycle) {
bitmap.recycle();
diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java
index f21746cd..465340f6 100644
--- a/android/app/WallpaperManager.java
+++ b/android/app/WallpaperManager.java
@@ -51,15 +51,13 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadSystemException;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.service.wallpaper.WallpaperService;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -443,6 +441,9 @@ public class WallpaperManager {
synchronized (this) {
mCachedWallpaper = null;
mCachedWallpaperUserId = 0;
+ if (mDefaultWallpaper != null) {
+ mDefaultWallpaper.recycle();
+ }
mDefaultWallpaper = null;
}
}
@@ -914,9 +915,14 @@ public class WallpaperManager {
/**
* Get the primary colors of a wallpaper.
*
- * <p>You can expect null if:
- * • Colors are still being processed by the system.
- * • A live wallpaper doesn't implement {@link WallpaperService.Engine#onComputeColors()}.
+ * <p>This method can return {@code null} when:
+ * <ul>
+ * <li>Colors are still being processed by the system.</li>
+ * <li>The user has chosen to use a live wallpaper: live wallpapers might not
+ * implement
+ * {@link android.service.wallpaper.WallpaperService.Engine#onComputeColors()
+ * WallpaperService.Engine#onComputeColors()}.</li>
+ * </ul>
*
* @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
* {@link #FLAG_LOCK}.
@@ -928,7 +934,7 @@ public class WallpaperManager {
}
/**
- * Get the primary colors of a wallpaper
+ * Get the primary colors of the wallpaper configured in the given user.
* @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or
* {@link #FLAG_LOCK}
* @param userId Owner of the wallpaper.
@@ -1002,7 +1008,7 @@ public class WallpaperManager {
Log.w(TAG, "WallpaperService not running");
throw new RuntimeException(new DeadSystemException());
} else {
- return sGlobals.mService.getWallpaperInfo(UserHandle.myUserId());
+ return sGlobals.mService.getWallpaperInfo(mContext.getUserId());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1150,7 +1156,7 @@ public class WallpaperManager {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
"res:" + resources.getResourceName(resid),
mContext.getOpPackageName(), null, false, result, which, completion,
- UserHandle.myUserId());
+ mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
boolean ok = false;
@@ -1255,7 +1261,7 @@ public class WallpaperManager {
boolean allowBackup, @SetWallpaperFlags int which)
throws IOException {
return setBitmap(fullImage, visibleCropHint, allowBackup, which,
- UserHandle.myUserId());
+ mContext.getUserId());
}
/**
@@ -1329,11 +1335,7 @@ public class WallpaperManager {
private void copyStreamToWallpaperFile(InputStream data, FileOutputStream fos)
throws IOException {
- byte[] buffer = new byte[32768];
- int amt;
- while ((amt=data.read(buffer)) > 0) {
- fos.write(buffer, 0, amt);
- }
+ FileUtils.copy(data, fos);
}
/**
@@ -1406,7 +1408,7 @@ public class WallpaperManager {
try {
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
mContext.getOpPackageName(), visibleCropHint, allowBackup,
- result, which, completion, UserHandle.myUserId());
+ result, which, completion, mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
try {
@@ -1562,11 +1564,13 @@ public class WallpaperManager {
* Specify extra padding that the wallpaper should have outside of the display.
* That is, the given padding supplies additional pixels the wallpaper should extend
* outside of the display itself.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#SET_WALLPAPER_HINTS}.
+ *
* @param padding The number of pixels the wallpaper should extend beyond the display,
* on its left, top, right, and bottom sides.
- * @hide
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER_HINTS)
public void setDisplayPadding(Rect padding) {
try {
@@ -1603,11 +1607,11 @@ public class WallpaperManager {
}
/**
- * Clear the wallpaper.
+ * Reset all wallpaper to the factory default.
*
- * @hide
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#SET_WALLPAPER}.
*/
- @SystemApi
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
public void clearWallpaper() {
clearWallpaper(FLAG_LOCK, mContext.getUserId());
@@ -1643,7 +1647,7 @@ public class WallpaperManager {
@SystemApi
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
public boolean setWallpaperComponent(ComponentName name) {
- return setWallpaperComponent(name, UserHandle.myUserId());
+ return setWallpaperComponent(name, mContext.getUserId());
}
/**
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 085fc79f..46566e79 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -146,6 +146,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
})
public @interface WindowConfig {}
+ /** @hide */
+ public static final int PINNED_WINDOWING_MODE_ELEVATION_IN_DIP = 5;
+
public WindowConfiguration() {
unset();
}
diff --git a/android/app/admin/DeviceAdminInfo.java b/android/app/admin/DeviceAdminInfo.java
index 1de1d2fb..5fbe5b39 100644
--- a/android/app/admin/DeviceAdminInfo.java
+++ b/android/app/admin/DeviceAdminInfo.java
@@ -16,31 +16,31 @@
package android.app.admin;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.content.res.Resources.NotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
import android.util.Xml;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -75,6 +75,10 @@ public final class DeviceAdminInfo implements Parcelable {
*
* <p>To control this policy, the device admin must have a "limit-password"
* tag in the "uses-policies" section of its meta-data.
+ *
+ * <p>This policy is deprecated for use by a device admin. In future releases, it will
+ * only be possible for a device owner or profile owner to enforce constraints on user
+ * passwords.
*/
public static final int USES_POLICY_LIMIT_PASSWORD = 0;
@@ -136,6 +140,9 @@ public final class DeviceAdminInfo implements Parcelable {
*
* <p>To control this policy, the device admin must have an "expire-password"
* tag in the "uses-policies" section of its meta-data.
+ *
+ * <p>This policy is deprecated for use by a device admin. In future releases, it will
+ * only be possible for a device owner or profile owner to enforce password expiry.
*/
public static final int USES_POLICY_EXPIRE_PASSWORD = 6;
@@ -152,6 +159,9 @@ public final class DeviceAdminInfo implements Parcelable {
*
* <p>To control this policy, the device admin must have a "disable-camera"
* tag in the "uses-policies" section of its meta-data.
+ *
+ * <p>This policy is deprecated for use by a device admin. In future releases, it will
+ * only be possible for a device owner or profile owner to disable use of the camera.
*/
public static final int USES_POLICY_DISABLE_CAMERA = 8;
@@ -160,6 +170,10 @@ public final class DeviceAdminInfo implements Parcelable {
*
* <p>To control this policy, the device admin must have a "disable-keyguard-features"
* tag in the "uses-policies" section of its meta-data.
+ *
+ * <p>This policy is deprecated for use by a device admin. In future releases, it will
+ * only be possible for a device owner or profile owner to disable use of keyguard
+ * features.
*/
public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9;
@@ -252,6 +266,12 @@ public final class DeviceAdminInfo implements Parcelable {
*/
int mUsesPolicies;
+ /**
+ * Whether this administrator can be a target in an ownership transfer.
+ *
+ * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
+ */
+ boolean mSupportsTransferOwnership;
/**
* Constructor.
@@ -333,6 +353,12 @@ public final class DeviceAdminInfo implements Parcelable {
+ getComponent() + ": " + policyName);
}
}
+ } else if (tagName.equals("support-transfer-ownership")) {
+ if (parser.next() != XmlPullParser.END_TAG) {
+ throw new XmlPullParserException(
+ "support-transfer-ownership tag must be empty.");
+ }
+ mSupportsTransferOwnership = true;
}
}
} catch (NameNotFoundException e) {
@@ -346,6 +372,7 @@ public final class DeviceAdminInfo implements Parcelable {
DeviceAdminInfo(Parcel source) {
mActivityInfo = ActivityInfo.CREATOR.createFromParcel(source);
mUsesPolicies = source.readInt();
+ mSupportsTransferOwnership = source.readBoolean();
}
/**
@@ -444,6 +471,13 @@ public final class DeviceAdminInfo implements Parcelable {
return sRevKnownPolicies.get(policyIdent).tag;
}
+ /**
+ * Return true if this administrator can be a target in an ownership transfer.
+ */
+ public boolean supportsTransferOwnership() {
+ return mSupportsTransferOwnership;
+ }
+
/** @hide */
public ArrayList<PolicyInfo> getUsedPolicies() {
ArrayList<PolicyInfo> res = new ArrayList<PolicyInfo>();
@@ -488,6 +522,7 @@ public final class DeviceAdminInfo implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
mActivityInfo.writeToParcel(dest, flags);
dest.writeInt(mUsesPolicies);
+ dest.writeBoolean(mSupportsTransferOwnership);
}
/**
diff --git a/android/app/admin/DeviceAdminReceiver.java b/android/app/admin/DeviceAdminReceiver.java
index 28e845a0..1c9477d0 100644
--- a/android/app/admin/DeviceAdminReceiver.java
+++ b/android/app/admin/DeviceAdminReceiver.java
@@ -19,6 +19,8 @@ package android.app.admin;
import android.accounts.AccountManager;
import android.annotation.BroadcastBehavior;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -34,9 +36,6 @@ import android.os.Process;
import android.os.UserHandle;
import android.security.KeyChain;
-import libcore.util.NonNull;
-import libcore.util.Nullable;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -506,31 +505,6 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
public static final String EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE =
"android.app.extra.TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE";
- /**
- * Name under which a device administration component indicates whether it supports transfer of
- * ownership. This meta-data is of type <code>boolean</code>. A value of <code>true</code>
- * allows this administrator to be used as a target administrator for a transfer. If the value
- * is <code>false</code>, ownership cannot be transferred to this administrator. The default
- * value is <code>false</code>.
- * <p>This metadata is used to avoid ownership transfer migration to an administrator with a
- * version which does not yet support it.
- * <p>Usage:
- * <pre>
- * &lt;receiver name="..." android:permission="android.permission.BIND_DEVICE_ADMIN"&gt;
- * &lt;meta-data
- * android:name="android.app.device_admin"
- * android:resource="@xml/..." /&gt;
- * &lt;meta-data
- * android:name="android.app.support_transfer_ownership"
- * android:value="true" /&gt;
- * &lt;/receiver&gt;
- * </pre>
- *
- * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
- */
- public static final String SUPPORT_TRANSFER_OWNERSHIP_META_DATA =
- "android.app.support_transfer_ownership";
-
private DevicePolicyManager mManager;
private ComponentName mWho;
@@ -928,29 +902,29 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
int networkLogsCount) {
}
- /**
- * Called when a user or profile is created.
- *
- * <p>This callback is only applicable to device owners.
- *
- * @param context The running context as per {@link #onReceive}.
- * @param intent The received intent as per {@link #onReceive}.
- * @param newUser The {@link UserHandle} of the user that has just been added.
- */
- public void onUserAdded(Context context, Intent intent, UserHandle newUser) {
- }
-
- /**
- * Called when a user or profile is removed.
- *
- * <p>This callback is only applicable to device owners.
- *
- * @param context The running context as per {@link #onReceive}.
- * @param intent The received intent as per {@link #onReceive}.
- * @param removedUser The {@link UserHandle} of the user that has just been removed.
- */
- public void onUserRemoved(Context context, Intent intent, UserHandle removedUser) {
- }
+ /**
+ * Called when a user or profile is created.
+ *
+ * <p>This callback is only applicable to device owners.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @param newUser The {@link UserHandle} of the user that has just been added.
+ */
+ public void onUserAdded(Context context, Intent intent, @NonNull UserHandle newUser) {
+ }
+
+ /**
+ * Called when a user or profile is removed.
+ *
+ * <p>This callback is only applicable to device owners.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @param removedUser The {@link UserHandle} of the user that has just been removed.
+ */
+ public void onUserRemoved(Context context, Intent intent, @NonNull UserHandle removedUser) {
+ }
/**
* Called when a user or profile is started.
@@ -961,7 +935,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* @param intent The received intent as per {@link #onReceive}.
* @param startedUser The {@link UserHandle} of the user that has just been started.
*/
- public void onUserStarted(Context context, Intent intent, UserHandle startedUser) {
+ public void onUserStarted(Context context, Intent intent, @NonNull UserHandle startedUser) {
}
/**
@@ -973,7 +947,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* @param intent The received intent as per {@link #onReceive}.
* @param stoppedUser The {@link UserHandle} of the user that has just been stopped.
*/
- public void onUserStopped(Context context, Intent intent, UserHandle stoppedUser) {
+ public void onUserStopped(Context context, Intent intent, @NonNull UserHandle stoppedUser) {
}
/**
@@ -985,7 +959,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* @param intent The received intent as per {@link #onReceive}.
* @param switchedUser The {@link UserHandle} of the user that has just been switched to.
*/
- public void onUserSwitched(Context context, Intent intent, UserHandle switchedUser) {
+ public void onUserSwitched(Context context, Intent intent, @NonNull UserHandle switchedUser) {
}
/**
diff --git a/android/app/admin/DevicePolicyCache.java b/android/app/admin/DevicePolicyCache.java
new file mode 100644
index 00000000..fbb8ddfe
--- /dev/null
+++ b/android/app/admin/DevicePolicyCache.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.admin;
+
+import android.annotation.UserIdInt;
+
+import com.android.server.LocalServices;
+
+/**
+ * Stores a copy of the set of device policies maintained by {@link DevicePolicyManager} that
+ * can be accessed from any place without risking dead locks.
+ *
+ * @hide
+ */
+public abstract class DevicePolicyCache {
+ protected DevicePolicyCache() {
+ }
+
+ /**
+ * @return the instance.
+ */
+ public static DevicePolicyCache getInstance() {
+ final DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ return (dpmi != null) ? dpmi.getDevicePolicyCache() : EmptyDevicePolicyCache.INSTANCE;
+ }
+
+ /**
+ * See {@link DevicePolicyManager#getScreenCaptureDisabled}
+ */
+ public abstract boolean getScreenCaptureDisabled(@UserIdInt int userHandle);
+
+ /**
+ * Empty implementation.
+ */
+ private static class EmptyDevicePolicyCache extends DevicePolicyCache {
+ private static final EmptyDevicePolicyCache INSTANCE = new EmptyDevicePolicyCache();
+
+ @Override
+ public boolean getScreenCaptureDisabled(int userHandle) {
+ return false;
+ }
+ }
+}
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 8f76032b..b64aae52 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -21,9 +21,11 @@ import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -54,9 +56,13 @@ import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManager.UserOperationException;
+import android.os.UserManager.UserOperationResult;
import android.provider.ContactsContract.Directory;
+import android.provider.Settings;
import android.security.AttestedKeyPair;
import android.security.Credentials;
import android.security.KeyChain;
@@ -116,7 +122,9 @@ import java.util.concurrent.Executor;
* guide. </div>
*/
@SystemService(Context.DEVICE_POLICY_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_DEVICE_ADMIN)
public class DevicePolicyManager {
+
private static String TAG = "DevicePolicyManager";
private final Context mContext;
@@ -140,7 +148,7 @@ public class DevicePolicyManager {
/** @hide test will override it. */
@VisibleForTesting
protected int myUserId() {
- return UserHandle.myUserId();
+ return mContext.getUserId();
}
/**
@@ -1605,8 +1613,6 @@ public class DevicePolicyManager {
* <li>keyguard
* </ul>
*
- * This is the default configuration for LockTask.
- *
* @see #setLockTaskFeatures(ComponentName, int)
*/
public static final int LOCK_TASK_FEATURE_NONE = 0;
@@ -1624,7 +1630,10 @@ public class DevicePolicyManager {
/**
* Enable notifications during LockTask mode. This includes notification icons on the status
* bar, heads-up notifications, and the expandable notification shade. Note that the Quick
- * Settings panel will still be disabled.
+ * Settings panel remains disabled. This feature flag can only be used in combination with
+ * {@link #LOCK_TASK_FEATURE_HOME}. {@link #setLockTaskFeatures(ComponentName, int)}
+ * throws an {@link IllegalArgumentException} if this feature flag is defined without
+ * {@link #LOCK_TASK_FEATURE_HOME}.
*
* @see #setLockTaskFeatures(ComponentName, int)
*/
@@ -1642,17 +1651,24 @@ public class DevicePolicyManager {
public static final int LOCK_TASK_FEATURE_HOME = 1 << 2;
/**
- * Enable the Recents button and the Recents screen during LockTask mode.
+ * Enable the Overview button and the Overview screen during LockTask mode. This feature flag
+ * can only be used in combination with {@link #LOCK_TASK_FEATURE_HOME}, and
+ * {@link #setLockTaskFeatures(ComponentName, int)} will throw an
+ * {@link IllegalArgumentException} if this feature flag is defined without
+ * {@link #LOCK_TASK_FEATURE_HOME}.
*
* @see #setLockTaskFeatures(ComponentName, int)
*/
- public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3;
+ public static final int LOCK_TASK_FEATURE_OVERVIEW = 1 << 3;
/**
* Enable the global actions dialog during LockTask mode. This is the dialog that shows up when
* the user long-presses the power button, for example. Note that the user may not be able to
* power off the device if this flag is not set.
*
+ * <p>This flag is enabled by default until {@link #setLockTaskFeatures(ComponentName, int)} is
+ * called for the first time.
+ *
* @see #setLockTaskFeatures(ComponentName, int)
*/
public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4;
@@ -1678,7 +1694,7 @@ public class DevicePolicyManager {
LOCK_TASK_FEATURE_SYSTEM_INFO,
LOCK_TASK_FEATURE_NOTIFICATIONS,
LOCK_TASK_FEATURE_HOME,
- LOCK_TASK_FEATURE_RECENTS,
+ LOCK_TASK_FEATURE_OVERVIEW,
LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
LOCK_TASK_FEATURE_KEYGUARD
})
@@ -1736,6 +1752,25 @@ public class DevicePolicyManager {
public static final int ID_TYPE_MEID = 8;
/**
+ * Specifies that the calling app should be granted access to the installed credentials
+ * immediately. Otherwise, access to the credentials will be gated by user approval.
+ * For use with {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)}
+ *
+ * @see #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)
+ */
+ public static final int INSTALLKEY_REQUEST_CREDENTIALS_ACCESS = 1;
+
+ /**
+ * Specifies that a user can select the key via the Certificate Selection prompt.
+ * If this flag is not set when calling {@link #installKeyPair}, the key can only be granted
+ * access by implementing {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * For use with {@link #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)}
+ *
+ * @see #installKeyPair(ComponentName, PrivateKey, Certificate[], String, int)
+ */
+ public static final int INSTALLKEY_SET_USER_SELECTABLE = 2;
+
+ /**
* Broadcast action: sent when the profile owner is set, changed or cleared.
*
* This broadcast is sent only to the user managed by the new profile owner.
@@ -2712,110 +2747,6 @@ 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
@@ -2850,8 +2781,8 @@ 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.
+ * <strong>Note</strong>: 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.
@@ -3444,9 +3375,6 @@ public class DevicePolicyManager {
/**
* Flag for {@link #wipeData(int)}: also erase the device's eUICC data.
- *
- * TODO(b/35851809): make this public.
- * @hide
*/
public static final int WIPE_EUICC = 0x0004;
@@ -3490,18 +3418,18 @@ public class DevicePolicyManager {
* that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
* @throws IllegalArgumentException if the input reason string is null or empty.
*/
- public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
- throwIfParentInstance("wipeDataWithReason");
+ public void wipeData(int flags, @NonNull CharSequence reason) {
+ throwIfParentInstance("wipeData");
Preconditions.checkNotNull(reason, "CharSequence is null");
wipeDataInternal(flags, reason.toString());
}
/**
* Internal function for both {@link #wipeData(int)} and
- * {@link #wipeDataWithReason(int, CharSequence)} to call.
+ * {@link #wipeData(int, CharSequence)} to call.
*
* @see #wipeData(int)
- * @see #wipeDataWithReason(int, CharSequence)
+ * @see #wipeData(int, CharSequence)
* @hide
*/
private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
@@ -3704,7 +3632,9 @@ public class DevicePolicyManager {
public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
/**
- * Disable all keyguard widgets. Has no effect.
+ * Disable all keyguard widgets. Has no effect starting from
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP} since keyguard widget is only supported
+ * on Android versions lower than 5.0.
*/
public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0;
@@ -3724,13 +3654,15 @@ public class DevicePolicyManager {
public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 1 << 3;
/**
- * Ignore trust agent state on secure keyguard screens
- * (e.g. PIN/Pattern/Password).
+ * Disable trust agents on secure keyguard screens (e.g. PIN/Pattern/Password).
+ * By setting this flag alone, all trust agents are disabled. If the admin then wants to
+ * whitelist specific features of some trust agent, {@link #setTrustAgentConfiguration} can be
+ * used in conjuction to set trust-agent-specific configurations.
*/
public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4;
/**
- * Disable fingerprint sensor on keyguard secure screens (e.g. PIN/Pattern/Password).
+ * Disable fingerprint authentication on keyguard secure screens (e.g. PIN/Pattern/Password).
*/
public static final int KEYGUARD_DISABLE_FINGERPRINT = 1 << 5;
@@ -3740,6 +3672,25 @@ public class DevicePolicyManager {
public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 1 << 6;
/**
+ * Disable face authentication on keyguard secure screens (e.g. PIN/Pattern/Password).
+ */
+ public static final int KEYGUARD_DISABLE_FACE = 1 << 7;
+
+ /**
+ * Disable iris authentication on keyguard secure screens (e.g. PIN/Pattern/Password).
+ */
+ public static final int KEYGUARD_DISABLE_IRIS = 1 << 8;
+
+ /**
+ * Disable all biometric authentication on keyguard secure screens (e.g. PIN/Pattern/Password).
+ */
+ public static final int KEYGUARD_DISABLE_BIOMETRICS =
+ DevicePolicyManager.KEYGUARD_DISABLE_FACE
+ | DevicePolicyManager.KEYGUARD_DISABLE_IRIS
+ | DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
+
+
+ /**
* Disable all current and future keyguard customizations.
*/
public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
@@ -3757,7 +3708,7 @@ public class DevicePolicyManager {
/**
* Called by an application that is administering the device to request that the storage system
- * be encrypted.
+ * be encrypted. Does nothing if the caller is on a secondary user or a managed profile.
* <p>
* When multiple device administrators attempt to control device encryption, the most secure,
* supported setting will always be used. If any device administrator requests device
@@ -3779,10 +3730,13 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param encrypt true to request encryption, false to release any previous request
- * @return the new request status (for all active admins) - will be one of
- * {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE}, or
- * {@link #ENCRYPTION_STATUS_ACTIVE}. This is the value of the requests; Use
- * {@link #getStorageEncryptionStatus()} to query the actual device state.
+ * @return the new total request status (for all active admins), or {@link
+ * DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} if called for a non-system user.
+ * Will be one of {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link
+ * #ENCRYPTION_STATUS_INACTIVE}, or {@link #ENCRYPTION_STATUS_ACTIVE}. This is the value
+ * of the requests; use {@link #getStorageEncryptionStatus()} to query the actual device
+ * state.
+ *
* @throws SecurityException if {@code admin} is not an active administrator or does not use
* {@link DeviceAdminInfo#USES_ENCRYPTED_STORAGE}
*/
@@ -4088,7 +4042,11 @@ public class DevicePolicyManager {
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
- return installKeyPair(admin, privKey, certs, alias, requestAccess, true);
+ int flags = INSTALLKEY_SET_USER_SELECTABLE;
+ if (requestAccess) {
+ flags |= INSTALLKEY_REQUEST_CREDENTIALS_ACCESS;
+ }
+ return installKeyPair(admin, privKey, certs, alias, flags);
}
/**
@@ -4112,13 +4070,9 @@ public class DevicePolicyManager {
* {@link android.security.KeyChain#getCertificateChain}.
* @param alias The private key alias under which to install the certificate. If a certificate
* with that alias already exists, it will be overwritten.
- * @param requestAccess {@code true} to request that the calling app be granted access to the
- * credentials immediately. Otherwise, access to the credentials will be gated by user
- * approval.
- * @param isUserSelectable {@code true} to indicate that a user can select this key via the
- * Certificate Selection prompt, false to indicate that this key can only be granted
- * access by implementing
- * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * @param flags Flags to request that the calling app be granted access to the credentials
+ * and set the key to be user-selectable. See {@link #INSTALLKEY_SET_USER_SELECTABLE} and
+ * {@link #INSTALLKEY_REQUEST_CREDENTIALS_ACCESS}.
* @return {@code true} if the keys were installed, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
@@ -4127,9 +4081,12 @@ public class DevicePolicyManager {
* @see #DELEGATION_CERT_INSTALL
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
- @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess,
- boolean isUserSelectable) {
+ @NonNull Certificate[] certs, @NonNull String alias, int flags) {
throwIfParentInstance("installKeyPair");
+ boolean requestAccess = (flags & INSTALLKEY_REQUEST_CREDENTIALS_ACCESS)
+ == INSTALLKEY_REQUEST_CREDENTIALS_ACCESS;
+ boolean isUserSelectable = (flags & INSTALLKEY_SET_USER_SELECTABLE)
+ == INSTALLKEY_SET_USER_SELECTABLE;
try {
final byte[] pemCert = Credentials.convertToPem(certs[0]);
byte[] pemChain = null;
@@ -4208,6 +4165,8 @@ public class DevicePolicyManager {
* algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec}
* or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the
* {@code keySpec} does not contain an attestation challenge.
+ * @throws UnsupportedOperationException if Device ID attestation was requested but the
+ * underlying hardware does not support it.
* @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])
*/
public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin,
@@ -5511,7 +5470,7 @@ public class DevicePolicyManager {
@SystemApi
public @Nullable ComponentName getProfileOwner() throws IllegalArgumentException {
throwIfParentInstance("getProfileOwner");
- return getProfileOwnerAsUser(Process.myUserHandle().getIdentifier());
+ return getProfileOwnerAsUser(mContext.getUserId());
}
/**
@@ -5539,7 +5498,7 @@ public class DevicePolicyManager {
public @Nullable String getProfileOwnerName() throws IllegalArgumentException {
if (mService != null) {
try {
- return mService.getProfileOwnerName(Process.myUserHandle().getIdentifier());
+ return mService.getProfileOwnerName(mContext.getUserId());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -5569,10 +5528,13 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile owner or device owner to add a default intent handler activity for
- * intents that match a certain intent filter. This activity will remain the default intent
- * handler even if the set of potential event handlers for the intent filter changes and if the
- * intent preferences are reset.
+ * Called by a profile owner or device owner to set a default activity that the system selects
+ * to handle intents that match the given {@link IntentFilter}. This activity will remain the
+ * default intent handler even if the set of potential event handlers for the intent filter
+ * changes and if the intent preferences are reset.
+ * <p>
+ * Note that the caller should still declare the activity in the manifest, the API just sets
+ * the activity to be the default one to handle the given intent filter.
* <p>
* The default disambiguation mechanism takes over if the activity is not installed (anymore).
* When the activity is (re)installed, it is automatically reset as default intent handler for
@@ -5622,6 +5584,29 @@ public class DevicePolicyManager {
}
/**
+ * Called by a device owner to set the default SMS application.
+ * <p>
+ * The calling device admin must be a device owner. If it is not, a security exception will be
+ * thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName The name of the package to set as the default SMS application.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @hide
+ */
+ public void setDefaultSmsApplication(@NonNull ComponentName admin, String packageName) {
+ throwIfParentInstance("setDefaultSmsApplication");
+ if (mService != null) {
+ try {
+ mService.setDefaultSmsApplication(admin, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by a profile owner or device owner to grant permission to a package to manage
* application restrictions for the calling user via {@link #setApplicationRestrictions} and
* {@link #getApplicationRestrictions}.
@@ -5760,11 +5745,20 @@ public class DevicePolicyManager {
}
/**
- * Sets a list of configuration features to enable for a TrustAgent component. This is meant to
+ * Sets a list of configuration features to enable for a trust agent component. This is meant to
* be used in conjunction with {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, which disables all trust
* agents but those enabled by this function call. If flag
* {@link #KEYGUARD_DISABLE_TRUST_AGENTS} is not set, then this call has no effect.
* <p>
+ * For any specific trust agent, whether it is disabled or not depends on the aggregated state
+ * of each admin's {@link #KEYGUARD_DISABLE_TRUST_AGENTS} setting and its trust agent
+ * configuration as set by this function call. In particular: if any admin sets
+ * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} and does not additionally set any
+ * trust agent configuration, the trust agent is disabled completely. Otherwise, the trust agent
+ * will receive the list of configurations from all admins who set
+ * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} and aggregate the configurations to determine its
+ * behavior. The exact meaning of aggregation is trust-agent-specific.
+ * <p>
* The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} to be able to call this method;
* if not, a security exception will be thrown.
@@ -5774,17 +5768,10 @@ public class DevicePolicyManager {
* the parent profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param target Component name of the agent to be enabled.
- * @param configuration TrustAgent-specific feature bundle. If null for any admin, agent will be
- * strictly disabled according to the state of the
- * {@link #KEYGUARD_DISABLE_TRUST_AGENTS} flag.
- * <p>
- * If {@link #KEYGUARD_DISABLE_TRUST_AGENTS} is set and options is not null for all
- * admins, then it's up to the TrustAgent itself to aggregate the values from all
- * device admins.
- * <p>
- * Consult documentation for the specific TrustAgent to determine legal options
- * parameters.
+ * @param target Component name of the agent to be configured.
+ * @param configuration Trust-agent-specific feature configuration bundle. Please consult
+ * documentation of the specific trust agent to determine the interpretation of this
+ * bundle.
* @throws SecurityException if {@code admin} is not an active administrator or does not use
* {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
*/
@@ -5845,7 +5832,7 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param disabled If true caller-Id information in the managed profile is not displayed.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a profile owner.
*/
public void setCrossProfileCallerIdDisabled(@NonNull ComponentName admin, boolean disabled) {
throwIfParentInstance("setCrossProfileCallerIdDisabled");
@@ -5866,7 +5853,7 @@ public class DevicePolicyManager {
* thrown.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a profile owner.
*/
public boolean getCrossProfileCallerIdDisabled(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfileCallerIdDisabled");
@@ -5906,7 +5893,7 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param disabled If true contacts search in the managed profile is not displayed.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a profile owner.
*/
public void setCrossProfileContactsSearchDisabled(@NonNull ComponentName admin,
boolean disabled) {
@@ -5928,7 +5915,7 @@ public class DevicePolicyManager {
* thrown.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a profile owner.
*/
public boolean getCrossProfileContactsSearchDisabled(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfileContactsSearchDisabled");
@@ -5999,7 +5986,7 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param disabled If true, bluetooth devices cannot access enterprise contacts.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a profile owner.
*/
public void setBluetoothContactSharingDisabled(@NonNull ComponentName admin, boolean disabled) {
throwIfParentInstance("setBluetoothContactSharingDisabled");
@@ -6022,7 +6009,7 @@ public class DevicePolicyManager {
* This API works on managed profile only.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a profile owner.
*/
public boolean getBluetoothContactSharingDisabled(@NonNull ComponentName admin) {
throwIfParentInstance("getBluetoothContactSharingDisabled");
@@ -6092,7 +6079,7 @@ public class DevicePolicyManager {
* {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @throws SecurityException if {@code admin} is not a device or profile owner.
+ * @throws SecurityException if {@code admin} is not a profile owner.
*/
public void clearCrossProfileIntentFilters(@NonNull ComponentName admin) {
throwIfParentInstance("clearCrossProfileIntentFilters");
@@ -6106,21 +6093,22 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile or device owner to set the permitted accessibility services. When set by
+ * Called by a profile or device owner to set the permitted
+ * {@link android.accessibilityservice.AccessibilityService}. When set by
* a device owner or profile owner the restriction applies to all profiles of the user the
- * device owner or profile owner is an admin for. By default the user can use any accessiblity
- * service. When zero or more packages have been added, accessiblity services that are not in
+ * device owner or profile owner is an admin for. By default, the user can use any accessibility
+ * service. When zero or more packages have been added, accessibility services that are not in
* the list and not part of the system can not be enabled by the user.
* <p>
* Calling with a null value for the list disables the restriction so that all services can be
- * used, calling with an empty list only allows the builtin system's services.
+ * used, calling with an empty list only allows the built-in system services. Any non-system
+ * accessibility service that's currently enabled must be included in the list.
* <p>
* System accessibility services are always available to the user the list can't modify this.
- *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageNames List of accessibility service package names.
- * @return true if setting the restriction succeeded. It fail if there is one or more non-system
- * accessibility services enabled, that are not in the list.
+ * @return {@code true} if the operation succeeded, or {@code false} if the list didn't
+ * contain every enabled non-system accessibility service.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public boolean setPermittedAccessibilityServices(@NonNull ComponentName admin,
@@ -6209,10 +6197,11 @@ public class DevicePolicyManager {
/**
* Called by a profile or device owner to set the permitted input methods services. When set by
* a device owner or profile owner the restriction applies to all profiles of the user the
- * device owner or profile owner is an admin for. By default the user can use any input method.
+ * device owner or profile owner is an admin for. By default, the user can use any input method.
* When zero or more packages have been added, input method that are not in the list and not
* part of the system can not be enabled by the user. This method will fail if it is called for
- * a admin that is not for the foreground user or a profile of the foreground user.
+ * a admin that is not for the foreground user or a profile of the foreground user. Any
+ * non-system input method service that's currently enabled must be included in the list.
* <p>
* Calling with a null value for the list disables the restriction so that all input methods can
* be used, calling with an empty list disables all but the system's own input methods.
@@ -6221,8 +6210,8 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageNames List of input method package names.
- * @return true if setting the restriction succeeded. It will fail if there are one or more
- * non-system input methods currently enabled that are not in the packageNames list.
+ * @return {@code true} if the operation succeeded, or {@code false} if the list didn't
+ * contain every enabled non-system input method service.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public boolean setPermittedInputMethods(
@@ -6295,6 +6284,7 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public @Nullable List<String> getPermittedInputMethodsForCurrentUser() {
throwIfParentInstance("getPermittedInputMethodsForCurrentUser");
if (mService != null) {
@@ -6544,6 +6534,9 @@ public class DevicePolicyManager {
* <p>
* If the adminExtras are not null, they will be stored on the device until the user is started
* for the first time. Then the extras will be passed to the admin when onEnable is called.
+ * <p>From {@link android.os.Build.VERSION_CODES#P} onwards, if targeting
+ * {@link android.os.Build.VERSION_CODES#P}, throws {@link UserOperationException} instead of
+ * returning {@code null} on failure.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param name The user's name.
@@ -6558,6 +6551,9 @@ public class DevicePolicyManager {
* @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
* user could not be created.
* @throws SecurityException if {@code admin} is not a device owner.
+ * @throws UserOperationException if the user could not be created and the calling app is
+ * targeting {@link android.os.Build.VERSION_CODES#P} and running on
+ * {@link android.os.Build.VERSION_CODES#P}.
*/
public @Nullable UserHandle createAndManageUser(@NonNull ComponentName admin,
@NonNull String name,
@@ -6566,6 +6562,8 @@ public class DevicePolicyManager {
throwIfParentInstance("createAndManageUser");
try {
return mService.createAndManageUser(admin, name, profileOwner, adminExtras, flags);
+ } catch (ServiceSpecificException e) {
+ throw new UserOperationException(e.getMessage(), e.errorCode);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -6612,12 +6610,16 @@ public class DevicePolicyManager {
* Called by a device owner to start the specified secondary user in background.
*
* @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 started, {@code false} otherwise.
+ * @param userHandle the user to be started in background.
+ * @return one of the following result codes:
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_MAX_RUNNING_USERS},
* @throws SecurityException if {@code admin} is not a device owner.
* @see #getSecondaryUsers(ComponentName)
*/
- public boolean startUserInBackground(
+ public @UserOperationResult int startUserInBackground(
@NonNull ComponentName admin, @NonNull UserHandle userHandle) {
throwIfParentInstance("startUserInBackground");
try {
@@ -6632,11 +6634,16 @@ public class DevicePolicyManager {
*
* @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.
+ * @return one of the following result codes:
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER}
* @throws SecurityException if {@code admin} is not a device owner.
* @see #getSecondaryUsers(ComponentName)
*/
- public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) {
+ public @UserOperationResult int stopUser(
+ @NonNull ComponentName admin, @NonNull UserHandle userHandle) {
throwIfParentInstance("stopUser");
try {
return mService.stopUser(admin, userHandle);
@@ -6650,11 +6657,15 @@ public class DevicePolicyManager {
* calling user and switch back to primary.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @return {@code true} if the exit was successful, {@code false} otherwise.
+ * @return one of the following result codes:
+ * {@link UserManager#USER_OPERATION_ERROR_UNKNOWN},
+ * {@link UserManager#USER_OPERATION_SUCCESS},
+ * {@link UserManager#USER_OPERATION_ERROR_MANAGED_PROFILE},
+ * {@link UserManager#USER_OPERATION_ERROR_CURRENT_USER}
* @throws SecurityException if {@code admin} is not a profile owner affiliated with the device.
* @see #getSecondaryUsers(ComponentName)
*/
- public boolean logoutUser(@NonNull ComponentName admin) {
+ public @UserOperationResult int logoutUser(@NonNull ComponentName admin) {
throwIfParentInstance("logoutUser");
try {
return mService.logoutUser(admin);
@@ -7101,30 +7112,24 @@ public class DevicePolicyManager {
}
/**
- * Sets which system features to enable for LockTask mode.
- * <p>
- * Feature flags set through this method will only take effect for the duration when the device
- * is in LockTask mode. If this method is not called, none of the features listed here will be
- * enabled.
- * <p>
- * This function can only be called by the device owner, a profile owner of an affiliated user
- * or profile, or the profile owner when no device owner is set. See {@link #isAffiliatedUser}.
- * Any features set via this method will be cleared if the user becomes unaffiliated.
+ * Sets which system features are enabled when the device runs in lock task mode. This method
+ * doesn't affect the features when lock task mode is inactive. Any system features not included
+ * in {@code flags} are implicitly disabled when calling this method. By default, only
+ * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS} is enabled—all the other features are disabled. To
+ * disable the global actions dialog, call this method omitting
+ * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}.
+ *
+ * <p>This method can only be called by the device owner, a profile owner of an affiliated
+ * user or profile, or the profile owner when no device owner is set. See
+ * {@link #isAffiliatedUser}.
+ * Any features set using this method are cleared if the user becomes unaffiliated.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param flags Bitfield of feature flags:
- * {@link #LOCK_TASK_FEATURE_NONE} (default),
- * {@link #LOCK_TASK_FEATURE_SYSTEM_INFO},
- * {@link #LOCK_TASK_FEATURE_NOTIFICATIONS},
- * {@link #LOCK_TASK_FEATURE_HOME},
- * {@link #LOCK_TASK_FEATURE_RECENTS},
- * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
- * {@link #LOCK_TASK_FEATURE_KEYGUARD}
+ * @param flags The system features enabled during lock task mode.
* @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
* affiliated user or profile, or the profile owner when no device owner is set.
* @see #isAffiliatedUser
- * @throws SecurityException if {@code admin} is not the device owner or the profile owner.
- */
+ **/
public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
throwIfParentInstance("setLockTaskFeatures");
if (mService != null) {
@@ -7207,12 +7212,22 @@ public class DevicePolicyManager {
}
}
+ /** @hide */
+ @StringDef({
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS,
+ Settings.System.SCREEN_OFF_TIMEOUT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SystemSettingsWhitelist {}
+
/**
- * 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.
+ * Called by a device or profile 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:
+ * The settings that can be updated by a device owner or profile owner of secondary user with
+ * this method are:
* <ul>
* <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS}</li>
* <li>{@link android.provider.Settings.System#SCREEN_BRIGHTNESS_MODE}</li>
@@ -7224,10 +7239,10 @@ public class DevicePolicyManager {
* @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.
+ * @throws SecurityException if {@code admin} is not a device or profile owner.
*/
- public void setSystemSetting(@NonNull ComponentName admin, @NonNull String setting,
- String value) {
+ public void setSystemSetting(@NonNull ComponentName admin,
+ @NonNull @SystemSettingsWhitelist String setting, String value) {
throwIfParentInstance("setSystemSetting");
if (mService != null) {
try {
@@ -7540,13 +7555,28 @@ public class DevicePolicyManager {
/**
* Called by device owners to set a local system update policy. When a new policy is set,
* {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcasted.
+ * <p>
+ * If the supplied system update policy has freeze periods set but the freeze periods do not
+ * meet 90-day maximum length or 60-day minimum separation requirement set out in
+ * {@link SystemUpdatePolicy#setFreezePeriods},
+ * {@link SystemUpdatePolicy.ValidationFailedException} will the thrown. Note that the system
+ * keeps a record of freeze periods the device experienced previously, and combines them with
+ * the new freeze periods to be set when checking the maximum freeze length and minimum freeze
+ * separation constraints. As a result, freeze periods that passed validation during
+ * {@link SystemUpdatePolicy#setFreezePeriods} might fail the additional checks here due to
+ * the freeze period history. If this is causing issues during development,
+ * {@code adb shell dpm clear-freeze-period-record} can be used to clear the record.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. All
* components in the device owner package can set system update policies and the most
* recent policy takes effect.
* @param policy the new policy, or {@code null} to clear the current policy.
* @throws SecurityException if {@code admin} is not a device owner.
+ * @throws IllegalArgumentException if the policy type or maintenance window is not valid.
+ * @throws SystemUpdatePolicy.ValidationFailedException if the policy's freeze period does not
+ * meet the requirement.
* @see SystemUpdatePolicy
+ * @see SystemUpdatePolicy#setFreezePeriods(List)
*/
public void setSystemUpdatePolicy(@NonNull ComponentName admin, SystemUpdatePolicy policy) {
throwIfParentInstance("setSystemUpdatePolicy");
@@ -7577,6 +7607,23 @@ public class DevicePolicyManager {
}
/**
+ * Reset record of previous system update freeze period the device went through.
+ * Only callable by ADB.
+ * @hide
+ */
+ public void clearSystemUpdatePolicyFreezePeriodRecord() {
+ throwIfParentInstance("clearSystemUpdatePolicyFreezePeriodRecord");
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.clearSystemUpdatePolicyFreezePeriodRecord();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called by a device owner or profile owner of secondary users that is affiliated with the
* device to disable the keyguard altogether.
* <p>
@@ -7759,11 +7806,14 @@ public class DevicePolicyManager {
* {@link #PERMISSION_GRANT_STATE_DEFAULT default} in which a user can manage it through the UI,
* {@link #PERMISSION_GRANT_STATE_DENIED denied}, in which the permission is denied and the user
* cannot manage it through the UI, and {@link #PERMISSION_GRANT_STATE_GRANTED granted} in which
- * the permission is granted and the user cannot manage it through the UI. This might affect all
- * permissions in a group that the runtime permission belongs to. This method can only be called
- * by a profile owner, device owner, or a delegate given the
+ * the permission is granted and the user cannot manage it through the UI. This method can only
+ * be called by a profile owner, device owner, or a delegate given the
* {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
* <p/>
+ * Note that user cannot manage other permissions in the affected group through the UI
+ * either and their granted state will be kept as the current value. Thus, it's recommended that
+ * you set the grant state of all the permissions in the affected group.
+ * <p/>
* Setting the grant state to {@link #PERMISSION_GRANT_STATE_DEFAULT default} does not revoke
* the permission. It retains the previous grant, if any.
* <p/>
@@ -8214,6 +8264,22 @@ public class DevicePolicyManager {
}
/**
+ * Forces a batch of security logs to be fetched from logd and makes it available for DPC.
+ * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0.
+ * @hide
+ */
+ public long forceSecurityLogs() {
+ if (mService == null) {
+ return 0;
+ }
+ try {
+ return mService.forceSecurityLogs();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called by the system to obtain a {@link DevicePolicyManager} whose calls act on the parent
* profile.
*
@@ -8230,19 +8296,19 @@ public class DevicePolicyManager {
}
/**
- * Called by a device or profile owner to restrict packages from accessing metered data.
+ * Called by a device or profile owner to restrict packages from using metered data.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
* @param packageNames the list of package names to be restricted.
* @return a list of package names which could not be restricted.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
- public @NonNull List<String> setMeteredDataDisabled(@NonNull ComponentName admin,
+ public @NonNull List<String> setMeteredDataDisabledPackages(@NonNull ComponentName admin,
@NonNull List<String> packageNames) {
throwIfParentInstance("setMeteredDataDisabled");
if (mService != null) {
try {
- return mService.setMeteredDataDisabled(admin, packageNames);
+ return mService.setMeteredDataDisabledPackages(admin, packageNames);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -8252,17 +8318,17 @@ public class DevicePolicyManager {
/**
* Called by a device or profile owner to retrieve the list of packages which are restricted
- * by the admin from accessing metered data.
+ * by the admin from using metered data.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
* @return the list of restricted package names.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
- public @NonNull List<String> getMeteredDataDisabled(@NonNull ComponentName admin) {
+ public @NonNull List<String> getMeteredDataDisabledPackages(@NonNull ComponentName admin) {
throwIfParentInstance("getMeteredDataDisabled");
if (mService != null) {
try {
- return mService.getMeteredDataDisabled(admin);
+ return mService.getMeteredDataDisabledPackages(admin);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -8271,6 +8337,30 @@ public class DevicePolicyManager {
}
/**
+ * Called by the system to check if a package is restricted from using metered data
+ * by {@param admin}.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName the package whose restricted status is needed.
+ * @param userId the user to which {@param packageName} belongs.
+ * @return {@code true} if the package is restricted by admin, otherwise {@code false}
+ * @throws SecurityException if the caller doesn't run with {@link Process#SYSTEM_UID}
+ * @hide
+ */
+ public boolean isMeteredDataDisabledPackageForUser(@NonNull ComponentName admin,
+ String packageName, @UserIdInt int userId) {
+ throwIfParentInstance("getMeteredDataDisabledForUser");
+ if (mService != null) {
+ try {
+ return mService.isMeteredDataDisabledPackageForUser(admin, packageName, userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Called by device owners to retrieve device logs from before the device's last reboot.
* <p>
* <strong> This API is not supported on all devices. Calling this API on unsupported devices
@@ -8460,6 +8550,7 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
@UserProvisioningState
public int getUserProvisioningState() {
throwIfParentInstance("getUserProvisioningState");
@@ -8604,6 +8695,7 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public boolean isDeviceProvisioned() {
try {
return mService.isDeviceProvisioned();
@@ -8727,14 +8819,20 @@ public class DevicePolicyManager {
* <p>If backups were disabled and a non-null backup transport {@link ComponentName} is
* specified, backups will be enabled.
*
+ * <p>NOTE: The method shouldn't be called on the main thread.
+ *
* @param admin admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param backupTransportComponent The backup transport layer to be used for mandatory backups.
+ * @return {@code true} if the backup transport was successfully set; {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner.
*/
- public void setMandatoryBackupTransport(
- @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) {
+ @WorkerThread
+ public boolean setMandatoryBackupTransport(
+ @NonNull ComponentName admin,
+ @Nullable ComponentName backupTransportComponent) {
+ throwIfParentInstance("setMandatoryBackupTransport");
try {
- mService.setMandatoryBackupTransport(admin, backupTransportComponent);
+ return mService.setMandatoryBackupTransport(admin, backupTransportComponent);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -8748,6 +8846,7 @@ public class DevicePolicyManager {
* mandatory or {@code null} if backups are not mandatory.
*/
public ComponentName getMandatoryBackupTransport() {
+ throwIfParentInstance("getMandatoryBackupTransport");
try {
return mService.getMandatoryBackupTransport();
} catch (RemoteException re) {
@@ -9045,15 +9144,15 @@ public class DevicePolicyManager {
* @param executor The executor through which the listener should be invoked.
* @param listener A callback object that will inform the caller when the clearing is done.
* @throws SecurityException if the caller is not the device owner/profile owner.
- * @return whether the clearing succeeded.
*/
- public boolean clearApplicationUserData(@NonNull ComponentName admin,
+ public void clearApplicationUserData(@NonNull ComponentName admin,
@NonNull String packageName, @NonNull @CallbackExecutor Executor executor,
@NonNull OnClearApplicationUserDataListener listener) {
throwIfParentInstance("clearAppData");
Preconditions.checkNotNull(executor);
+ Preconditions.checkNotNull(listener);
try {
- return mService.clearApplicationUserData(admin, packageName,
+ mService.clearApplicationUserData(admin, packageName,
new IPackageDataObserver.Stub() {
public void onRemoveCompleted(String pkg, boolean succeeded) {
executor.execute(() ->
@@ -9153,9 +9252,10 @@ public class DevicePolicyManager {
* after calling this method.
*
* <p>The incoming target administrator must have the
- * {@link DeviceAdminReceiver#SUPPORT_TRANSFER_OWNERSHIP_META_DATA} <code>meta-data</code> tag
- * included in its corresponding <code>receiver</code> component with a value of {@code true}.
- * Otherwise an {@link IllegalArgumentException} will be thrown.
+ * <code>&lt;support-transfer-ownership /&gt;</code> tag inside the
+ * <code>&lt;device-admin&gt;&lt;/device-admin&gt;</code> tags in the xml file referenced by
+ * {@link DeviceAdminReceiver#DEVICE_ADMIN_META_DATA}. Otherwise an
+ * {@link IllegalArgumentException} will be thrown.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with
* @param target which {@link DeviceAdminReceiver} we want the new administrator to be
@@ -9255,43 +9355,27 @@ public class DevicePolicyManager {
}
/**
- * Allows/disallows printing.
- *
- * Called by a device owner or a profile owner.
- * Device owner changes policy for all users. Profile owner can override it if present.
- * Printing is enabled by default. If {@code FEATURE_PRINTING} is absent, the call is ignored.
- *
- * @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param enabled whether printing should be allowed or not.
- * @throws SecurityException if {@code admin} is neither device, nor profile owner.
- */
- public void setPrintingEnabled(@NonNull ComponentName admin, boolean enabled) {
- try {
- mService.setPrintingEnabled(admin, enabled);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
- * Returns whether printing is enabled for this user.
- *
- * Always {@code false} if {@code FEATURE_PRINTING} is absent.
- * Otherwise, {@code true} by default.
- *
- * @return {@code true} iff printing is enabled.
- */
- public boolean isPrintingEnabled() {
- try {
- return mService.isPrintingEnabled();
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* Called by device owner to add an override APN.
*
+ * <p>This method may returns {@code -1} if {@code apnSetting} conflicts with an existing
+ * override APN. Update the existing conflicted APN with
+ * {@link #updateOverrideApn(ComponentName, int, ApnSetting)} instead of adding a new entry.
+ * <p>Two override APNs are considered to conflict when all the following APIs return
+ * the same values on both override APNs:
+ * <ul>
+ * <li>{@link ApnSetting#getOperatorNumeric()}</li>
+ * <li>{@link ApnSetting#getApnName()}</li>
+ * <li>{@link ApnSetting#getProxyAddress()}</li>
+ * <li>{@link ApnSetting#getProxyPort()}</li>
+ * <li>{@link ApnSetting#getMmsProxyAddress()}</li>
+ * <li>{@link ApnSetting#getMmsProxyPort()}</li>
+ * <li>{@link ApnSetting#getMmsc()}</li>
+ * <li>{@link ApnSetting#isEnabled()}</li>
+ * <li>{@link ApnSetting#getMvnoType()}</li>
+ * <li>{@link ApnSetting#getProtocol()}</li>
+ * <li>{@link ApnSetting#getRoamingProtocol()}</li>
+ * </ul>
+ *
* @param admin which {@link DeviceAdminReceiver} this request is associated with
* @param apnSetting the override APN to insert
* @return The {@code id} of inserted override APN. Or {@code -1} when failed to insert into
@@ -9315,6 +9399,12 @@ public class DevicePolicyManager {
/**
* Called by device owner to update an override APN.
*
+ * <p>This method may returns {@code false} if there is no override APN with the given
+ * {@code apnId}.
+ * <p>This method may also returns {@code false} if {@code apnSetting} conflicts with an
+ * existing override APN. Update the existing conflicted APN instead.
+ * <p>See {@link #addOverrideApn} for the definition of conflict.
+ *
* @param admin which {@link DeviceAdminReceiver} this request is associated with
* @param apnId the {@code id} of the override APN to update
* @param apnSetting the override APN to update
@@ -9340,6 +9430,9 @@ public class DevicePolicyManager {
/**
* Called by device owner to remove an override APN.
*
+ * <p>This method may returns {@code false} if there is no override APN with the given
+ * {@code apnId}.
+ *
* @param admin which {@link DeviceAdminReceiver} this request is associated with
* @param apnId the {@code id} of the override APN to remove
* @return {@code true} if the required override APN is successfully removed, {@code false}
@@ -9426,12 +9519,19 @@ public class DevicePolicyManager {
/**
* Returns the data passed from the current administrator to the new administrator during an
* ownership transfer. This is the same {@code bundle} passed in
- * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}.
+ * {@link #transferOwnership(ComponentName, ComponentName, PersistableBundle)}. The bundle is
+ * persisted until the profile owner or device owner is removed.
+ *
+ * <p>This is the same <code>bundle</code> received in the
+ * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}.
+ * Use this method to retrieve it after the transfer as long as the new administrator is the
+ * active device or profile owner.
*
* <p>Returns <code>null</code> if no ownership transfer was started for the calling user.
*
* @see #transferOwnership
* @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)
+ * @throws SecurityException if the caller is not a device or profile owner.
*/
@Nullable
public PersistableBundle getTransferOwnershipBundle() {
diff --git a/android/app/admin/DevicePolicyManagerInternal.java b/android/app/admin/DevicePolicyManagerInternal.java
index ebaf4648..de929789 100644
--- a/android/app/admin/DevicePolicyManagerInternal.java
+++ b/android/app/admin/DevicePolicyManagerInternal.java
@@ -141,4 +141,10 @@ public abstract class DevicePolicyManagerInternal {
* @return localized error message
*/
public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId);
+
+ /**
+ * @return cached version of DPM policies that can be accessed without risking deadlocks.
+ * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
+ */
+ protected abstract DevicePolicyCache getDevicePolicyCache();
}
diff --git a/android/app/admin/FreezeInterval.java b/android/app/admin/FreezeInterval.java
new file mode 100644
index 00000000..de5e21ac
--- /dev/null
+++ b/android/app/admin/FreezeInterval.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2018 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.admin;
+
+import android.app.admin.SystemUpdatePolicy.ValidationFailedException;
+import android.util.Log;
+import android.util.Pair;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An interval representing one freeze period which repeats annually. We use the number of days
+ * since the start of (non-leap) year to define the start and end dates of an interval, both
+ * inclusive. If the end date is smaller than the start date, the interval is considered wrapped
+ * around the year-end. As far as an interval is concerned, February 29th should be treated as
+ * if it were February 28th: so an interval starting or ending on February 28th are not
+ * distinguishable from an interval on February 29th. When calulating interval length or
+ * distance between two dates, February 29th is also disregarded.
+ *
+ * @see SystemUpdatePolicy#setFreezePeriods
+ * @hide
+ */
+public class FreezeInterval {
+ private static final String TAG = "FreezeInterval";
+
+ private static final int DUMMY_YEAR = 2001;
+ static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year
+
+ final int mStartDay; // [1,365]
+ final int mEndDay; // [1,365]
+
+ FreezeInterval(int startDay, int endDay) {
+ if (startDay < 1 || startDay > 365 || endDay < 1 || endDay > 365) {
+ throw new RuntimeException("Bad dates for Interval: " + startDay + "," + endDay);
+ }
+ mStartDay = startDay;
+ mEndDay = endDay;
+ }
+
+ int getLength() {
+ return getEffectiveEndDay() - mStartDay + 1;
+ }
+
+ boolean isWrapped() {
+ return mEndDay < mStartDay;
+ }
+
+ /**
+ * Returns the effective end day, taking wrapping around year-end into consideration
+ */
+ int getEffectiveEndDay() {
+ if (!isWrapped()) {
+ return mEndDay;
+ } else {
+ return mEndDay + DAYS_IN_YEAR;
+ }
+ }
+
+ boolean contains(LocalDate localDate) {
+ final int daysOfYear = dayOfYearDisregardLeapYear(localDate);
+ if (!isWrapped()) {
+ // ---[start---now---end]---
+ return (mStartDay <= daysOfYear) && (daysOfYear <= mEndDay);
+ } else {
+ // ---end]---[start---now---
+ // or ---now---end]---[start---
+ return (mStartDay <= daysOfYear) || (daysOfYear <= mEndDay);
+ }
+ }
+
+ boolean after(LocalDate localDate) {
+ return mStartDay > dayOfYearDisregardLeapYear(localDate);
+ }
+
+ /**
+ * Instantiate the current interval to real calendar dates, given a calendar date
+ * {@code now}. If the interval contains now, the returned calendar dates should be the
+ * current interval (in real calendar dates) that includes now. If the interval does not
+ * include now, the returned dates represents the next future interval.
+ * The result will always have the same month and dayOfMonth value as the non-instantiated
+ * interval itself.
+ */
+ Pair<LocalDate, LocalDate> toCurrentOrFutureRealDates(LocalDate now) {
+ final int nowDays = dayOfYearDisregardLeapYear(now);
+ final int startYearAdjustment, endYearAdjustment;
+ if (contains(now)) {
+ // current interval
+ if (mStartDay <= nowDays) {
+ // ----------[start---now---end]---
+ // or ---end]---[start---now----------
+ startYearAdjustment = 0;
+ endYearAdjustment = isWrapped() ? 1 : 0;
+ } else /* nowDays <= mEndDay */ {
+ // or ---now---end]---[start----------
+ startYearAdjustment = -1;
+ endYearAdjustment = 0;
+ }
+ } else {
+ // next interval
+ if (mStartDay > nowDays) {
+ // ----------now---[start---end]---
+ // or ---end]---now---[start----------
+ startYearAdjustment = 0;
+ endYearAdjustment = isWrapped() ? 1 : 0;
+ } else /* mStartDay <= nowDays */ {
+ // or ---[start---end]---now----------
+ startYearAdjustment = 1;
+ endYearAdjustment = 1;
+ }
+ }
+ final LocalDate startDate = LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).withYear(
+ now.getYear() + startYearAdjustment);
+ final LocalDate endDate = LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).withYear(
+ now.getYear() + endYearAdjustment);
+ return new Pair<>(startDate, endDate);
+ }
+
+ @Override
+ public String toString() {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd");
+ return LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).format(formatter) + " - "
+ + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter);
+ }
+
+ // Treat the supplied date as in a non-leap year and return its day of year.
+ static int dayOfYearDisregardLeapYear(LocalDate date) {
+ return date.withYear(DUMMY_YEAR).getDayOfYear();
+ }
+
+ /**
+ * Compute the number of days between first (inclusive) and second (exclusive),
+ * treating all years in between as non-leap.
+ */
+ public static int distanceWithoutLeapYear(LocalDate first, LocalDate second) {
+ return dayOfYearDisregardLeapYear(first) - dayOfYearDisregardLeapYear(second)
+ + DAYS_IN_YEAR * (first.getYear() - second.getYear());
+ }
+
+ /**
+ * Sort, de-duplicate and merge an interval list
+ *
+ * Instead of using any fancy logic for merging intervals which has loads of corner cases,
+ * simply flatten the interval onto a list of 365 calendar days and recreate the interval list
+ * from that.
+ *
+ * This method should return a list of intervals with the following post-conditions:
+ * 1. Interval.startDay in strictly ascending order
+ * 2. No two intervals should overlap or touch
+ * 3. At most one wrapped Interval remains, and it will be at the end of the list
+ * @hide
+ */
+ protected static List<FreezeInterval> canonicalizeIntervals(List<FreezeInterval> intervals) {
+ boolean[] taken = new boolean[DAYS_IN_YEAR];
+ // First convert the intervals into flat array
+ for (FreezeInterval interval : intervals) {
+ for (int i = interval.mStartDay; i <= interval.getEffectiveEndDay(); i++) {
+ taken[(i - 1) % DAYS_IN_YEAR] = true;
+ }
+ }
+ // Then reconstruct intervals from the array
+ List<FreezeInterval> result = new ArrayList<>();
+ int i = 0;
+ while (i < DAYS_IN_YEAR) {
+ if (!taken[i]) {
+ i++;
+ continue;
+ }
+ final int intervalStart = i + 1;
+ while (i < DAYS_IN_YEAR && taken[i]) i++;
+ result.add(new FreezeInterval(intervalStart, i));
+ }
+ // Check if the last entry can be merged to the first entry to become one single
+ // wrapped interval
+ final int lastIndex = result.size() - 1;
+ if (lastIndex > 0 && result.get(lastIndex).mEndDay == DAYS_IN_YEAR
+ && result.get(0).mStartDay == 1) {
+ FreezeInterval wrappedInterval = new FreezeInterval(result.get(lastIndex).mStartDay,
+ result.get(0).mEndDay);
+ result.set(lastIndex, wrappedInterval);
+ result.remove(0);
+ }
+ return result;
+ }
+
+ /**
+ * Verifies if the supplied freeze periods satisfies the constraints set out in
+ * {@link SystemUpdatePolicy#setFreezePeriods(List)}, and in particular, any single freeze
+ * period cannot exceed {@link SystemUpdatePolicy#FREEZE_PERIOD_MAX_LENGTH} days, and two freeze
+ * periods need to be at least {@link SystemUpdatePolicy#FREEZE_PERIOD_MIN_SEPARATION} days
+ * apart.
+ *
+ * @hide
+ */
+ protected static void validatePeriods(List<FreezeInterval> periods) {
+ List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods);
+ if (allPeriods.size() != periods.size()) {
+ throw SystemUpdatePolicy.ValidationFailedException.duplicateOrOverlapPeriods();
+ }
+ for (int i = 0; i < allPeriods.size(); i++) {
+ FreezeInterval current = allPeriods.get(i);
+ if (current.getLength() > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) {
+ throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooLong("Freeze "
+ + "period " + current + " is too long: " + current.getLength() + " days");
+ }
+ FreezeInterval previous = i > 0 ? allPeriods.get(i - 1)
+ : allPeriods.get(allPeriods.size() - 1);
+ if (previous != current) {
+ final int separation;
+ if (i == 0 && !previous.isWrapped()) {
+ // -->[current]---[-previous-]<---
+ separation = current.mStartDay
+ + (DAYS_IN_YEAR - previous.mEndDay) - 1;
+ } else {
+ // --[previous]<--->[current]---------
+ // OR ----prev---]<--->[current]---[prev-
+ separation = current.mStartDay - previous.mEndDay - 1;
+ }
+ if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) {
+ throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooClose("Freeze"
+ + " periods " + previous + " and " + current + " are too close "
+ + "together: " + separation + " days apart");
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies that the current freeze periods are still legal, considering the previous freeze
+ * periods the device went through. In particular, when combined with the previous freeze
+ * period, the maximum freeze length or the minimum freeze separation should not be violated.
+ *
+ * @hide
+ */
+ protected static void validateAgainstPreviousFreezePeriod(List<FreezeInterval> periods,
+ LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) {
+ if (periods.size() == 0 || prevPeriodStart == null || prevPeriodEnd == null) {
+ return;
+ }
+ if (prevPeriodStart.isAfter(now) || prevPeriodEnd.isAfter(now)) {
+ Log.w(TAG, "Previous period (" + prevPeriodStart + "," + prevPeriodEnd + ") is after"
+ + " current date " + now);
+ // Clock was adjusted backwards. We can continue execution though, the separation
+ // and length validation below still works under this condition.
+ }
+ List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods);
+ // Given current time now, find the freeze period that's either current, or the one
+ // that's immediately afterwards. For the later case, it might be after the year-end,
+ // but this can only happen if there is only one freeze period.
+ FreezeInterval curOrNextFreezePeriod = allPeriods.get(0);
+ for (FreezeInterval interval : allPeriods) {
+ if (interval.contains(now)
+ || interval.mStartDay > FreezeInterval.dayOfYearDisregardLeapYear(now)) {
+ curOrNextFreezePeriod = interval;
+ break;
+ }
+ }
+ Pair<LocalDate, LocalDate> curOrNextFreezeDates = curOrNextFreezePeriod
+ .toCurrentOrFutureRealDates(now);
+ if (now.isAfter(curOrNextFreezeDates.first)) {
+ curOrNextFreezeDates = new Pair<>(now, curOrNextFreezeDates.second);
+ }
+ if (curOrNextFreezeDates.first.isAfter(curOrNextFreezeDates.second)) {
+ throw new IllegalStateException("Current freeze dates inverted: "
+ + curOrNextFreezeDates.first + "-" + curOrNextFreezeDates.second);
+ }
+ // Now validate [prevPeriodStart, prevPeriodEnd] against curOrNextFreezeDates
+ final String periodsDescription = "Prev: " + prevPeriodStart + "," + prevPeriodEnd
+ + "; cur: " + curOrNextFreezeDates.first + "," + curOrNextFreezeDates.second;
+ long separation = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.first,
+ prevPeriodEnd) - 1;
+ if (separation > 0) {
+ // Two intervals do not overlap, check separation
+ if (separation < SystemUpdatePolicy.FREEZE_PERIOD_MIN_SEPARATION) {
+ throw ValidationFailedException.combinedPeriodTooClose("Previous freeze period "
+ + "too close to new period: " + separation + ", " + periodsDescription);
+ }
+ } else {
+ // Two intervals overlap, check combined length
+ long length = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.second,
+ prevPeriodStart) + 1;
+ if (length > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) {
+ throw ValidationFailedException.combinedPeriodTooLong("Combined freeze period "
+ + "exceeds maximum days: " + length + ", " + periodsDescription);
+ }
+ }
+ }
+}
diff --git a/android/app/admin/SecurityLog.java b/android/app/admin/SecurityLog.java
index d3b66d0d..38b4f8ff 100644
--- a/android/app/admin/SecurityLog.java
+++ b/android/app/admin/SecurityLog.java
@@ -18,6 +18,7 @@ package android.app.admin;
import android.annotation.IntDef;
import android.annotation.TestApi;
+import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
@@ -53,64 +54,396 @@ public class SecurityLog {
TAG_APP_PROCESS_START,
TAG_KEYGUARD_DISMISSED,
TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
- TAG_KEYGUARD_SECURED
+ TAG_KEYGUARD_SECURED,
+ TAG_OS_STARTUP,
+ TAG_OS_SHUTDOWN,
+ TAG_LOGGING_STARTED,
+ TAG_LOGGING_STOPPED,
+ TAG_MEDIA_MOUNT,
+ TAG_MEDIA_UNMOUNT,
+ TAG_LOG_BUFFER_SIZE_CRITICAL,
+ TAG_PASSWORD_EXPIRATION_SET,
+ TAG_PASSWORD_COMPLEXITY_SET,
+ TAG_PASSWORD_HISTORY_LENGTH_SET,
+ TAG_MAX_SCREEN_LOCK_TIMEOUT_SET,
+ TAG_MAX_PASSWORD_ATTEMPTS_SET,
+ TAG_KEYGUARD_DISABLED_FEATURES_SET,
+ TAG_REMOTE_LOCK,
+ TAG_USER_RESTRICTION_ADDED,
+ TAG_USER_RESTRICTION_REMOVED,
+ TAG_WIPE_FAILURE,
+ TAG_KEY_GENERATED,
+ TAG_KEY_IMPORT,
+ TAG_KEY_DESTRUCTION,
+ TAG_CERT_AUTHORITY_INSTALLED,
+ TAG_CERT_AUTHORITY_REMOVED,
+ TAG_CRYPTO_SELF_TEST_COMPLETED,
+ TAG_KEY_INTEGRITY_VIOLATION,
+ TAG_CERT_VALIDATION_FAILURE,
})
public @interface SecurityLogTag {}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "LEVEL_" }, value = {
+ LEVEL_INFO,
+ LEVEL_WARNING,
+ LEVEL_ERROR
+ })
+ public @interface SecurityLogLevel {}
+
/**
- * Indicate that an ADB interactive shell was opened via "adb shell".
+ * Indicates that an ADB interactive shell was opened via "adb shell".
* There is no extra payload in the log event.
*/
public static final int TAG_ADB_SHELL_INTERACTIVE =
SecurityLogTags.SECURITY_ADB_SHELL_INTERACTIVE;
+
/**
- * Indicate that an shell command was issued over ADB via "adb shell command"
- * The log entry contains a string data of the shell command, accessible via
- * {@link SecurityEvent#getData()}
+ * Indicates that a shell command was issued over ADB via {@code adb shell <command>}
+ * The log entry contains a {@code String} payload containing the shell command, accessible
+ * via {@link SecurityEvent#getData()}.
*/
public static final int TAG_ADB_SHELL_CMD = SecurityLogTags.SECURITY_ADB_SHELL_COMMAND;
+
/**
- * Indicate that a file was pulled from the device via the adb daemon, for example via
- * "adb pull". The log entry contains a string data of the path of the pulled file,
- * accessible via {@link SecurityEvent#getData()}
+ * Indicates that a file was pulled from the device via the adb daemon, for example via
+ * {@code adb pull}. The log entry contains a {@code String} payload containing the path of the
+ * pulled file on the device, accessible via {@link SecurityEvent#getData()}.
*/
public static final int TAG_SYNC_RECV_FILE = SecurityLogTags.SECURITY_ADB_SYNC_RECV;
+
/**
- * Indicate that a file was pushed to the device via the adb daemon, for example via
- * "adb push". The log entry contains a string data of the destination path of the
- * pushed file, accessible via {@link SecurityEvent#getData()}
+ * Indicates that a file was pushed to the device via the adb daemon, for example via
+ * {@code adb push}. The log entry contains a {@code String} payload containing the destination
+ * path of the pushed file, accessible via {@link SecurityEvent#getData()}.
*/
public static final int TAG_SYNC_SEND_FILE = SecurityLogTags.SECURITY_ADB_SYNC_SEND;
+
/**
- * Indicate that an app process was started. The log entry contains the following
+ * Indicates that an app process was started. The log entry contains the following
* information about the process encapsulated in an {@link Object} array, accessible via
* {@link SecurityEvent#getData()}:
- * process name (String), exact start time (long), app Uid (integer), app Pid (integer),
- * seinfo tag (String), SHA-256 hash of the base APK in hexadecimal (String)
+ * <li> [0] process name ({@code String})
+ * <li> [1] exact start time in milliseconds according to {@code System.currentTimeMillis()}
+ * ({@code Long})
+ * <li> [2] app uid ({@code Integer})
+ * <li> [3] app pid ({@code Integer})
+ * <li> [4] seinfo tag ({@code String})
+ * <li> [5] SHA-256 hash of the base APK in hexadecimal ({@code String})
*/
public static final int TAG_APP_PROCESS_START = SecurityLogTags.SECURITY_APP_PROCESS_START;
+
/**
- * Indicate that keyguard is being dismissed.
+ * Indicates that keyguard has been dismissed.
* There is no extra payload in the log event.
*/
- public static final int TAG_KEYGUARD_DISMISSED =
- SecurityLogTags.SECURITY_KEYGUARD_DISMISSED;
+ public static final int TAG_KEYGUARD_DISMISSED = SecurityLogTags.SECURITY_KEYGUARD_DISMISSED;
+
/**
- * Indicate that there has been an authentication attempt to dismiss the keyguard. The log entry
- * contains the following information about the attempt encapsulated in an {@link Object} array,
- * accessible via {@link SecurityEvent#getData()}:
- * attempt result (integer, 1 for successful, 0 for unsuccessful), strength of auth method
- * (integer, 1 if strong auth method was used, 0 otherwise)
+ * Indicates that there has been an authentication attempt to dismiss the keyguard. The log
+ * entry contains the following information about the attempt encapsulated in an {@link Object}
+ * array, accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] attempt result ({@code Integer}, 1 for successful, 0 for unsuccessful)
+ * <li> [1] strength of authentication method ({@code Integer}, 1 if strong authentication
+ * method was used, 0 otherwise)
*/
public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT =
SecurityLogTags.SECURITY_KEYGUARD_DISMISS_AUTH_ATTEMPT;
+
/**
- * Indicate that the device has been locked, either by user or by timeout.
- * There is no extra payload in the log event.
+ * Indicates that the device has been locked, either by the user or by a timeout. There is no
+ * extra payload in the log event.
*/
public static final int TAG_KEYGUARD_SECURED = SecurityLogTags.SECURITY_KEYGUARD_SECURED;
/**
+ * Indicates that the Android OS has started. The log entry contains the following information
+ * about the startup time software integrity check encapsulated in an {@link Object} array,
+ * accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] Verified Boot state ({@code String})
+ * <li> [1] dm-verity mode ({@code String}).
+ * <p>Verified Boot state can be one of the following:
+ * <li> {@code green} indicates that there is a full chain of trust extending from the
+ * bootloader to verified partitions including the bootloader, boot partition, and all verified
+ * partitions.
+ * <li> {@code yellow} indicates that the boot partition has been verified using the embedded
+ * certificate and the signature is valid.
+ * <li> {@code orange} indicates that the device may be freely modified. Device integrity is
+ * left to the user to verify out-of-band.
+ * <p>dm-verity mode can be one of the following:
+ * <li> {@code enforcing} indicates that the device will be restarted when corruption is
+ * detected.
+ * <li> {@code eio} indicates that an I/O error will be returned for an attempt to read
+ * corrupted data blocks.
+ * For details see Verified Boot documentation.
+ */
+ public static final int TAG_OS_STARTUP = SecurityLogTags.SECURITY_OS_STARTUP;
+
+ /**
+ * Indicates that the Android OS has shutdown. There is no extra payload in the log event.
+ */
+ public static final int TAG_OS_SHUTDOWN = SecurityLogTags.SECURITY_OS_SHUTDOWN;
+
+ /**
+ * Indicates start-up of audit logging. There is no extra payload in the log event.
+ */
+ public static final int TAG_LOGGING_STARTED = SecurityLogTags.SECURITY_LOGGING_STARTED;
+
+ /**
+ * Indicates shutdown of audit logging. There is no extra payload in the log event.
+ */
+ public static final int TAG_LOGGING_STOPPED = SecurityLogTags.SECURITY_LOGGING_STOPPED;
+
+ /**
+ * Indicates that removable media has been mounted on the device. The log entry contains the
+ * following information about the event, encapsulated in an {@link Object} array and
+ * accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] mount point ({@code String})
+ * <li> [1] volume label ({@code String}).
+ */
+ public static final int TAG_MEDIA_MOUNT = SecurityLogTags.SECURITY_MEDIA_MOUNTED;
+
+ /**
+ * Indicates that removable media was unmounted from the device. The log entry contains the
+ * following information about the event, encapsulated in an {@link Object} array and
+ * accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] mount point ({@code String})
+ * <li> [1] volume label ({@code String}).
+ */
+ public static final int TAG_MEDIA_UNMOUNT = SecurityLogTags.SECURITY_MEDIA_UNMOUNTED;
+
+ /**
+ * Indicates that the audit log buffer has reached 90% of its capacity. There is no extra
+ * payload in the log event.
+ */
+ public static final int TAG_LOG_BUFFER_SIZE_CRITICAL =
+ SecurityLogTags.SECURITY_LOG_BUFFER_SIZE_CRITICAL;
+
+ /**
+ * Indicates that an admin has set a password expiration timeout. The log entry contains the
+ * following information about the event, encapsulated in an {@link Object} array and accessible
+ * via {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] target user ID ({@code Integer})
+ * <li> [3] new password expiration timeout in milliseconds ({@code Long}).
+ * @see DevicePolicyManager#setPasswordExpirationTimeout(ComponentName, long)
+ */
+ public static final int TAG_PASSWORD_EXPIRATION_SET =
+ SecurityLogTags.SECURITY_PASSWORD_EXPIRATION_SET;
+
+ /**
+ * Indicates that an admin has set a requirement for password complexity. The log entry contains
+ * the following information about the event, encapsulated in an {@link Object} array and
+ * accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] target user ID ({@code Integer})
+ * <li> [3] minimum password length ({@code Integer})
+ * <li> [4] password quality constraint ({@code Integer})
+ * <li> [5] minimum number of letters ({@code Integer})
+ * <li> [6] minimum number of non-letters ({@code Integer})
+ * <li> [7] minimum number of digits ({@code Integer})
+ * <li> [8] minimum number of uppercase letters ({@code Integer})
+ * <li> [9] minimum number of lowercase letters ({@code Integer})
+ * <li> [10] minimum number of symbols ({@code Integer})
+ *
+ * @see DevicePolicyManager#setPasswordMinimumLength(ComponentName, int)
+ * @see DevicePolicyManager#setPasswordQuality(ComponentName, int)
+ * @see DevicePolicyManager#setPasswordMinimumLetters(ComponentName, int)
+ * @see DevicePolicyManager#setPasswordMinimumNonLetter(ComponentName, int)
+ * @see DevicePolicyManager#setPasswordMinimumLowerCase(ComponentName, int)
+ * @see DevicePolicyManager#setPasswordMinimumUpperCase(ComponentName, int)
+ * @see DevicePolicyManager#setPasswordMinimumNumeric(ComponentName, int)
+ * @see DevicePolicyManager#setPasswordMinimumSymbols(ComponentName, int)
+ */
+ public static final int TAG_PASSWORD_COMPLEXITY_SET =
+ SecurityLogTags.SECURITY_PASSWORD_COMPLEXITY_SET;
+
+ /**
+ * Indicates that an admin has set a password history length. The log entry contains the
+ * following information about the event encapsulated in an {@link Object} array, accessible
+ * via {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] target user ID ({@code Integer})
+ * <li> [3] new password history length value ({@code Integer})
+ * @see DevicePolicyManager#setPasswordHistoryLength(ComponentName, int)
+ */
+ public static final int TAG_PASSWORD_HISTORY_LENGTH_SET =
+ SecurityLogTags.SECURITY_PASSWORD_HISTORY_LENGTH_SET;
+
+ /**
+ * Indicates that an admin has set a maximum screen lock timeout. The log entry contains the
+ * following information about the event encapsulated in an {@link Object} array, accessible
+ * via {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] target user ID ({@code Integer})
+ * <li> [3] new screen lock timeout in milliseconds ({@code Long})
+ * @see DevicePolicyManager#setMaximumTimeToLock(ComponentName, long)
+ */
+ public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET =
+ SecurityLogTags.SECURITY_MAX_SCREEN_LOCK_TIMEOUT_SET;
+
+ /**
+ * Indicates that an admin has set a maximum number of failed password attempts before wiping
+ * data. The log entry contains the following information about the event encapsulated in an
+ * {@link Object} array, accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] target user ID ({@code Integer})
+ * <li> [3] new maximum number of failed password attempts ({@code Integer})
+ * @see DevicePolicyManager#setMaximumFailedPasswordsForWipe(ComponentName, int)
+ */
+ public static final int TAG_MAX_PASSWORD_ATTEMPTS_SET =
+ SecurityLogTags.SECURITY_MAX_PASSWORD_ATTEMPTS_SET;
+
+ /**
+ * Indicates that an admin has set disabled keyguard features. The log entry contains the
+ * following information about the event encapsulated in an {@link Object} array, accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] target user ID ({@code Integer})
+ * <li> [3] disabled keyguard feature mask ({@code Integer}).
+ * @see DevicePolicyManager#setKeyguardDisabledFeatures(ComponentName, int)
+ */
+ public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET =
+ SecurityLogTags.SECURITY_KEYGUARD_DISABLED_FEATURES_SET;
+
+ /**
+ * Indicates that an admin remotely locked the device or profile. The log entry contains the
+ * following information about the event encapsulated in an {@link Object} array, accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String}),
+ * <li> [1] admin user ID ({@code Integer}).
+ * <li> [2] target user ID ({@code Integer})
+ */
+ public static final int TAG_REMOTE_LOCK = SecurityLogTags.SECURITY_REMOTE_LOCK;
+
+ /**
+ * Indicates a failure to wipe device or user data. There is no extra payload in the log event.
+ */
+ public static final int TAG_WIPE_FAILURE = SecurityLogTags.SECURITY_WIPE_FAILED;
+
+ /**
+ * Indicates that an authentication key was generated. The log entry contains the following
+ * information about the event, encapsulated in an {@link Object} array and accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
+ * <li> [1] alias of the key ({@code String})
+ * <li> [2] requesting process uid ({@code Integer}).
+ */
+ public static final int TAG_KEY_GENERATED =
+ SecurityLogTags.SECURITY_KEY_GENERATED;
+
+ /**
+ * Indicates that a cryptographic key was imported. The log entry contains the following
+ * information about the event, encapsulated in an {@link Object} array and accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
+ * <li> [1] alias of the key ({@code String})
+ * <li> [2] requesting process uid ({@code Integer}).
+ */
+ public static final int TAG_KEY_IMPORT = SecurityLogTags.SECURITY_KEY_IMPORTED;
+
+ /**
+ * Indicates that a cryptographic key was destroyed. The log entry contains the following
+ * information about the event, encapsulated in an {@link Object} array and accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
+ * <li> [1] alias of the key ({@code String})
+ * <li> [2] requesting process uid ({@code Integer}).
+ */
+ public static final int TAG_KEY_DESTRUCTION = SecurityLogTags.SECURITY_KEY_DESTROYED;
+
+ /**
+ * Indicates that a new root certificate has been installed into system's trusted credential
+ * storage. The log entry contains the following information about the event, encapsulated in an
+ * {@link Object} array and accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
+ * <li> [1] subject of the certificate ({@code String}).
+ */
+ public static final int TAG_CERT_AUTHORITY_INSTALLED =
+ SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED;
+
+ /**
+ * Indicates that a new root certificate has been removed from system's trusted credential
+ * storage. The log entry contains the following information about the event, encapsulated in an
+ * {@link Object} array and accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
+ * <li> [1] subject of the certificate ({@code String}).
+ */
+ public static final int TAG_CERT_AUTHORITY_REMOVED =
+ SecurityLogTags.SECURITY_CERT_AUTHORITY_REMOVED;
+
+ /**
+ * Indicates that an admin has set a user restriction. The log entry contains the following
+ * information about the event, encapsulated in an {@link Object} array and accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] user restriction ({@code String})
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ */
+ public static final int TAG_USER_RESTRICTION_ADDED =
+ SecurityLogTags.SECURITY_USER_RESTRICTION_ADDED;
+
+ /**
+ * Indicates that an admin has removed a user restriction. The log entry contains the following
+ * information about the event, encapsulated in an {@link Object} array and accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] user restriction ({@code String})
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ */
+ public static final int TAG_USER_RESTRICTION_REMOVED =
+ SecurityLogTags.SECURITY_USER_RESTRICTION_REMOVED;
+
+ /**
+ * Indicates that cryptographic functionality self test has completed. The log entry contains an
+ * {@code Integer} payload, indicating the result of the test (0 if the test failed, 1 if
+ * succeeded) and accessible via {@link SecurityEvent#getData()}.
+ */
+ public static final int TAG_CRYPTO_SELF_TEST_COMPLETED =
+ SecurityLogTags.SECURITY_CRYPTO_SELF_TEST_COMPLETED;
+
+ /**
+ * Indicates a failed cryptographic key integrity check. The log entry contains the following
+ * information about the event, encapsulated in an {@link Object} array and accessible via
+ * {@link SecurityEvent#getData()}:
+ * <li> [0] alias of the key ({@code String})
+ * <li> [1] owner application uid ({@code Integer}).
+ */
+ public static final int TAG_KEY_INTEGRITY_VIOLATION =
+ SecurityLogTags.SECURITY_KEY_INTEGRITY_VIOLATION;
+
+ /**
+ * Indicates a failure to validate X.509v3 certificate. The log entry contains a {@code String}
+ * payload indicating the failure reason, accessible via {@link SecurityEvent#getData()}.
+ */
+ public static final int TAG_CERT_VALIDATION_FAILURE =
+ SecurityLogTags.SECURITY_CERT_VALIDATION_FAILURE;
+
+ /**
+ * Event severity level indicating that the event corresponds to normal workflow.
+ */
+ public static final int LEVEL_INFO = 1;
+
+ /**
+ * Event severity level indicating that the event may require admin attention.
+ */
+ public static final int LEVEL_WARNING = 2;
+
+ /**
+ * Event severity level indicating that the event requires urgent admin action.
+ */
+ public static final int LEVEL_ERROR = 3;
+
+ /**
* Returns if security logging is enabled. Log producers should only write new logs if this is
* true. Under the hood this is the logical AND of whether device owner exists and whether
* it enables logging by setting the system property {@link #PROPERTY_LOGGING_ENABLED}.
@@ -198,6 +531,64 @@ public class SecurityLog {
return mId;
}
+ /**
+ * Returns severity level for the event.
+ */
+ public @SecurityLogLevel int getLogLevel() {
+ switch (mEvent.getTag()) {
+ case TAG_ADB_SHELL_INTERACTIVE:
+ case TAG_ADB_SHELL_CMD:
+ case TAG_SYNC_RECV_FILE:
+ case TAG_SYNC_SEND_FILE:
+ case TAG_APP_PROCESS_START:
+ case TAG_KEYGUARD_DISMISSED:
+ case TAG_KEYGUARD_SECURED:
+ case TAG_OS_STARTUP:
+ case TAG_OS_SHUTDOWN:
+ case TAG_LOGGING_STARTED:
+ case TAG_LOGGING_STOPPED:
+ case TAG_MEDIA_MOUNT:
+ case TAG_MEDIA_UNMOUNT:
+ case TAG_PASSWORD_EXPIRATION_SET:
+ case TAG_PASSWORD_COMPLEXITY_SET:
+ case TAG_PASSWORD_HISTORY_LENGTH_SET:
+ case TAG_MAX_SCREEN_LOCK_TIMEOUT_SET:
+ case TAG_MAX_PASSWORD_ATTEMPTS_SET:
+ case TAG_USER_RESTRICTION_ADDED:
+ case TAG_USER_RESTRICTION_REMOVED:
+ return LEVEL_INFO;
+ case TAG_CERT_AUTHORITY_REMOVED:
+ case TAG_CRYPTO_SELF_TEST_COMPLETED:
+ return getSuccess() ? LEVEL_INFO : LEVEL_ERROR;
+ case TAG_CERT_AUTHORITY_INSTALLED:
+ case TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT:
+ case TAG_KEY_IMPORT:
+ case TAG_KEY_DESTRUCTION:
+ case TAG_KEY_GENERATED:
+ return getSuccess() ? LEVEL_INFO : LEVEL_WARNING;
+ case TAG_LOG_BUFFER_SIZE_CRITICAL:
+ case TAG_WIPE_FAILURE:
+ case TAG_KEY_INTEGRITY_VIOLATION:
+ return LEVEL_ERROR;
+ case TAG_CERT_VALIDATION_FAILURE:
+ return LEVEL_WARNING;
+ default:
+ return LEVEL_INFO;
+ }
+ }
+
+ // Success/failure if present is encoded as an integer in the first (0th) element of data.
+ private boolean getSuccess() {
+ final Object data = getData();
+ if (data == null || !(data instanceof Object[])) {
+ return false;
+ }
+
+ final Object[] array = (Object[]) data;
+ return array.length >= 1 && array[0] instanceof Integer && (Integer) array[0] != 0;
+ }
+
+
@Override
public int describeContents() {
return 0;
@@ -263,8 +654,8 @@ public class SecurityLog {
throws IOException;
/**
- * Retrieve all security logs whose timestamp (in nanosceonds) is equal to or greater than the
- * given timestamp. This method will block until either the last log earlier than the given
+ * Retrieve all security logs whose timestamp is equal to or greater than the given timestamp in
+ * nanoseconds. This method will block until either the last log earlier than the given
* timestamp is about to be pruned, or after a 2-hour timeout has passed.
* @hide
*/
diff --git a/android/app/admin/SystemUpdatePolicy.java b/android/app/admin/SystemUpdatePolicy.java
index 232a6887..47b3a81d 100644
--- a/android/app/admin/SystemUpdatePolicy.java
+++ b/android/app/admin/SystemUpdatePolicy.java
@@ -16,16 +16,34 @@
package android.app.admin;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
+import android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* A class that represents a local system update policy set by the device owner.
@@ -34,6 +52,7 @@ import java.lang.annotation.RetentionPolicy;
* @see DevicePolicyManager#getSystemUpdatePolicy
*/
public class SystemUpdatePolicy implements Parcelable {
+ private static final String TAG = "SystemUpdatePolicy";
/** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
@@ -91,23 +110,173 @@ public class SystemUpdatePolicy implements Parcelable {
*/
public static final int TYPE_POSTPONE = 3;
+ /**
+ * Incoming system updates (including security updates) should be blocked. This flag is not
+ * exposed to third-party apps (and any attempt to set it will raise exceptions). This is used
+ * to represent the current installation option type to the privileged system update clients,
+ * for example to indicate OTA freeze is currently in place or when system is outside a daily
+ * maintenance window.
+ *
+ * @see InstallationOption
+ * @hide
+ */
+ @SystemApi
+ public static final int TYPE_PAUSE = 4;
+
private static final String KEY_POLICY_TYPE = "policy_type";
private static final String KEY_INSTALL_WINDOW_START = "install_window_start";
private static final String KEY_INSTALL_WINDOW_END = "install_window_end";
+ private static final String KEY_FREEZE_TAG = "freeze";
+ private static final String KEY_FREEZE_START = "start";
+ private static final String KEY_FREEZE_END = "end";
+
/**
* The upper boundary of the daily maintenance window: 24 * 60 minutes.
*/
private static final int WINDOW_BOUNDARY = 24 * 60;
+ /**
+ * The maximum length of a single freeze period: 90 days.
+ */
+ static final int FREEZE_PERIOD_MAX_LENGTH = 90;
+
+ /**
+ * The minimum allowed time between two adjacent freeze period (from the end of the first
+ * freeze period to the start of the second freeze period, both exclusive): 60 days.
+ */
+ static final int FREEZE_PERIOD_MIN_SEPARATION = 60;
+
+
+ /**
+ * An exception class that represents various validation errors thrown from
+ * {@link SystemUpdatePolicy#setFreezePeriods} and
+ * {@link DevicePolicyManager#setSystemUpdatePolicy}
+ */
+ public static final class ValidationFailedException extends IllegalArgumentException
+ implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_NONE,
+ ERROR_DUPLICATE_OR_OVERLAP,
+ ERROR_NEW_FREEZE_PERIOD_TOO_LONG,
+ ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE,
+ ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG,
+ ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ValidationFailureType {}
+
+ /** @hide */
+ public static final int ERROR_NONE = 0;
+
+ /**
+ * The freeze periods contains duplicates, periods that overlap with each
+ * other or periods whose start and end joins.
+ */
+ public static final int ERROR_DUPLICATE_OR_OVERLAP = 1;
+
+ /**
+ * There exists at least one freeze period whose length exceeds 90 days.
+ */
+ public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2;
+
+ /**
+ * There exists some freeze period which starts within 60 days of the preceding period's
+ * end time.
+ */
+ public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3;
+
+ /**
+ * The device has been in a freeze period and when combining with the new freeze period
+ * to be set, it will result in the total freeze period being longer than 90 days.
+ */
+ public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4;
+
+ /**
+ * The device has been in a freeze period and some new freeze period to be set is less
+ * than 60 days from the end of the last freeze period the device went through.
+ */
+ public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5;
+
+ @ValidationFailureType
+ private final int mErrorCode;
+
+ private ValidationFailedException(int errorCode, String message) {
+ super(message);
+ mErrorCode = errorCode;
+ }
+
+ /**
+ * Returns the type of validation error associated with this exception.
+ */
+ public @ValidationFailureType int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** @hide */
+ public static ValidationFailedException duplicateOrOverlapPeriods() {
+ return new ValidationFailedException(ERROR_DUPLICATE_OR_OVERLAP,
+ "Found duplicate or overlapping periods");
+ }
+
+ /** @hide */
+ public static ValidationFailedException freezePeriodTooLong(String message) {
+ return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_LONG, message);
+ }
+
+ /** @hide */
+ public static ValidationFailedException freezePeriodTooClose(String message) {
+ return new ValidationFailedException(ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, message);
+ }
+
+ /** @hide */
+ public static ValidationFailedException combinedPeriodTooLong(String message) {
+ return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, message);
+ }
+
+ /** @hide */
+ public static ValidationFailedException combinedPeriodTooClose(String message) {
+ return new ValidationFailedException(ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, message);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mErrorCode);
+ dest.writeString(getMessage());
+ }
+
+ public static final Parcelable.Creator<ValidationFailedException> CREATOR =
+ new Parcelable.Creator<ValidationFailedException>() {
+ @Override
+ public ValidationFailedException createFromParcel(Parcel source) {
+ return new ValidationFailedException(source.readInt(), source.readString());
+ }
+
+ @Override
+ public ValidationFailedException[] newArray(int size) {
+ return new ValidationFailedException[size];
+ }
+
+ };
+ }
+
@SystemUpdatePolicyType
private int mPolicyType;
private int mMaintenanceWindowStart;
private int mMaintenanceWindowEnd;
+ private final ArrayList<FreezeInterval> mFreezePeriods;
private SystemUpdatePolicy() {
mPolicyType = TYPE_UNKNOWN;
+ mFreezePeriods = new ArrayList<>();
}
/**
@@ -206,38 +375,305 @@ public class SystemUpdatePolicy implements Parcelable {
}
/**
- * Return if this object represents a valid policy.
+ * Return if this object represents a valid policy with:
+ * 1. Correct type
+ * 2. Valid maintenance window if applicable
+ * 3. Valid freeze periods
* @hide
*/
public boolean isValid() {
- if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) {
+ try {
+ validateType();
+ validateFreezePeriods();
return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Validate the type and maintenance window (if applicable) of this policy object,
+ * throws {@link IllegalArgumentException} if it's invalid.
+ * @hide
+ */
+ public void validateType() {
+ if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) {
+ return;
} else if (mPolicyType == TYPE_INSTALL_WINDOWED) {
- return mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY
- && mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY;
+ if (!(mMaintenanceWindowStart >= 0 && mMaintenanceWindowStart < WINDOW_BOUNDARY
+ && mMaintenanceWindowEnd >= 0 && mMaintenanceWindowEnd < WINDOW_BOUNDARY)) {
+ throw new IllegalArgumentException("Invalid maintenance window");
+ }
} else {
- return false;
+ throw new IllegalArgumentException("Invalid system update policy type.");
+ }
+ }
+
+ /**
+ * Configure a list of freeze periods on top of the current policy. When the device's clock is
+ * within any of the freeze periods, all incoming system updates including security patches will
+ * be blocked and cannot be installed. When the device is outside the freeze periods, the normal
+ * policy behavior will apply.
+ * <p>
+ * Each freeze period is defined by a starting and finishing date (both inclusive). Since the
+ * freeze period repeats annually, both of these dates are simply represented by integers
+ * counting the number of days since year start, similar to {@link LocalDate#getDayOfYear()}. We
+ * do not consider leap year when handling freeze period so the valid range of the integer is
+ * always [1,365] (see last section for more details on leap year). If the finishing date is
+ * smaller than the starting date, the freeze period is considered to be spanning across
+ * year-end.
+ * <p>
+ * Each individual freeze period is allowed to be at most 90 days long, and adjacent freeze
+ * periods need to be at least 60 days apart. Also, the list of freeze periods should not
+ * contain duplicates or overlap with each other. If any of these conditions is not met, a
+ * {@link ValidationFailedException} will be thrown.
+ * <p>
+ * Handling of leap year: we do not consider leap year when handling freeze period, in
+ * particular,
+ * <ul>
+ * <li>When a freeze period is defined by the day of year, February 29th does not count as one
+ * day, so day 59 is February 28th while day 60 is March 1st.</li>
+ * <li>When applying freeze period behavior to the device, a system clock of February 29th is
+ * treated as if it were February 28th</li>
+ * <li>When calculating the number of days of a freeze period or separation between two freeze
+ * periods, February 29th is also ignored and not counted as one day.</li>
+ * </ul>
+ *
+ * @param freezePeriods the list of freeze periods
+ * @throws ValidationFailedException if the supplied freeze periods do not meet the
+ * requirement set above
+ * @return this instance
+ */
+ public SystemUpdatePolicy setFreezePeriods(List<Pair<Integer, Integer>> freezePeriods) {
+ List<FreezeInterval> newPeriods = freezePeriods.stream().map(
+ p -> new FreezeInterval(p.first, p.second)).collect(Collectors.toList());
+ FreezeInterval.validatePeriods(newPeriods);
+ mFreezePeriods.clear();
+ mFreezePeriods.addAll(newPeriods);
+ return this;
+ }
+
+ /**
+ * Returns the list of freeze periods previously set on this system update policy object.
+ *
+ * @return the list of freeze periods, or an empty list if none was set.
+ */
+ public List<Pair<Integer, Integer>> getFreezePeriods() {
+ List<Pair<Integer, Integer>> result = new ArrayList<>(mFreezePeriods.size());
+ for (FreezeInterval interval : mFreezePeriods) {
+ result.add(new Pair<>(interval.mStartDay, interval.mEndDay));
+ }
+ return result;
+ }
+
+ /**
+ * Returns the real calendar dates of the current freeze period, or null if the device
+ * is not in a freeze period at the moment.
+ * @hide
+ */
+ public Pair<LocalDate, LocalDate> getCurrentFreezePeriod(LocalDate now) {
+ for (FreezeInterval interval : mFreezePeriods) {
+ if (interval.contains(now)) {
+ return interval.toCurrentOrFutureRealDates(now);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns time (in milliseconds) until the start of the next freeze period, assuming now
+ * is not within a freeze period.
+ */
+ private long timeUntilNextFreezePeriod(long now) {
+ List<FreezeInterval> sortedPeriods = FreezeInterval.canonicalizeIntervals(mFreezePeriods);
+ LocalDate nowDate = millisToDate(now);
+ LocalDate nextFreezeStart = null;
+ for (FreezeInterval interval : sortedPeriods) {
+ if (interval.after(nowDate)) {
+ nextFreezeStart = interval.toCurrentOrFutureRealDates(nowDate).first;
+ break;
+ } else if (interval.contains(nowDate)) {
+ throw new IllegalArgumentException("Given date is inside a freeze period");
+ }
+ }
+ if (nextFreezeStart == null) {
+ // If no interval is after now, then it must be the one that starts at the beginning
+ // of next year
+ nextFreezeStart = sortedPeriods.get(0).toCurrentOrFutureRealDates(nowDate).first;
+ }
+ return dateToMillis(nextFreezeStart) - now;
+ }
+
+ /** @hide */
+ public void validateFreezePeriods() {
+ FreezeInterval.validatePeriods(mFreezePeriods);
+ }
+
+ /** @hide */
+ public void validateAgainstPreviousFreezePeriod(LocalDate prevPeriodStart,
+ LocalDate prevPeriodEnd, LocalDate now) {
+ FreezeInterval.validateAgainstPreviousFreezePeriod(mFreezePeriods, prevPeriodStart,
+ prevPeriodEnd, now);
+ }
+
+ /**
+ * An installation option represents how system update clients should act on incoming system
+ * updates and how long this action is valid for, given the current system update policy. Its
+ * action could be one of the following
+ * <ul>
+ * <li> {@code TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and without
+ * user intervention as soon as they become available.
+ * <li> {@code TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days
+ * <li> {@code TYPE_PAUSE} system updates should be postponed indefinitely until further notice
+ * </ul>
+ *
+ * The effective time measures how long this installation option is valid for from the queried
+ * time, in milliseconds.
+ *
+ * This is an internal API for system update clients.
+ * @hide
+ */
+ @SystemApi
+ public static class InstallationOption {
+ private final int mType;
+ private long mEffectiveTime;
+
+ InstallationOption(int type, long effectiveTime) {
+ this.mType = type;
+ this.mEffectiveTime = effectiveTime;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public long getEffectiveTime() {
+ return mEffectiveTime;
+ }
+
+ /** @hide */
+ protected void limitEffectiveTime(long otherTime) {
+ mEffectiveTime = Long.min(mEffectiveTime, otherTime);
}
}
+ /**
+ * Returns the installation option at the specified time, under the current
+ * {@code SystemUpdatePolicy} object. This is a convenience method for system update clients
+ * so they can instantiate this policy at any given time and find out what to do with incoming
+ * system updates, without the need of examining the overall policy structure.
+ *
+ * Normally the system update clients will query the current installation option by calling this
+ * method with the current timestamp, and act on the returned option until its effective time
+ * lapses. It can then query the latest option using a new timestamp. It should also listen
+ * for {@code DevicePolicyManager#ACTION_SYSTEM_UPDATE_POLICY_CHANGED} broadcast, in case the
+ * whole policy is updated.
+ *
+ * @param when At what time the intallation option is being queried, specified in number of
+ milliseonds since the epoch.
+ * @see InstallationOption
+ * @hide
+ */
+ @SystemApi
+ public InstallationOption getInstallationOptionAt(long when) {
+ LocalDate whenDate = millisToDate(when);
+ Pair<LocalDate, LocalDate> current = getCurrentFreezePeriod(whenDate);
+ if (current != null) {
+ return new InstallationOption(TYPE_PAUSE,
+ dateToMillis(roundUpLeapDay(current.second).plusDays(1)) - when);
+ }
+ // We are not within a freeze period, query the underlying policy.
+ // But also consider the start of the next freeze period, which might
+ // reduce the effective time of the current installation option
+ InstallationOption option = getInstallationOptionRegardlessFreezeAt(when);
+ if (mFreezePeriods.size() > 0) {
+ option.limitEffectiveTime(timeUntilNextFreezePeriod(when));
+ }
+ return option;
+ }
+
+ private InstallationOption getInstallationOptionRegardlessFreezeAt(long when) {
+ if (mPolicyType == TYPE_INSTALL_AUTOMATIC || mPolicyType == TYPE_POSTPONE) {
+ return new InstallationOption(mPolicyType, Long.MAX_VALUE);
+ } else if (mPolicyType == TYPE_INSTALL_WINDOWED) {
+ Calendar query = Calendar.getInstance();
+ query.setTimeInMillis(when);
+ // Calculate the number of milliseconds since midnight of the time specified by when
+ long whenMillis = TimeUnit.HOURS.toMillis(query.get(Calendar.HOUR_OF_DAY))
+ + TimeUnit.MINUTES.toMillis(query.get(Calendar.MINUTE))
+ + TimeUnit.SECONDS.toMillis(query.get(Calendar.SECOND))
+ + query.get(Calendar.MILLISECOND);
+ long windowStartMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowStart);
+ long windowEndMillis = TimeUnit.MINUTES.toMillis(mMaintenanceWindowEnd);
+ final long dayInMillis = TimeUnit.DAYS.toMillis(1);
+
+ if ((windowStartMillis <= whenMillis && whenMillis <= windowEndMillis)
+ || ((windowStartMillis > windowEndMillis)
+ && (windowStartMillis <= whenMillis || whenMillis <= windowEndMillis))) {
+ return new InstallationOption(TYPE_INSTALL_AUTOMATIC,
+ (windowEndMillis - whenMillis + dayInMillis) % dayInMillis);
+ } else {
+ return new InstallationOption(TYPE_PAUSE,
+ (windowStartMillis - whenMillis + dayInMillis) % dayInMillis);
+ }
+ } else {
+ throw new RuntimeException("Unknown policy type");
+ }
+ }
+
+ private static LocalDate roundUpLeapDay(LocalDate date) {
+ if (date.isLeapYear() && date.getMonthValue() == 2 && date.getDayOfMonth() == 28) {
+ return date.plusDays(1);
+ } else {
+ return date;
+ }
+ }
+
+ /** Convert a timestamp since epoch to a LocalDate using default timezone, truncating
+ * the hour/min/seconds part.
+ */
+ private static LocalDate millisToDate(long when) {
+ return Instant.ofEpochMilli(when).atZone(ZoneId.systemDefault()).toLocalDate();
+ }
+
+ /**
+ * Returns the timestamp since epoch of a LocalDate, assuming the time is 00:00:00.
+ */
+ private static long dateToMillis(LocalDate when) {
+ return LocalDateTime.of(when, LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant()
+ .toEpochMilli();
+ }
+
@Override
public String toString() {
- return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d)",
- mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd);
+ return String.format("SystemUpdatePolicy (type: %d, windowStart: %d, windowEnd: %d, "
+ + "freezes: [%s])",
+ mPolicyType, mMaintenanceWindowStart, mMaintenanceWindowEnd,
+ mFreezePeriods.stream().map(n -> n.toString()).collect(Collectors.joining(",")));
}
+ @SystemApi
@Override
public int describeContents() {
return 0;
}
+ @SystemApi
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mPolicyType);
dest.writeInt(mMaintenanceWindowStart);
dest.writeInt(mMaintenanceWindowEnd);
+ int freezeCount = mFreezePeriods.size();
+ dest.writeInt(freezeCount);
+ for (int i = 0; i < freezeCount; i++) {
+ FreezeInterval interval = mFreezePeriods.get(i);
+ dest.writeInt(interval.mStartDay);
+ dest.writeInt(interval.mEndDay);
+ }
}
+ @SystemApi
public static final Parcelable.Creator<SystemUpdatePolicy> CREATOR =
new Parcelable.Creator<SystemUpdatePolicy>() {
@@ -247,6 +683,12 @@ public class SystemUpdatePolicy implements Parcelable {
policy.mPolicyType = source.readInt();
policy.mMaintenanceWindowStart = source.readInt();
policy.mMaintenanceWindowEnd = source.readInt();
+ int freezeCount = source.readInt();
+ policy.mFreezePeriods.ensureCapacity(freezeCount);
+ for (int i = 0; i < freezeCount; i++) {
+ policy.mFreezePeriods.add(
+ new FreezeInterval(source.readInt(), source.readInt()));
+ }
return policy;
}
@@ -256,8 +698,10 @@ public class SystemUpdatePolicy implements Parcelable {
}
};
-
/**
+ * Restore a previously saved SystemUpdatePolicy from XML. No need to validate
+ * the reconstructed policy since the XML is supposed to be created by the
+ * system server from a validated policy object previously.
* @hide
*/
public static SystemUpdatePolicy restoreFromXml(XmlPullParser parser) {
@@ -275,10 +719,26 @@ public class SystemUpdatePolicy implements Parcelable {
if (value != null) {
policy.mMaintenanceWindowEnd = Integer.parseInt(value);
}
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != END_DOCUMENT
+ && (type != END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == END_TAG || type == TEXT) {
+ continue;
+ }
+ if (!parser.getName().equals(KEY_FREEZE_TAG)) {
+ continue;
+ }
+ policy.mFreezePeriods.add(new FreezeInterval(
+ Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_START)),
+ Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_END))));
+ }
return policy;
}
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException | XmlPullParserException | IOException e) {
// Fail through
+ Log.w(TAG, "Load xml failed", e);
}
return null;
}
@@ -290,6 +750,13 @@ public class SystemUpdatePolicy implements Parcelable {
out.attribute(null, KEY_POLICY_TYPE, Integer.toString(mPolicyType));
out.attribute(null, KEY_INSTALL_WINDOW_START, Integer.toString(mMaintenanceWindowStart));
out.attribute(null, KEY_INSTALL_WINDOW_END, Integer.toString(mMaintenanceWindowEnd));
+ for (int i = 0; i < mFreezePeriods.size(); i++) {
+ FreezeInterval interval = mFreezePeriods.get(i);
+ out.startTag(null, KEY_FREEZE_TAG);
+ out.attribute(null, KEY_FREEZE_START, Integer.toString(interval.mStartDay));
+ out.attribute(null, KEY_FREEZE_END, Integer.toString(interval.mEndDay));
+ out.endTag(null, KEY_FREEZE_TAG);
+ }
}
}
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index 87f22712..d5686624 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -4,6 +4,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.content.ComponentName;
+import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.net.Uri;
@@ -23,6 +24,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.View;
+import android.view.View.AutofillImportance;
import android.view.ViewRootImpl;
import android.view.ViewStructure;
import android.view.ViewStructure.HtmlInfo;
@@ -500,9 +502,8 @@ public class AssistStructure implements Parcelable {
ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false);
if ((root.getWindowFlags() & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
if (forAutoFill) {
- final int autofillFlags = (flags & FillRequest.FLAG_MANUAL_REQUEST) != 0
- ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
- view.onProvideAutofillStructure(builder, autofillFlags);
+ final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags);
+ view.onProvideAutofillStructure(builder, viewFlags);
} else {
// This is a secure window, so it doesn't want a screenshot, and that
// means we should also not copy out its view hierarchy for Assist
@@ -512,9 +513,8 @@ public class AssistStructure implements Parcelable {
}
}
if (forAutoFill) {
- final int autofillFlags = (flags & FillRequest.FLAG_MANUAL_REQUEST) != 0
- ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
- view.dispatchProvideAutofillStructure(builder, autofillFlags);
+ final int viewFlags = resolveViewAutofillFlags(view.getContext(), flags);
+ view.dispatchProvideAutofillStructure(builder, viewFlags);
} else {
view.dispatchProvideStructure(builder);
}
@@ -532,6 +532,12 @@ public class AssistStructure implements Parcelable {
mRoot = new ViewNode(reader, 0);
}
+ int resolveViewAutofillFlags(Context context, int fillRequestFlags) {
+ return (fillRequestFlags & FillRequest.FLAG_MANUAL_REQUEST) != 0
+ || context.isAutofillCompatibilityEnabled()
+ ? View.AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS : 0;
+ }
+
void writeSelfToParcel(Parcel out, PooledStringWriter pwriter, float[] tmpMatrix) {
out.writeInt(mX);
out.writeInt(mY);
@@ -627,6 +633,7 @@ public class AssistStructure implements Parcelable {
int mMaxEms = -1;
int mMaxLength = -1;
@Nullable String mTextIdEntry;
+ @AutofillImportance int mImportantForAutofill;
// POJO used to override some autofill-related values when the node is parcelized.
// Not written to parcel.
@@ -728,6 +735,7 @@ public class AssistStructure implements Parcelable {
mMaxEms = in.readInt();
mMaxLength = in.readInt();
mTextIdEntry = preader.readString();
+ mImportantForAutofill = in.readInt();
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
mX = in.readInt();
@@ -895,6 +903,7 @@ public class AssistStructure implements Parcelable {
out.writeInt(mMaxEms);
out.writeInt(mMaxLength);
pwriter.writeString(mTextIdEntry);
+ out.writeInt(mImportantForAutofill);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
out.writeInt(mX);
@@ -1294,6 +1303,22 @@ public class AssistStructure implements Parcelable {
}
/**
+ * @hide
+ */
+ public void setWebDomain(@Nullable String domain) {
+ if (domain == null) return;
+
+ final Uri uri = Uri.parse(domain);
+ if (uri == null) {
+ // Cannot log domain because it could contain PII;
+ Log.w(TAG, "Failed to parse web domain");
+ return;
+ }
+ mWebScheme = uri.getScheme();
+ mWebDomain = uri.getHost();
+ }
+
+ /**
* Returns the scheme of the HTML document represented by this view.
*
* <p>Typically used when the view associated with the view is a container for an HTML
@@ -1507,6 +1532,16 @@ public class AssistStructure implements Parcelable {
public int getMaxTextLength() {
return mMaxLength;
}
+
+ /**
+ * Gets the {@link View#setImportantForAutofill(int) importantForAutofill mode} of
+ * the view associated with this node.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes.
+ */
+ public @AutofillImportance int getImportantForAutofill() {
+ return mImportantForAutofill;
+ }
}
/**
@@ -1839,6 +1874,11 @@ public class AssistStructure implements Parcelable {
}
@Override
+ public void setImportantForAutofill(@AutofillImportance int mode) {
+ mNode.mImportantForAutofill = mode;
+ }
+
+ @Override
public void setInputType(int inputType) {
mNode.mInputType = inputType;
}
@@ -1865,14 +1905,7 @@ public class AssistStructure implements Parcelable {
@Override
public void setWebDomain(@Nullable String domain) {
- if (domain == null) {
- mNode.mWebScheme = null;
- mNode.mWebDomain = null;
- return;
- }
- Uri uri = Uri.parse(domain);
- mNode.mWebScheme = uri.getScheme();
- mNode.mWebDomain = uri.getHost();
+ mNode.setWebDomain(domain);
}
@Override
@@ -2139,7 +2172,8 @@ public class AssistStructure implements Parcelable {
+ ", options=" + Arrays.toString(node.getAutofillOptions())
+ ", hints=" + Arrays.toString(node.getAutofillHints())
+ ", value=" + node.getAutofillValue()
- + ", sanitized=" + node.isSanitized());
+ + ", sanitized=" + node.isSanitized()
+ + ", importantFor=" + node.getImportantForAutofill());
}
final int NCHILDREN = node.getChildCount();
@@ -2203,6 +2237,22 @@ public class AssistStructure implements Parcelable {
return mWindowNodes.get(index);
}
+ // TODO(b/35708678): temporary method that disable one-way warning flag on binder.
+ /** @hide */
+ public void ensureDataForAutofill() {
+ if (mHaveData) {
+ return;
+ }
+ mHaveData = true;
+ Binder.allowBlocking(mReceiveChannel);
+ try {
+ ParcelTransferReader reader = new ParcelTransferReader(mReceiveChannel);
+ reader.go();
+ } finally {
+ Binder.defaultBlocking(mReceiveChannel);
+ }
+ }
+
/** @hide */
public void ensureData() {
if (mHaveData) {
diff --git a/android/app/backup/BackupAgent.java b/android/app/backup/BackupAgent.java
index 861cb9a8..d1c957b8 100644
--- a/android/app/backup/BackupAgent.java
+++ b/android/app/backup/BackupAgent.java
@@ -18,6 +18,7 @@ package android.app.backup;
import android.app.IBackupAgent;
import android.app.QueuedWork;
+import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
@@ -143,6 +144,36 @@ public abstract class BackupAgent extends ContextWrapper {
/** @hide */
public static final int TYPE_SYMLINK = 3;
+ /**
+ * Flag for {@link BackupDataOutput#getTransportFlags()} and
+ * {@link FullBackupDataOutput#getTransportFlags()} only.
+ *
+ * <p>The transport has client-side encryption enabled. i.e., the user's backup has been
+ * encrypted with a key known only to the device, and not to the remote storage solution. Even
+ * if an attacker had root access to the remote storage provider they should not be able to
+ * decrypt the user's backup data.
+ */
+ public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1;
+
+ /**
+ * Flag for {@link BackupDataOutput#getTransportFlags()} and
+ * {@link FullBackupDataOutput#getTransportFlags()} only.
+ *
+ * <p>The transport is for a device-to-device transfer. There is no third party or intermediate
+ * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi.
+ */
+ public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2;
+
+ /**
+ * Flag for {@link BackupDataOutput#getTransportFlags()} and
+ * {@link FullBackupDataOutput#getTransportFlags()} only.
+ *
+ * <p>Used for internal testing only. Do not check this flag in production code.
+ *
+ * @hide
+ */
+ public static final int FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED = 1 << 31;
+
Handler mHandler = null;
Handler getHandler() {
@@ -313,8 +344,8 @@ public abstract class BackupAgent extends ContextWrapper {
return;
}
- Map<String, Set<String>> manifestIncludeMap;
- ArraySet<String> manifestExcludeSet;
+ Map<String, Set<PathWithRequiredFlags>> manifestIncludeMap;
+ ArraySet<PathWithRequiredFlags> manifestExcludeSet;
try {
manifestIncludeMap =
backupScheme.maybeParseAndGetCanonicalIncludePaths();
@@ -484,14 +515,13 @@ public abstract class BackupAgent extends ContextWrapper {
/**
* Check whether the xml yielded any <include/> tag for the provided <code>domainToken</code>.
* If so, perform a {@link #fullBackupFileTree} which backs up the file or recurses if the path
- * is a directory.
+ * is a directory, but only if all the required flags of the include rule are satisfied by
+ * the transport.
*/
private void applyXmlFiltersAndDoFullBackupForDomain(String packageName, String domainToken,
- Map<String, Set<String>> includeMap,
- ArraySet<String> filterSet,
- ArraySet<String> traversalExcludeSet,
- FullBackupDataOutput data)
- throws IOException {
+ Map<String, Set<PathWithRequiredFlags>> includeMap,
+ ArraySet<PathWithRequiredFlags> filterSet, ArraySet<String> traversalExcludeSet,
+ FullBackupDataOutput data) throws IOException {
if (includeMap == null || includeMap.size() == 0) {
// Do entire sub-tree for the provided token.
fullBackupFileTree(packageName, domainToken,
@@ -500,13 +530,22 @@ public abstract class BackupAgent extends ContextWrapper {
} else if (includeMap.get(domainToken) != null) {
// This will be null if the xml parsing didn't yield any rules for
// this domain (there may still be rules for other domains).
- for (String includeFile : includeMap.get(domainToken)) {
- fullBackupFileTree(packageName, domainToken, includeFile, filterSet,
- traversalExcludeSet, data);
+ for (PathWithRequiredFlags includeFile : includeMap.get(domainToken)) {
+ if (areIncludeRequiredTransportFlagsSatisfied(includeFile.getRequiredFlags(),
+ data.getTransportFlags())) {
+ fullBackupFileTree(packageName, domainToken, includeFile.getPath(), filterSet,
+ traversalExcludeSet, data);
+ }
}
}
}
+ private boolean areIncludeRequiredTransportFlagsSatisfied(int includeFlags,
+ int transportFlags) {
+ // all bits that are set in includeFlags must also be set in transportFlags
+ return (transportFlags & includeFlags) == includeFlags;
+ }
+
/**
* Write an entire file as part of a full-backup operation. The file's contents
* will be delivered to the backup destination along with the metadata necessary
@@ -653,7 +692,7 @@ public abstract class BackupAgent extends ContextWrapper {
* @hide
*/
protected final void fullBackupFileTree(String packageName, String domain, String startingPath,
- ArraySet<String> manifestExcludes,
+ ArraySet<PathWithRequiredFlags> manifestExcludes,
ArraySet<String> systemExcludes,
FullBackupDataOutput output) {
// Pull out the domain and set it aside to use when making the tarball.
@@ -684,7 +723,8 @@ public abstract class BackupAgent extends ContextWrapper {
filePath = file.getCanonicalPath();
// prune this subtree?
- if (manifestExcludes != null && manifestExcludes.contains(filePath)) {
+ if (manifestExcludes != null
+ && manifestExcludesContainFilePath(manifestExcludes, filePath)) {
continue;
}
if (systemExcludes != null && systemExcludes.contains(filePath)) {
@@ -720,6 +760,17 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
+ private boolean manifestExcludesContainFilePath(
+ ArraySet<PathWithRequiredFlags> manifestExcludes, String filePath) {
+ for (PathWithRequiredFlags exclude : manifestExcludes) {
+ String excludePath = exclude.getPath();
+ if (excludePath != null && excludePath.equals(filePath)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Handle the data delivered via the given file descriptor during a full restore
* operation. The agent is given the path to the file's original location as well
@@ -766,8 +817,8 @@ public abstract class BackupAgent extends ContextWrapper {
return false;
}
- Map<String, Set<String>> includes = null;
- ArraySet<String> excludes = null;
+ Map<String, Set<PathWithRequiredFlags>> includes = null;
+ ArraySet<PathWithRequiredFlags> excludes = null;
final String destinationCanonicalPath = destination.getCanonicalPath();
try {
includes = bs.maybeParseAndGetCanonicalIncludePaths();
@@ -796,7 +847,7 @@ public abstract class BackupAgent extends ContextWrapper {
// Rather than figure out the <include/> domain based on the path (a lot of code, and
// it's a small list), we'll go through and look for it.
boolean explicitlyIncluded = false;
- for (Set<String> domainIncludes : includes.values()) {
+ for (Set<PathWithRequiredFlags> domainIncludes : includes.values()) {
explicitlyIncluded |= isFileSpecifiedInPathList(destination, domainIncludes);
if (explicitlyIncluded) {
break;
@@ -819,9 +870,10 @@ public abstract class BackupAgent extends ContextWrapper {
* @return True if the provided file is either directly in the provided list, or the provided
* file is within a directory in the list.
*/
- private boolean isFileSpecifiedInPathList(File file, Collection<String> canonicalPathList)
- throws IOException {
- for (String canonicalPath : canonicalPathList) {
+ private boolean isFileSpecifiedInPathList(File file,
+ Collection<PathWithRequiredFlags> canonicalPathList) throws IOException {
+ for (PathWithRequiredFlags canonical : canonicalPathList) {
+ String canonicalPath = canonical.getPath();
File fileFromList = new File(canonicalPath);
if (fileFromList.isDirectory()) {
if (file.isDirectory()) {
@@ -920,12 +972,14 @@ public abstract class BackupAgent extends ContextWrapper {
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState,
- long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException {
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags)
+ throws RemoteException {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
if (DEBUG) Log.v(TAG, "doBackup() invoked");
- BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes);
+ BackupDataOutput output = new BackupDataOutput(
+ data.getFileDescriptor(), quotaBytes, transportFlags);
try {
BackupAgent.this.onBackup(oldState, output, newState);
@@ -999,7 +1053,7 @@ public abstract class BackupAgent extends ContextWrapper {
@Override
public void doFullBackup(ParcelFileDescriptor data,
- long quotaBytes, int token, IBackupManager callbackBinder) {
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
@@ -1010,7 +1064,8 @@ public abstract class BackupAgent extends ContextWrapper {
waitForSharedPrefs();
try {
- BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes));
+ BackupAgent.this.onFullBackup(new FullBackupDataOutput(
+ data, quotaBytes, transportFlags));
} catch (IOException ex) {
Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw new RuntimeException(ex);
@@ -1044,10 +1099,12 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
- public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) {
+ public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
+ int transportFlags) {
// Ensure that we're running with the app's normal permission level
final long ident = Binder.clearCallingIdentity();
- FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes);
+ FullBackupDataOutput measureOutput =
+ new FullBackupDataOutput(quotaBytes, transportFlags);
waitForSharedPrefs();
try {
diff --git a/android/app/backup/BackupDataOutput.java b/android/app/backup/BackupDataOutput.java
index c7586a29..5a66f340 100644
--- a/android/app/backup/BackupDataOutput.java
+++ b/android/app/backup/BackupDataOutput.java
@@ -18,6 +18,7 @@ package android.app.backup;
import android.annotation.SystemApi;
import android.os.ParcelFileDescriptor;
+
import java.io.FileDescriptor;
import java.io.IOException;
@@ -62,7 +63,10 @@ import java.io.IOException;
* @see BackupAgent
*/
public class BackupDataOutput {
- final long mQuota;
+
+ private final long mQuota;
+ private final int mTransportFlags;
+
long mBackupWriter;
/**
@@ -71,14 +75,20 @@ public class BackupDataOutput {
* @hide */
@SystemApi
public BackupDataOutput(FileDescriptor fd) {
- this(fd, -1);
+ this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
}
/** @hide */
@SystemApi
public BackupDataOutput(FileDescriptor fd, long quota) {
+ this(fd, quota, /*transportFlags=*/ 0);
+ }
+
+ /** @hide */
+ public BackupDataOutput(FileDescriptor fd, long quota, int transportFlags) {
if (fd == null) throw new NullPointerException();
mQuota = quota;
+ mTransportFlags = transportFlags;
mBackupWriter = ctor(fd);
if (mBackupWriter == 0) {
throw new RuntimeException("Native initialization failed with fd=" + fd);
@@ -96,6 +106,16 @@ public class BackupDataOutput {
}
/**
+ * Returns flags with additional information about the backup transport. For supported flags see
+ * {@link android.app.backup.BackupAgent}
+ *
+ * @see FullBackupDataOutput#getTransportFlags()
+ */
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
+ /**
* Mark the beginning of one record in the backup data stream. This must be called before
* {@link #writeEntityData}.
* @param key A string key that uniquely identifies the data record within the application.
diff --git a/android/app/backup/BackupManager.java b/android/app/backup/BackupManager.java
index 12f44831..debc32bd 100644
--- a/android/app/backup/BackupManager.java
+++ b/android/app/backup/BackupManager.java
@@ -19,6 +19,7 @@ package android.app.backup;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -252,6 +253,8 @@ public class BackupManager {
}
/**
+ * @deprecated Since Android P app can no longer request restoring of its backup.
+ *
* Restore the calling application from backup. The data will be restored from the
* current backup dataset if the application has stored data there, or from
* the dataset used during the last full device setup operation if the current
@@ -269,6 +272,7 @@ public class BackupManager {
*
* @return Zero on success; nonzero on error.
*/
+ @Deprecated
public int requestRestore(RestoreObserver observer) {
return requestRestore(observer, null);
}
@@ -276,6 +280,8 @@ public class BackupManager {
// system APIs start here
/**
+ * @deprecated Since Android P app can no longer request restoring of its backup.
+ *
* Restore the calling application from backup. The data will be restored from the
* current backup dataset if the application has stored data there, or from
* the dataset used during the last full device setup operation if the current
@@ -298,28 +304,12 @@ public class BackupManager {
*
* @hide
*/
+ @Deprecated
@SystemApi
public int requestRestore(RestoreObserver observer, BackupManagerMonitor monitor) {
- int result = -1;
- checkServiceBinder();
- if (sService != null) {
- RestoreSession session = null;
- try {
- IRestoreSession binder = sService.beginRestoreSession(mContext.getPackageName(),
- null);
- if (binder != null) {
- session = new RestoreSession(mContext, binder);
- result = session.restorePackage(mContext.getPackageName(), observer, monitor);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "restoreSelf() unable to contact service");
- } finally {
- if (session != null) {
- session.endRestoreSession();
- }
- }
- }
- return result;
+ Log.w(TAG, "requestRestore(): Since Android P app can no longer request restoring"
+ + " of its backup.");
+ return -1;
}
/**
@@ -726,6 +716,92 @@ public class BackupManager {
}
}
+ /**
+ * Returns an {@link Intent} for the specified transport's configuration UI.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public Intent getConfigurationIntent(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getConfigurationIntent(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getConfigurationIntent() couldn't connect");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a {@link String} describing where the specified transport is sending data.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public String getDestinationString(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getDestinationString(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDestinationString() couldn't connect");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns an {@link Intent} for the specified transport's data management UI.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public Intent getDataManagementIntent(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getDataManagementIntent(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDataManagementIntent() couldn't connect");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a {@link String} describing what the specified transport's data management intent is
+ * used for.
+ * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String,
+ * Intent, String)}.
+ *
+ * @param transportName The name of the registered transport.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public String getDataManagementLabel(String transportName) {
+ if (sService != null) {
+ try {
+ return sService.getDataManagementLabel(transportName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDataManagementLabel() couldn't connect");
+ }
+ }
+ return null;
+ }
+
/*
* We wrap incoming binder calls with a private class implementation that
* redirects them into main-thread actions. This serializes the backup
diff --git a/android/app/backup/BackupManagerMonitor.java b/android/app/backup/BackupManagerMonitor.java
index a91aded1..07e7688a 100644
--- a/android/app/backup/BackupManagerMonitor.java
+++ b/android/app/backup/BackupManagerMonitor.java
@@ -174,7 +174,6 @@ public class BackupManagerMonitor {
/**
* The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}.
- * @hide
*/
public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51;
diff --git a/android/app/backup/BackupTransport.java b/android/app/backup/BackupTransport.java
index 266f58df..0963594b 100644
--- a/android/app/backup/BackupTransport.java
+++ b/android/app/backup/BackupTransport.java
@@ -60,8 +60,6 @@ public class BackupTransport {
*
* <p>This is only valid when backup manager called {@link
* #performBackup(PackageInfo, ParcelFileDescriptor, int)} with {@link #FLAG_INCREMENTAL}.
- *
- * @hide
*/
public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006;
@@ -73,7 +71,7 @@ public class BackupTransport {
* For key value backup, indicates that the backup data is a diff from a previous backup. The
* transport must apply this diff to an existing backup to build the new backup set.
*
- * @hide
+ * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
*/
public static final int FLAG_INCREMENTAL = 1 << 1;
@@ -81,10 +79,17 @@ public class BackupTransport {
* For key value backup, indicates that the backup data is a complete set, not a diff from a
* previous backup. The transport should clear any previous backup when storing this backup.
*
- * @hide
+ * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
*/
public static final int FLAG_NON_INCREMENTAL = 1 << 2;
+ /**
+ * Used as a boolean extra in the binding intent of transports. We pass {@code true} to
+ * notify transports that the current connection is used for registering the transport.
+ */
+ public static final String EXTRA_TRANSPORT_REGISTRATION =
+ "android.app.backup.extra.TRANSPORT_REGISTRATION";
+
IBackupTransport mBinderImpl = new TransportImpl();
public IBinder getBinder() {
@@ -609,6 +614,15 @@ public class BackupTransport {
}
/**
+ * Returns flags with additional information about the transport, which is accessible to the
+ * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to do based on
+ * properties of the transport.
+ */
+ public int getTransportFlags() {
+ return 0;
+ }
+
+ /**
* Bridge between the actual IBackupTransport implementation and the stable API. If the
* binder interface needs to change, we use this layer to translate so that we can
* (if appropriate) decouple those framework-side changes from the BackupTransport
@@ -740,6 +754,11 @@ public class BackupTransport {
}
@Override
+ public int getTransportFlags() {
+ return BackupTransport.this.getTransportFlags();
+ }
+
+ @Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return BackupTransport.this.getNextFullRestoreDataChunk(socket);
}
diff --git a/android/app/backup/FullBackup.java b/android/app/backup/FullBackup.java
index a5dd5bd3..b7a8da59 100644
--- a/android/app/backup/FullBackup.java
+++ b/android/app/backup/FullBackup.java
@@ -82,6 +82,11 @@ public class FullBackup {
public static final String FULL_RESTORE_INTENT_ACTION = "fullrest";
public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken";
+ public static final String FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION = "clientSideEncryption";
+ public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer";
+ public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION =
+ "fakeClientSideEncryption";
+
/**
* @hide
*/
@@ -224,6 +229,9 @@ public class FullBackup {
private final File EXTERNAL_DIR;
+ private final static String TAG_INCLUDE = "include";
+ private final static String TAG_EXCLUDE = "exclude";
+
final int mFullBackupContent;
final PackageManager mPackageManager;
final StorageManager mStorageManager;
@@ -303,15 +311,45 @@ public class FullBackup {
}
/**
- * A map of domain -> list of canonical file names in that domain that are to be included.
- * We keep track of the domain so that we can go through the file system in order later on.
- */
- Map<String, Set<String>> mIncludes;
- /**e
- * List that will be populated with the canonical names of each file or directory that is
- * to be excluded.
+ * Represents a path attribute specified in an <include /> rule along with optional
+ * transport flags required from the transport to include file(s) under that path as
+ * specified by requiredFlags attribute. If optional requiredFlags attribute is not
+ * provided, default requiredFlags to 0.
+ * Note: since our parsing codepaths were the same for <include /> and <exclude /> tags,
+ * this structure is also used for <exclude /> tags to preserve that, however you can expect
+ * the getRequiredFlags() to always return 0 for exclude rules.
+ */
+ public static class PathWithRequiredFlags {
+ private final String mPath;
+ private final int mRequiredFlags;
+
+ public PathWithRequiredFlags(String path, int requiredFlags) {
+ mPath = path;
+ mRequiredFlags = requiredFlags;
+ }
+
+ public String getPath() {
+ return mPath;
+ }
+
+ public int getRequiredFlags() {
+ return mRequiredFlags;
+ }
+ }
+
+ /**
+ * A map of domain -> set of pairs (canonical file; required transport flags) in that
+ * domain that are to be included if the transport has decared the required flags.
+ * We keep track of the domain so that we can go through the file system in order later on.
+ */
+ Map<String, Set<PathWithRequiredFlags>> mIncludes;
+
+ /**
+ * Set that will be populated with pairs (canonical file; requiredFlags=0) for each file or
+ * directory that is to be excluded. Note that for excludes, the requiredFlags attribute is
+ * ignored and the value should be always set to 0.
*/
- ArraySet<String> mExcludes;
+ ArraySet<PathWithRequiredFlags> mExcludes;
BackupScheme(Context context) {
mFullBackupContent = context.getApplicationInfo().fullBackupContent;
@@ -356,13 +394,14 @@ public class FullBackup {
}
/**
- * @return A mapping of domain -> canonical paths within that domain. Each of these paths
- * specifies a file that the client has explicitly included in their backup set. If this
- * map is empty we will back up the entire data directory (including managed external
- * storage).
+ * @return A mapping of domain -> set of pairs (canonical file; required transport flags)
+ * in that domain that are to be included if the transport has decared the required flags.
+ * Each of these paths specifies a file that the client has explicitly included in their
+ * backup set. If this map is empty we will back up the entire data directory (including
+ * managed external storage).
*/
- public synchronized Map<String, Set<String>> maybeParseAndGetCanonicalIncludePaths()
- throws IOException, XmlPullParserException {
+ public synchronized Map<String, Set<PathWithRequiredFlags>>
+ maybeParseAndGetCanonicalIncludePaths() throws IOException, XmlPullParserException {
if (mIncludes == null) {
maybeParseBackupSchemeLocked();
}
@@ -370,9 +409,10 @@ public class FullBackup {
}
/**
- * @return A set of canonical paths that are to be excluded from the backup/restore set.
+ * @return A set of (canonical paths; requiredFlags=0) that are to be excluded from the
+ * backup/restore set.
*/
- public synchronized ArraySet<String> maybeParseAndGetCanonicalExcludePaths()
+ public synchronized ArraySet<PathWithRequiredFlags> maybeParseAndGetCanonicalExcludePaths()
throws IOException, XmlPullParserException {
if (mExcludes == null) {
maybeParseBackupSchemeLocked();
@@ -382,8 +422,8 @@ public class FullBackup {
private void maybeParseBackupSchemeLocked() throws IOException, XmlPullParserException {
// This not being null is how we know that we've tried to parse the xml already.
- mIncludes = new ArrayMap<String, Set<String>>();
- mExcludes = new ArraySet<String>();
+ mIncludes = new ArrayMap<String, Set<PathWithRequiredFlags>>();
+ mExcludes = new ArraySet<PathWithRequiredFlags>();
if (mFullBackupContent == 0) {
// android:fullBackupContent="true" which means that we'll do everything.
@@ -415,8 +455,8 @@ public class FullBackup {
@VisibleForTesting
public void parseBackupSchemeFromXmlLocked(XmlPullParser parser,
- Set<String> excludes,
- Map<String, Set<String>> includes)
+ Set<PathWithRequiredFlags> excludes,
+ Map<String, Set<PathWithRequiredFlags>> includes)
throws IOException, XmlPullParserException {
int event = parser.getEventType(); // START_DOCUMENT
while (event != XmlPullParser.START_TAG) {
@@ -441,8 +481,7 @@ public class FullBackup {
case XmlPullParser.START_TAG:
validateInnerTagContents(parser);
final String domainFromXml = parser.getAttributeValue(null, "domain");
- final File domainDirectory =
- getDirectoryForCriteriaDomain(domainFromXml);
+ final File domainDirectory = getDirectoryForCriteriaDomain(domainFromXml);
if (domainDirectory == null) {
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...parsing \"" + parser.getName() + "\": "
@@ -457,12 +496,23 @@ public class FullBackup {
break;
}
- Set<String> activeSet = parseCurrentTagForDomain(
+ int requiredFlags = 0; // no transport flags are required by default
+ if (TAG_INCLUDE.equals(parser.getName())) {
+ // requiredFlags are only supported for <include /> tag, for <exclude />
+ // we should always leave them as the default = 0
+ requiredFlags = getRequiredFlagsFromString(
+ parser.getAttributeValue(null, "requireFlags"));
+ }
+
+ // retrieve the include/exclude set we'll be adding this rule to
+ Set<PathWithRequiredFlags> activeSet = parseCurrentTagForDomain(
parser, excludes, includes, domainFromXml);
- activeSet.add(canonicalFile.getCanonicalPath());
+ activeSet.add(new PathWithRequiredFlags(canonicalFile.getCanonicalPath(),
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...parsed " + canonicalFile.getCanonicalPath()
- + " for domain \"" + domainFromXml + "\"");
+ + " for domain \"" + domainFromXml + "\", requiredFlags + \""
+ + requiredFlags + "\"");
}
// Special case journal files (not dirs) for sqlite database. frowny-face.
@@ -472,14 +522,16 @@ public class FullBackup {
if ("database".equals(domainFromXml) && !canonicalFile.isDirectory()) {
final String canonicalJournalPath =
canonicalFile.getCanonicalPath() + "-journal";
- activeSet.add(canonicalJournalPath);
+ activeSet.add(new PathWithRequiredFlags(canonicalJournalPath,
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalJournalPath + ". Ignore if nonexistent.");
}
final String canonicalWalPath =
canonicalFile.getCanonicalPath() + "-wal";
- activeSet.add(canonicalWalPath);
+ activeSet.add(new PathWithRequiredFlags(canonicalWalPath,
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalWalPath + ". Ignore if nonexistent.");
@@ -491,7 +543,8 @@ public class FullBackup {
!canonicalFile.getCanonicalPath().endsWith(".xml")) {
final String canonicalXmlPath =
canonicalFile.getCanonicalPath() + ".xml";
- activeSet.add(canonicalXmlPath);
+ activeSet.add(new PathWithRequiredFlags(canonicalXmlPath,
+ requiredFlags));
if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
Log.v(TAG_XML_PARSER, "...automatically generated "
+ canonicalXmlPath + ". Ignore if nonexistent.");
@@ -508,10 +561,12 @@ public class FullBackup {
Log.v(TAG_XML_PARSER, " ...nothing specified (This means the entirety of app"
+ " data minus excludes)");
} else {
- for (Map.Entry<String, Set<String>> entry : includes.entrySet()) {
+ for (Map.Entry<String, Set<PathWithRequiredFlags>> entry
+ : includes.entrySet()) {
Log.v(TAG_XML_PARSER, " domain=" + entry.getKey());
- for (String includeData : entry.getValue()) {
- Log.v(TAG_XML_PARSER, " " + includeData);
+ for (PathWithRequiredFlags includeData : entry.getValue()) {
+ Log.v(TAG_XML_PARSER, " path: " + includeData.getPath()
+ + " requiredFlags: " + includeData.getRequiredFlags());
}
}
}
@@ -520,8 +575,9 @@ public class FullBackup {
if (excludes.isEmpty()) {
Log.v(TAG_XML_PARSER, " ...nothing to exclude.");
} else {
- for (String excludeData : excludes) {
- Log.v(TAG_XML_PARSER, " " + excludeData);
+ for (PathWithRequiredFlags excludeData : excludes) {
+ Log.v(TAG_XML_PARSER, " path: " + excludeData.getPath()
+ + " requiredFlags: " + excludeData.getRequiredFlags());
}
}
@@ -531,20 +587,43 @@ public class FullBackup {
}
}
- private Set<String> parseCurrentTagForDomain(XmlPullParser parser,
- Set<String> excludes,
- Map<String, Set<String>> includes,
- String domain)
+ private int getRequiredFlagsFromString(String requiredFlags) {
+ int flags = 0;
+ if (requiredFlags == null || requiredFlags.length() == 0) {
+ // requiredFlags attribute was missing or empty in <include /> tag
+ return flags;
+ }
+ String[] flagsStr = requiredFlags.split("\\|");
+ for (String f : flagsStr) {
+ switch (f) {
+ case FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION:
+ flags |= BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ break;
+ case FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER:
+ flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER;
+ break;
+ case FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION:
+ flags |= BackupAgent.FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ default:
+ Log.w(TAG, "Unrecognized requiredFlag provided, value: \"" + f + "\"");
+ }
+ }
+ return flags;
+ }
+
+ private Set<PathWithRequiredFlags> parseCurrentTagForDomain(XmlPullParser parser,
+ Set<PathWithRequiredFlags> excludes,
+ Map<String, Set<PathWithRequiredFlags>> includes, String domain)
throws XmlPullParserException {
- if ("include".equals(parser.getName())) {
+ if (TAG_INCLUDE.equals(parser.getName())) {
final String domainToken = getTokenForXmlDomain(domain);
- Set<String> includeSet = includes.get(domainToken);
+ Set<PathWithRequiredFlags> includeSet = includes.get(domainToken);
if (includeSet == null) {
- includeSet = new ArraySet<String>();
+ includeSet = new ArraySet<PathWithRequiredFlags>();
includes.put(domainToken, includeSet);
}
return includeSet;
- } else if ("exclude".equals(parser.getName())) {
+ } else if (TAG_EXCLUDE.equals(parser.getName())) {
return excludes;
} else {
// Unrecognised tag => hard failure.
@@ -589,8 +668,8 @@ public class FullBackup {
/**
*
* @param domain Directory where the specified file should exist. Not null.
- * @param filePathFromXml parsed from xml. Not sanitised before calling this function so may be
- * null.
+ * @param filePathFromXml parsed from xml. Not sanitised before calling this function so may
+ * be null.
* @return The canonical path of the file specified or null if no such file exists.
*/
private File extractCanonicalFile(File domain, String filePathFromXml) {
@@ -650,15 +729,27 @@ public class FullBackup {
* Let's be strict about the type of xml the client can write. If we see anything untoward,
* throw an XmlPullParserException.
*/
- private void validateInnerTagContents(XmlPullParser parser)
- throws XmlPullParserException {
- if (parser.getAttributeCount() > 2) {
- throw new XmlPullParserException("At most 2 tag attributes allowed for \""
- + parser.getName() + "\" tag (\"domain\" & \"path\".");
+ private void validateInnerTagContents(XmlPullParser parser) throws XmlPullParserException {
+ if (parser == null) {
+ return;
}
- if (!"include".equals(parser.getName()) && !"exclude".equals(parser.getName())) {
- throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" +
- " \"<exclude/>. You provided \"" + parser.getName() + "\"");
+ switch (parser.getName()) {
+ case TAG_INCLUDE:
+ if (parser.getAttributeCount() > 3) {
+ throw new XmlPullParserException("At most 3 tag attributes allowed for "
+ + "\"include\" tag (\"domain\" & \"path\""
+ + " & optional \"requiredFlags\").");
+ }
+ break;
+ case TAG_EXCLUDE:
+ if (parser.getAttributeCount() > 2) {
+ throw new XmlPullParserException("At most 2 tag attributes allowed for "
+ + "\"exclude\" tag (\"domain\" & \"path\".");
+ }
+ break;
+ default:
+ throw new XmlPullParserException("A valid tag is one of \"<include/>\" or" +
+ " \"<exclude/>. You provided \"" + parser.getName() + "\"");
}
}
}
diff --git a/android/app/backup/FullBackupDataOutput.java b/android/app/backup/FullBackupDataOutput.java
index 5deedd03..18f42833 100644
--- a/android/app/backup/FullBackupDataOutput.java
+++ b/android/app/backup/FullBackupDataOutput.java
@@ -11,6 +11,7 @@ public class FullBackupDataOutput {
// Currently a name-scoping shim around BackupDataOutput
private final BackupDataOutput mData;
private final long mQuota;
+ private final int mTransportFlags;
private long mSize;
/**
@@ -23,22 +24,49 @@ public class FullBackupDataOutput {
return mQuota;
}
+ /**
+ * Returns flags with additional information about the backup transport. For supported flags see
+ * {@link android.app.backup.BackupAgent}
+ *
+ * @see BackupDataOutput#getTransportFlags()
+ */
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
/** @hide - used only in measure operation */
public FullBackupDataOutput(long quota) {
mData = null;
mQuota = quota;
mSize = 0;
+ mTransportFlags = 0;
+ }
+
+ /** @hide - used only in measure operation */
+ public FullBackupDataOutput(long quota, int transportFlags) {
+ mData = null;
+ mQuota = quota;
+ mSize = 0;
+ mTransportFlags = transportFlags;
}
/** @hide */
public FullBackupDataOutput(ParcelFileDescriptor fd, long quota) {
- mData = new BackupDataOutput(fd.getFileDescriptor(), quota);
+ mData = new BackupDataOutput(fd.getFileDescriptor(), quota, 0);
+ mQuota = quota;
+ mTransportFlags = 0;
+ }
+
+ /** @hide */
+ public FullBackupDataOutput(ParcelFileDescriptor fd, long quota, int transportFlags) {
+ mData = new BackupDataOutput(fd.getFileDescriptor(), quota, transportFlags);
mQuota = quota;
+ mTransportFlags = transportFlags;
}
/** @hide - used only internally to the backup manager service's stream construction */
public FullBackupDataOutput(ParcelFileDescriptor fd) {
- this(fd, -1);
+ this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
}
/** @hide */
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index cba9dcc3..02afcc7c 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -65,7 +65,6 @@ public class JobInfo implements Parcelable {
NETWORK_TYPE_UNMETERED,
NETWORK_TYPE_NOT_ROAMING,
NETWORK_TYPE_CELLULAR,
- NETWORK_TYPE_METERED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface NetworkType {}
@@ -253,7 +252,15 @@ public class JobInfo implements Parcelable {
/**
* @hide
*/
- public static final int FLAG_IS_PREFETCH = 1 << 2;
+ public static final int FLAG_PREFETCH = 1 << 2;
+
+ /**
+ * This job needs to be exempted from the app standby throttling. Only the system (UID 1000)
+ * can set it. Jobs with a time constrant must not have it.
+ *
+ * @hide
+ */
+ public static final int FLAG_EXEMPT_FROM_APP_STANDBY = 1 << 3;
/**
* @hide
@@ -288,7 +295,8 @@ public class JobInfo implements Parcelable {
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
private final NetworkRequest networkRequest;
- private final long networkBytes;
+ private final long networkDownloadBytes;
+ private final long networkUploadBytes;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
private final boolean isPeriodic;
@@ -309,30 +317,28 @@ public class JobInfo implements Parcelable {
}
/**
- * Bundle of extras which are returned to your application at execution time.
+ * @see JobInfo.Builder#setExtras(PersistableBundle)
*/
public @NonNull PersistableBundle getExtras() {
return extras;
}
/**
- * Bundle of transient extras which are returned to your application at execution time,
- * but not persisted by the system.
+ * @see JobInfo.Builder#setTransientExtras(Bundle)
*/
public @NonNull Bundle getTransientExtras() {
return transientExtras;
}
/**
- * ClipData of information that is returned to your application at execution time,
- * but not persisted by the system.
+ * @see JobInfo.Builder#setClipData(ClipData, int)
*/
public @Nullable ClipData getClipData() {
return clipData;
}
/**
- * Permission grants that go along with {@link #getClipData}.
+ * @see JobInfo.Builder#setClipData(ClipData, int)
*/
public int getClipGrantFlags() {
return clipGrantFlags;
@@ -355,33 +361,34 @@ public class JobInfo implements Parcelable {
return flags;
}
+ /** @hide */
+ public boolean isExemptedFromAppStandby() {
+ return ((flags & FLAG_EXEMPT_FROM_APP_STANDBY) != 0) && !isPeriodic();
+ }
+
/**
- * Whether this job requires that the device be charging (or be a non-battery-powered
- * device connected to permanent power, such as Android TV devices).
+ * @see JobInfo.Builder#setRequiresCharging(boolean)
*/
public boolean isRequireCharging() {
return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
}
/**
- * Whether this job needs the device's battery level to not be at below the critical threshold.
+ * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
*/
public boolean isRequireBatteryNotLow() {
return (constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0;
}
/**
- * Whether this job requires that the user <em>not</em> be interacting with the device.
- *
- * <p class="note">This is <em>not</em> the same as "doze" or "device idle";
- * it is purely about the user's direct interactions.</p>
+ * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
*/
public boolean isRequireDeviceIdle() {
return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
}
/**
- * Whether this job needs the device's storage to not be low.
+ * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
*/
public boolean isRequireStorageNotLow() {
return (constraintFlags & CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0;
@@ -397,6 +404,7 @@ public class JobInfo implements Parcelable {
/**
* Which content: URIs must change for the job to be scheduled. Returns null
* if there are none required.
+ * @see JobInfo.Builder#addTriggerContentUri(TriggerContentUri)
*/
public @Nullable TriggerContentUri[] getTriggerContentUris() {
return triggerContentUris;
@@ -405,6 +413,7 @@ public class JobInfo implements Parcelable {
/**
* When triggering on content URI changes, this is the delay from when a change
* is detected until the job is scheduled.
+ * @see JobInfo.Builder#setTriggerContentUpdateDelay(long)
*/
public long getTriggerContentUpdateDelay() {
return triggerContentUpdateDelay;
@@ -413,6 +422,7 @@ public class JobInfo implements Parcelable {
/**
* When triggering on content URI changes, this is the maximum delay we will
* use before scheduling the job.
+ * @see JobInfo.Builder#setTriggerContentMaxDelay(long)
*/
public long getTriggerContentMaxDelay() {
return triggerContentMaxDelay;
@@ -453,28 +463,59 @@ public class JobInfo implements Parcelable {
}
/**
- * Return the estimated size of network traffic that will be performed by
+ * @deprecated replaced by {@link #getEstimatedNetworkDownloadBytes()} and
+ * {@link #getEstimatedNetworkUploadBytes()}.
+ * @removed
+ */
+ @Deprecated
+ public @BytesLong long getEstimatedNetworkBytes() {
+ if (networkDownloadBytes == NETWORK_BYTES_UNKNOWN
+ && networkUploadBytes == NETWORK_BYTES_UNKNOWN) {
+ return NETWORK_BYTES_UNKNOWN;
+ } else if (networkDownloadBytes == NETWORK_BYTES_UNKNOWN) {
+ return networkUploadBytes;
+ } else if (networkUploadBytes == NETWORK_BYTES_UNKNOWN) {
+ return networkDownloadBytes;
+ } else {
+ return networkDownloadBytes + networkUploadBytes;
+ }
+ }
+
+ /**
+ * Return the estimated size of download traffic that will be performed by
* this job, in bytes.
*
- * @return Estimated size of network traffic, or
+ * @return Estimated size of download traffic, or
* {@link #NETWORK_BYTES_UNKNOWN} when unknown.
- * @see Builder#setEstimatedNetworkBytes(long)
+ * @see Builder#setEstimatedNetworkBytes(long, long)
*/
- public @BytesLong long getEstimatedNetworkBytes() {
- return networkBytes;
+ public @BytesLong long getEstimatedNetworkDownloadBytes() {
+ return networkDownloadBytes;
+ }
+
+ /**
+ * Return the estimated size of upload traffic that will be performed by
+ * this job, in bytes.
+ *
+ * @return Estimated size of upload traffic, or
+ * {@link #NETWORK_BYTES_UNKNOWN} when unknown.
+ * @see Builder#setEstimatedNetworkBytes(long, long)
+ */
+ public @BytesLong long getEstimatedNetworkUploadBytes() {
+ return networkUploadBytes;
}
/**
* Set for a job that does not recur periodically, to specify a delay after which the job
* will be eligible for execution. This value is not set if the job recurs periodically.
+ * @see JobInfo.Builder#setMinimumLatency(long)
*/
public long getMinLatencyMillis() {
return minLatencyMillis;
}
/**
- * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the job recurs
- * periodically.
+ * @see JobInfo.Builder#setOverrideDeadline(long)
*/
public long getMaxExecutionDelayMillis() {
return maxExecutionDelayMillis;
@@ -482,13 +523,15 @@ public class JobInfo implements Parcelable {
/**
* Track whether this job will repeat with a given period.
+ * @see JobInfo.Builder#setPeriodic(long)
+ * @see JobInfo.Builder#setPeriodic(long, long)
*/
public boolean isPeriodic() {
return isPeriodic;
}
/**
- * @return Whether or not this job should be persisted across device reboots.
+ * @see JobInfo.Builder#setPersisted(boolean)
*/
public boolean isPersisted() {
return isPersisted;
@@ -497,6 +540,8 @@ public class JobInfo implements Parcelable {
/**
* Set to the interval between occurrences of this job. This value is <b>not</b> set if the
* job does not recur periodically.
+ * @see JobInfo.Builder#setPeriodic(long)
+ * @see JobInfo.Builder#setPeriodic(long, long)
*/
public long getIntervalMillis() {
return intervalMillis;
@@ -505,6 +550,8 @@ public class JobInfo implements Parcelable {
/**
* Flex time for this job. Only valid if this is a periodic job. The job can
* execute at any time in a window of flex length at the end of the period.
+ * @see JobInfo.Builder#setPeriodic(long)
+ * @see JobInfo.Builder#setPeriodic(long, long)
*/
public long getFlexMillis() {
return flexMillis;
@@ -514,6 +561,7 @@ public class JobInfo implements Parcelable {
* The amount of time the JobScheduler will wait before rescheduling a failed job. This value
* will be increased depending on the backoff policy specified at job creation time. Defaults
* to 30 seconds, minimum is currently 10 seconds.
+ * @see JobInfo.Builder#setBackoffCriteria(long, int)
*/
public long getInitialBackoffMillis() {
return initialBackoffMillis;
@@ -521,12 +569,27 @@ public class JobInfo implements Parcelable {
/**
* Return the backoff policy of this job.
+ * @see JobInfo.Builder#setBackoffCriteria(long, int)
*/
public @BackoffPolicy int getBackoffPolicy() {
return backoffPolicy;
}
/**
+ * @see JobInfo.Builder#setImportantWhileForeground(boolean)
+ */
+ public boolean isImportantWhileForeground() {
+ return (flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0;
+ }
+
+ /**
+ * @see JobInfo.Builder#setPrefetch(boolean)
+ */
+ public boolean isPrefetch() {
+ return (flags & FLAG_PREFETCH) != 0;
+ }
+
+ /**
* User can specify an early constraint of 0L, which is valid, so we keep track of whether the
* function was called at all.
* @hide
@@ -597,7 +660,10 @@ public class JobInfo implements Parcelable {
if (!Objects.equals(networkRequest, j.networkRequest)) {
return false;
}
- if (networkBytes != j.networkBytes) {
+ if (networkDownloadBytes != j.networkDownloadBytes) {
+ return false;
+ }
+ if (networkUploadBytes != j.networkUploadBytes) {
return false;
}
if (minLatencyMillis != j.minLatencyMillis) {
@@ -660,7 +726,8 @@ public class JobInfo implements Parcelable {
if (networkRequest != null) {
hashCode = 31 * hashCode + networkRequest.hashCode();
}
- hashCode = 31 * hashCode + Long.hashCode(networkBytes);
+ hashCode = 31 * hashCode + Long.hashCode(networkDownloadBytes);
+ hashCode = 31 * hashCode + Long.hashCode(networkUploadBytes);
hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic);
@@ -695,7 +762,8 @@ public class JobInfo implements Parcelable {
} else {
networkRequest = null;
}
- networkBytes = in.readLong();
+ networkDownloadBytes = in.readLong();
+ networkUploadBytes = in.readLong();
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
isPeriodic = in.readInt() == 1;
@@ -724,7 +792,8 @@ public class JobInfo implements Parcelable {
triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
triggerContentMaxDelay = b.mTriggerContentMaxDelay;
networkRequest = b.mNetworkRequest;
- networkBytes = b.mNetworkBytes;
+ networkDownloadBytes = b.mNetworkDownloadBytes;
+ networkUploadBytes = b.mNetworkUploadBytes;
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
isPeriodic = b.mIsPeriodic;
@@ -767,7 +836,8 @@ public class JobInfo implements Parcelable {
} else {
out.writeInt(0);
}
- out.writeLong(networkBytes);
+ out.writeLong(networkDownloadBytes);
+ out.writeLong(networkUploadBytes);
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
out.writeInt(isPeriodic ? 1 : 0);
@@ -901,7 +971,8 @@ public class JobInfo implements Parcelable {
// Requirements.
private int mConstraintFlags;
private NetworkRequest mNetworkRequest;
- private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
+ private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
+ private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
private ArrayList<TriggerContentUri> mTriggerContentUris;
private long mTriggerContentUpdateDelay = -1;
private long mTriggerContentMaxDelay = -1;
@@ -952,6 +1023,7 @@ public class JobInfo implements Parcelable {
/**
* Set optional extras. This is persisted, so we only allow primitive types.
* @param extras Bundle containing extras you want the scheduler to hold on to for you.
+ * @see JobInfo#getExtras()
*/
public Builder setExtras(@NonNull PersistableBundle extras) {
mExtras = extras;
@@ -966,6 +1038,7 @@ public class JobInfo implements Parcelable {
* {@link android.app.job.JobInfo.Builder#build()} is called.</p>
*
* @param extras Bundle containing extras you want the scheduler to hold on to for you.
+ * @see JobInfo#getTransientExtras()
*/
public Builder setTransientExtras(@NonNull Bundle extras) {
mTransientExtras = extras;
@@ -993,6 +1066,8 @@ public class JobInfo implements Parcelable {
* a combination of {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION},
* {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, and
* {@link android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
+ * @see JobInfo#getClipData()
+ * @see JobInfo#getClipGrantFlags()
*/
public Builder setClipData(@Nullable ClipData clip, int grantFlags) {
mClipData = clip;
@@ -1083,6 +1158,16 @@ public class JobInfo implements Parcelable {
}
/**
+ * @deprecated replaced by
+ * {@link #setEstimatedNetworkBytes(long, long)}.
+ * @removed
+ */
+ @Deprecated
+ public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) {
+ return setEstimatedNetworkBytes(networkBytes, NETWORK_BYTES_UNKNOWN);
+ }
+
+ /**
* Set the estimated size of network traffic that will be performed by
* this job, in bytes.
* <p>
@@ -1099,23 +1184,30 @@ public class JobInfo implements Parcelable {
* <li>A job that synchronizes email could end up using an extreme range
* of data, from under 1KB when nothing has changed, to dozens of MB
* when there are new emails with attachments. Jobs that cannot provide
- * reasonable estimates should leave this estimated value undefined.
+ * reasonable estimates should use the sentinel value
+ * {@link JobInfo#NETWORK_BYTES_UNKNOWN}.
* </ul>
* Note that the system may choose to delay jobs with large network
* usage estimates when the device has a poor network connection, in
* order to save battery.
+ * <p>
+ * The values provided here only reflect the traffic that will be
+ * performed by the base job; if you're using {@link JobWorkItem} then
+ * you also need to define the network traffic used by each work item
+ * when constructing them.
*
- * @param networkBytes The estimated size of network traffic that will
- * be performed by this job, in bytes. This value only
- * reflects the traffic that will be performed by the base
- * job; if you're using {@link JobWorkItem} then you also
- * need to define the network traffic used by each work item
- * when constructing them.
- * @see JobInfo#getEstimatedNetworkBytes()
- * @see JobWorkItem#JobWorkItem(android.content.Intent, long)
+ * @param downloadBytes The estimated size of network traffic that will
+ * be downloaded by this job, in bytes.
+ * @param uploadBytes The estimated size of network traffic that will be
+ * uploaded by this job, in bytes.
+ * @see JobInfo#getEstimatedNetworkDownloadBytes()
+ * @see JobInfo#getEstimatedNetworkUploadBytes()
+ * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long)
*/
- public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) {
- mNetworkBytes = networkBytes;
+ public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
+ @BytesLong long uploadBytes) {
+ mNetworkDownloadBytes = downloadBytes;
+ mNetworkUploadBytes = uploadBytes;
return this;
}
@@ -1133,6 +1225,7 @@ public class JobInfo implements Parcelable {
*
* @param requiresCharging Pass {@code true} to require that the device be
* charging in order to run the job.
+ * @see JobInfo#isRequireCharging()
*/
public Builder setRequiresCharging(boolean requiresCharging) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING)
@@ -1146,6 +1239,7 @@ public class JobInfo implements Parcelable {
* is not low, which is generally the point where the user is given a "low battery"
* warning.
* @param batteryNotLow Whether or not the device's battery level must not be low.
+ * @see JobInfo#isRequireBatteryNotLow()
*/
public Builder setRequiresBatteryNotLow(boolean batteryNotLow) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_BATTERY_NOT_LOW)
@@ -1170,6 +1264,7 @@ public class JobInfo implements Parcelable {
*
* @param requiresDeviceIdle Pass {@code true} to prevent the job from running
* while the device is being used interactively.
+ * @see JobInfo#isRequireDeviceIdle()
*/
public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE)
@@ -1183,6 +1278,7 @@ public class JobInfo implements Parcelable {
* in a low storage state, which is generally the point where the user is given a
* "low storage" warning.
* @param storageNotLow Whether or not the device's available storage must not be low.
+ * @see JobInfo#isRequireStorageNotLow()
*/
public Builder setRequiresStorageNotLow(boolean storageNotLow) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_STORAGE_NOT_LOW)
@@ -1215,6 +1311,7 @@ public class JobInfo implements Parcelable {
* job}
*
* @param uri The content: URI to monitor.
+ * @see JobInfo#getTriggerContentUris()
*/
public Builder addTriggerContentUri(@NonNull TriggerContentUri uri) {
if (mTriggerContentUris == null) {
@@ -1229,6 +1326,7 @@ public class JobInfo implements Parcelable {
* the job is scheduled. If there are more changes during that time, the delay
* will be reset to start at the time of the most recent change.
* @param durationMs Delay after most recent content change, in milliseconds.
+ * @see JobInfo#getTriggerContentUpdateDelay()
*/
public Builder setTriggerContentUpdateDelay(long durationMs) {
mTriggerContentUpdateDelay = durationMs;
@@ -1239,6 +1337,7 @@ public class JobInfo implements Parcelable {
* Set the maximum total delay (in milliseconds) that is allowed from the first
* time a content change is detected until the job is scheduled.
* @param durationMs Delay after initial content change, in milliseconds.
+ * @see JobInfo#getTriggerContentMaxDelay()
*/
public Builder setTriggerContentMaxDelay(long durationMs) {
mTriggerContentMaxDelay = durationMs;
@@ -1252,6 +1351,8 @@ public class JobInfo implements Parcelable {
* Setting this function on the builder with {@link #setMinimumLatency(long)} or
* {@link #setOverrideDeadline(long)} will result in an error.
* @param intervalMillis Millisecond interval for which this job will repeat.
+ * @see JobInfo#getIntervalMillis()
+ * @see JobInfo#getFlexMillis()
*/
public Builder setPeriodic(long intervalMillis) {
return setPeriodic(intervalMillis, intervalMillis);
@@ -1265,6 +1366,8 @@ public class JobInfo implements Parcelable {
* @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
* {@link #getMinFlexMillis()} or 5 percent of the period, whichever is
* higher.
+ * @see JobInfo#getIntervalMillis()
+ * @see JobInfo#getFlexMillis()
*/
public Builder setPeriodic(long intervalMillis, long flexMillis) {
final long minPeriod = getMinPeriodMillis();
@@ -1296,6 +1399,7 @@ public class JobInfo implements Parcelable {
* {@link android.app.job.JobInfo.Builder#build()} is called.
* @param minLatencyMillis Milliseconds before which this job will not be considered for
* execution.
+ * @see JobInfo#getMinLatencyMillis()
*/
public Builder setMinimumLatency(long minLatencyMillis) {
mMinLatencyMillis = minLatencyMillis;
@@ -1309,6 +1413,7 @@ public class JobInfo implements Parcelable {
* this property on a periodic job, doing so will throw an
* {@link java.lang.IllegalArgumentException} when
* {@link android.app.job.JobInfo.Builder#build()} is called.
+ * @see JobInfo#getMaxExecutionDelayMillis()
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
mMaxExecutionDelayMillis = maxExecutionDelayMillis;
@@ -1328,6 +1433,8 @@ public class JobInfo implements Parcelable {
* mode.
* @param initialBackoffMillis Millisecond time interval to wait initially when job has
* failed.
+ * @see JobInfo#getInitialBackoffMillis()
+ * @see JobInfo#getBackoffPolicy()
*/
public Builder setBackoffCriteria(long initialBackoffMillis,
@BackoffPolicy int backoffPolicy) {
@@ -1358,6 +1465,7 @@ public class JobInfo implements Parcelable {
*
* @param importantWhileForeground whether to relax doze restrictions for this job when the
* app is in the foreground. False by default.
+ * @see JobInfo#isImportantWhileForeground()
*/
public Builder setImportantWhileForeground(boolean importantWhileForeground) {
if (importantWhileForeground) {
@@ -1369,6 +1477,15 @@ public class JobInfo implements Parcelable {
}
/**
+ * @removed
+ * @deprecated replaced with {@link #setPrefetch(boolean)}
+ */
+ @Deprecated
+ public Builder setIsPrefetch(boolean isPrefetch) {
+ return setPrefetch(isPrefetch);
+ }
+
+ /**
* Setting this to true indicates that this job is designed to prefetch
* content that will make a material improvement to the experience of
* the specific user of this device. For example, fetching top headlines
@@ -1380,12 +1497,13 @@ public class JobInfo implements Parcelable {
* network when there is a surplus of metered data available. The system
* may also use this signal in combination with end user usage patterns
* to ensure data is prefetched before the user launches your app.
+ * @see JobInfo#isPrefetch()
*/
- public Builder setIsPrefetch(boolean isPrefetch) {
- if (isPrefetch) {
- mFlags |= FLAG_IS_PREFETCH;
+ public Builder setPrefetch(boolean prefetch) {
+ if (prefetch) {
+ mFlags |= FLAG_PREFETCH;
} else {
- mFlags &= (~FLAG_IS_PREFETCH);
+ mFlags &= (~FLAG_PREFETCH);
}
return this;
}
@@ -1395,6 +1513,7 @@ public class JobInfo implements Parcelable {
*
* @param isPersisted True to indicate that the job will be written to
* disk and loaded at boot.
+ * @see JobInfo#isPersisted()
*/
@RequiresPermission(android.Manifest.permission.RECEIVE_BOOT_COMPLETED)
public Builder setPersisted(boolean isPersisted) {
@@ -1414,7 +1533,7 @@ public class JobInfo implements Parcelable {
"constraints, this is not allowed.");
}
// Check that network estimates require network type
- if (mNetworkBytes > 0 && mNetworkRequest == null) {
+ if ((mNetworkDownloadBytes > 0 || mNetworkUploadBytes > 0) && mNetworkRequest == null) {
throw new IllegalArgumentException(
"Can't provide estimated network usage without requiring a network");
}
diff --git a/android/app/job/JobParameters.java b/android/app/job/JobParameters.java
index c71bf2e6..d67f11bb 100644
--- a/android/app/job/JobParameters.java
+++ b/android/app/job/JobParameters.java
@@ -36,15 +36,16 @@ import android.os.RemoteException;
public class JobParameters implements Parcelable {
/** @hide */
- public static final int REASON_CANCELED = 0;
+ public static final int REASON_CANCELED = JobProtoEnums.STOP_REASON_CANCELLED; // 0.
/** @hide */
- public static final int REASON_CONSTRAINTS_NOT_SATISFIED = 1;
+ public static final int REASON_CONSTRAINTS_NOT_SATISFIED =
+ JobProtoEnums.STOP_REASON_CONSTRAINTS_NOT_SATISFIED; //1.
/** @hide */
- public static final int REASON_PREEMPT = 2;
+ public static final int REASON_PREEMPT = JobProtoEnums.STOP_REASON_PREEMPT; // 2.
/** @hide */
- public static final int REASON_TIMEOUT = 3;
+ public static final int REASON_TIMEOUT = JobProtoEnums.STOP_REASON_TIMEOUT; // 3.
/** @hide */
- public static final int REASON_DEVICE_IDLE = 4;
+ public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4.
/** @hide */
public static String getReasonName(int reason) {
diff --git a/android/app/job/JobWorkItem.java b/android/app/job/JobWorkItem.java
index 1c46e8ec..995f5226 100644
--- a/android/app/job/JobWorkItem.java
+++ b/android/app/job/JobWorkItem.java
@@ -16,6 +16,8 @@
package android.app.job;
+import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
+
import android.annotation.BytesLong;
import android.content.Intent;
import android.os.Parcel;
@@ -28,7 +30,8 @@ import android.os.Parcelable;
*/
final public class JobWorkItem implements Parcelable {
final Intent mIntent;
- final long mNetworkBytes;
+ final long mNetworkDownloadBytes;
+ final long mNetworkUploadBytes;
int mDeliveryCount;
int mWorkId;
Object mGrants;
@@ -41,22 +44,36 @@ final public class JobWorkItem implements Parcelable {
*/
public JobWorkItem(Intent intent) {
mIntent = intent;
- mNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+ mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
+ mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
+ }
+
+ /**
+ * @deprecated replaced by {@link #JobWorkItem(Intent, long, long)}
+ * @removed
+ */
+ @Deprecated
+ public JobWorkItem(Intent intent, @BytesLong long networkBytes) {
+ this(intent, networkBytes, NETWORK_BYTES_UNKNOWN);
}
/**
* Create a new piece of work, which can be submitted to
* {@link JobScheduler#enqueue JobScheduler.enqueue}.
+ * <p>
+ * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
+ * details about how to estimate network traffic.
*
* @param intent The general Intent describing this work.
- * @param networkBytes The estimated size of network traffic that will be
- * performed by this job work item, in bytes. See
- * {@link JobInfo.Builder#setEstimatedNetworkBytes(long)} for
- * details about how to estimate.
+ * @param downloadBytes The estimated size of network traffic that will be
+ * downloaded by this job work item, in bytes.
+ * @param uploadBytes The estimated size of network traffic that will be
+ * uploaded by this job work item, in bytes.
*/
- public JobWorkItem(Intent intent, @BytesLong long networkBytes) {
+ public JobWorkItem(Intent intent, @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
mIntent = intent;
- mNetworkBytes = networkBytes;
+ mNetworkDownloadBytes = downloadBytes;
+ mNetworkUploadBytes = uploadBytes;
}
/**
@@ -67,14 +84,44 @@ final public class JobWorkItem implements Parcelable {
}
/**
- * Return the estimated size of network traffic that will be performed by
+ * @deprecated replaced by {@link #getEstimatedNetworkDownloadBytes()} and
+ * {@link #getEstimatedNetworkUploadBytes()}.
+ * @removed
+ */
+ @Deprecated
+ public @BytesLong long getEstimatedNetworkBytes() {
+ if (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN
+ && mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) {
+ return NETWORK_BYTES_UNKNOWN;
+ } else if (mNetworkDownloadBytes == NETWORK_BYTES_UNKNOWN) {
+ return mNetworkUploadBytes;
+ } else if (mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) {
+ return mNetworkDownloadBytes;
+ } else {
+ return mNetworkDownloadBytes + mNetworkUploadBytes;
+ }
+ }
+
+ /**
+ * Return the estimated size of download traffic that will be performed by
+ * this job, in bytes.
+ *
+ * @return Estimated size of download traffic, or
+ * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown.
+ */
+ public @BytesLong long getEstimatedNetworkDownloadBytes() {
+ return mNetworkDownloadBytes;
+ }
+
+ /**
+ * Return the estimated size of upload traffic that will be performed by
* this job work item, in bytes.
*
- * @return estimated size, or {@link JobInfo#NETWORK_BYTES_UNKNOWN} when
- * unknown.
+ * @return Estimated size of upload traffic, or
+ * {@link JobInfo#NETWORK_BYTES_UNKNOWN} when unknown.
*/
- public @BytesLong long getEstimatedNetworkBytes() {
- return mNetworkBytes;
+ public @BytesLong long getEstimatedNetworkUploadBytes() {
+ return mNetworkUploadBytes;
}
/**
@@ -128,9 +175,13 @@ final public class JobWorkItem implements Parcelable {
sb.append(mWorkId);
sb.append(" intent=");
sb.append(mIntent);
- if (mNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- sb.append(" networkBytes=");
- sb.append(mNetworkBytes);
+ if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN) {
+ sb.append(" downloadBytes=");
+ sb.append(mNetworkDownloadBytes);
+ }
+ if (mNetworkUploadBytes != NETWORK_BYTES_UNKNOWN) {
+ sb.append(" uploadBytes=");
+ sb.append(mNetworkUploadBytes);
}
if (mDeliveryCount != 0) {
sb.append(" dcount=");
@@ -151,7 +202,8 @@ final public class JobWorkItem implements Parcelable {
} else {
out.writeInt(0);
}
- out.writeLong(mNetworkBytes);
+ out.writeLong(mNetworkDownloadBytes);
+ out.writeLong(mNetworkUploadBytes);
out.writeInt(mDeliveryCount);
out.writeInt(mWorkId);
}
@@ -173,7 +225,8 @@ final public class JobWorkItem implements Parcelable {
} else {
mIntent = null;
}
- mNetworkBytes = in.readLong();
+ mNetworkDownloadBytes = in.readLong();
+ mNetworkUploadBytes = in.readLong();
mDeliveryCount = in.readInt();
mWorkId = in.readInt();
}
diff --git a/android/app/servertransaction/ActivityLifecycleItem.java b/android/app/servertransaction/ActivityLifecycleItem.java
index 9a50a009..7f8c50cd 100644
--- a/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/android/app/servertransaction/ActivityLifecycleItem.java
@@ -91,4 +91,9 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem {
pw.println(prefix + "target state:" + getTargetState());
pw.println(prefix + "description: " + mDescription);
}
+
+ @Override
+ public void recycle() {
+ setDescription(null);
+ }
}
diff --git a/android/app/servertransaction/ActivityRelaunchItem.java b/android/app/servertransaction/ActivityRelaunchItem.java
new file mode 100644
index 00000000..d8a7463c
--- /dev/null
+++ b/android/app/servertransaction/ActivityRelaunchItem.java
@@ -0,0 +1,176 @@
+/*
+ * 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_ORDER;
+
+import android.app.ActivityThread;
+import android.app.ClientTransactionHandler;
+import android.app.ResultInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Activity relaunch callback.
+ * @hide
+ */
+public class ActivityRelaunchItem extends ClientTransactionItem {
+
+ private static final String TAG = "ActivityRelaunchItem";
+
+ private List<ResultInfo> mPendingResults;
+ private List<ReferrerIntent> mPendingNewIntents;
+ private int mConfigChanges;
+ private MergedConfiguration mConfig;
+ private boolean mPreserveWindow;
+
+ /**
+ * A record that was properly configured for relaunch. Execution will be cancelled if not
+ * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}.
+ */
+ private ActivityThread.ActivityClientRecord mActivityClientRecord;
+
+ @Override
+ public void preExecute(ClientTransactionHandler client, IBinder token) {
+ mActivityClientRecord = client.prepareRelaunchActivity(token, mPendingResults,
+ mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ if (mActivityClientRecord == null) {
+ if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
+ return;
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
+ client.handleRelaunchActivity(mActivityClientRecord, pendingActions);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public void postExecute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ client.reportRelaunch(token, pendingActions);
+ }
+
+ // ObjectPoolItem implementation
+
+ private ActivityRelaunchItem() {}
+
+ /** Obtain an instance initialized with provided params. */
+ public static ActivityRelaunchItem obtain(List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, int configChanges, MergedConfiguration config,
+ boolean preserveWindow) {
+ ActivityRelaunchItem instance = ObjectPool.obtain(ActivityRelaunchItem.class);
+ if (instance == null) {
+ instance = new ActivityRelaunchItem();
+ }
+ instance.mPendingResults = pendingResults;
+ instance.mPendingNewIntents = pendingNewIntents;
+ instance.mConfigChanges = configChanges;
+ instance.mConfig = config;
+ instance.mPreserveWindow = preserveWindow;
+
+ return instance;
+ }
+
+ @Override
+ public void recycle() {
+ mPendingResults = null;
+ mPendingNewIntents = null;
+ mConfigChanges = 0;
+ mConfig = null;
+ mPreserveWindow = false;
+ mActivityClientRecord = null;
+ ObjectPool.recycle(this);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mPendingResults, flags);
+ dest.writeTypedList(mPendingNewIntents, flags);
+ dest.writeInt(mConfigChanges);
+ dest.writeTypedObject(mConfig, flags);
+ dest.writeBoolean(mPreserveWindow);
+ }
+
+ /** Read from Parcel. */
+ private ActivityRelaunchItem(Parcel in) {
+ mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
+ mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ mConfigChanges = in.readInt();
+ mConfig = in.readTypedObject(MergedConfiguration.CREATOR);
+ mPreserveWindow = in.readBoolean();
+ }
+
+ public static final Creator<ActivityRelaunchItem> CREATOR =
+ new Creator<ActivityRelaunchItem>() {
+ public ActivityRelaunchItem createFromParcel(Parcel in) {
+ return new ActivityRelaunchItem(in);
+ }
+
+ public ActivityRelaunchItem[] newArray(int size) {
+ return new ActivityRelaunchItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityRelaunchItem other = (ActivityRelaunchItem) o;
+ return Objects.equals(mPendingResults, other.mPendingResults)
+ && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
+ && mConfigChanges == other.mConfigChanges && Objects.equals(mConfig, other.mConfig)
+ && mPreserveWindow == other.mPreserveWindow;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mPendingResults);
+ result = 31 * result + Objects.hashCode(mPendingNewIntents);
+ result = 31 * result + mConfigChanges;
+ result = 31 * result + Objects.hashCode(mConfig);
+ result = 31 * result + (mPreserveWindow ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ActivityRelaunchItem{pendingResults=" + mPendingResults
+ + ",pendingNewIntents=" + mPendingNewIntents + ",configChanges=" + mConfigChanges
+ + ",config=" + mConfig + ",preserveWindow" + mPreserveWindow + "}";
+ }
+}
diff --git a/android/app/servertransaction/ActivityResultItem.java b/android/app/servertransaction/ActivityResultItem.java
index 73b5ec44..545463c1 100644
--- a/android/app/servertransaction/ActivityResultItem.java
+++ b/android/app/servertransaction/ActivityResultItem.java
@@ -16,7 +16,7 @@
package android.app.servertransaction;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.app.ClientTransactionHandler;
@@ -38,8 +38,8 @@ public class ActivityResultItem extends ClientTransactionItem {
private List<ResultInfo> mResultInfoList;
@Override
- public int getPreExecutionState() {
- return ON_PAUSE;
+ public int getPostExecutionState() {
+ return ON_RESUME;
}
@Override
diff --git a/android/app/servertransaction/ClientTransactionItem.java b/android/app/servertransaction/ClientTransactionItem.java
index 6f2cc007..d94f08b6 100644
--- a/android/app/servertransaction/ClientTransactionItem.java
+++ b/android/app/servertransaction/ClientTransactionItem.java
@@ -32,12 +32,6 @@ import android.os.Parcelable;
*/
public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable {
- /** Get the state in which this callback can be executed. */
- @LifecycleState
- public int getPreExecutionState() {
- return UNDEFINED;
- }
-
/** Get the state that must follow this callback. */
@LifecycleState
public int getPostExecutionState() {
diff --git a/android/app/servertransaction/DestroyActivityItem.java b/android/app/servertransaction/DestroyActivityItem.java
index cbcf6c75..0edcf188 100644
--- a/android/app/servertransaction/DestroyActivityItem.java
+++ b/android/app/servertransaction/DestroyActivityItem.java
@@ -37,7 +37,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem {
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
client.handleDestroyActivity(token, mFinished, mConfigChanges,
- false /* getNonConfigInstance */);
+ false /* getNonConfigInstance */, getDescription());
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -65,6 +65,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem {
@Override
public void recycle() {
+ super.recycle();
mFinished = false;
mConfigChanges = 0;
ObjectPool.recycle(this);
diff --git a/android/app/servertransaction/NewIntentItem.java b/android/app/servertransaction/NewIntentItem.java
index 7dfde73c..e5ce3b00 100644
--- a/android/app/servertransaction/NewIntentItem.java
+++ b/android/app/servertransaction/NewIntentItem.java
@@ -38,11 +38,6 @@ public class NewIntentItem extends ClientTransactionItem {
// TODO(lifecycler): Switch new intent handling to this scheme.
/*@Override
- public int getPreExecutionState() {
- return ON_PAUSE;
- }
-
- @Override
public int getPostExecutionState() {
return ON_RESUME;
}*/
diff --git a/android/app/servertransaction/PauseActivityItem.java b/android/app/servertransaction/PauseActivityItem.java
index 70a4755f..65e42912 100644
--- a/android/app/servertransaction/PauseActivityItem.java
+++ b/android/app/servertransaction/PauseActivityItem.java
@@ -42,8 +42,8 @@ public class PauseActivityItem extends ActivityLifecycleItem {
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport,
- pendingActions);
+ client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, pendingActions,
+ "PAUSE_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -102,6 +102,7 @@ public class PauseActivityItem extends ActivityLifecycleItem {
@Override
public void recycle() {
+ super.recycle();
mFinished = false;
mUserLeaving = false;
mConfigChanges = 0;
diff --git a/android/app/servertransaction/PendingTransactionActions.java b/android/app/servertransaction/PendingTransactionActions.java
index 8304c1c5..af7b7a21 100644
--- a/android/app/servertransaction/PendingTransactionActions.java
+++ b/android/app/servertransaction/PendingTransactionActions.java
@@ -44,6 +44,7 @@ public class PendingTransactionActions {
private boolean mCallOnPostCreate;
private Bundle mOldState;
private StopInfo mStopInfo;
+ private boolean mReportRelaunchToWM;
public PendingTransactionActions() {
clear();
@@ -91,6 +92,24 @@ public class PendingTransactionActions {
mStopInfo = stopInfo;
}
+ /**
+ * Check if we should report an activity relaunch to WindowManager. We report back for every
+ * relaunch request to ActivityManager, but only for those that were actually finished to we
+ * report to WindowManager.
+ */
+ public boolean shouldReportRelaunchToWindowManager() {
+ return mReportRelaunchToWM;
+ }
+
+ /**
+ * Set if we should report an activity relaunch to WindowManager. We report back for every
+ * relaunch request to ActivityManager, but only for those that were actually finished we report
+ * to WindowManager.
+ */
+ public void setReportRelaunchToWindowManager(boolean reportToWm) {
+ mReportRelaunchToWM = reportToWm;
+ }
+
/** Reports to server about activity stop. */
public static class StopInfo implements Runnable {
private static final String TAG = "ActivityStopInfo";
@@ -134,7 +153,7 @@ public class PendingTransactionActions {
Bundle.dumpStats(pw, mPersistentState);
if (ex instanceof TransactionTooLargeException
- && mActivity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) {
+ && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
return;
}
diff --git a/android/app/servertransaction/ResumeActivityItem.java b/android/app/servertransaction/ResumeActivityItem.java
index ed90f2cb..d16bc97c 100644
--- a/android/app/servertransaction/ResumeActivityItem.java
+++ b/android/app/servertransaction/ResumeActivityItem.java
@@ -48,7 +48,8 @@ public class ResumeActivityItem extends ActivityLifecycleItem {
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- client.handleResumeActivity(token, true /* clearHide */, mIsForward, "RESUME_ACTIVITY");
+ client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward,
+ "RESUME_ACTIVITY");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -101,6 +102,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem {
@Override
public void recycle() {
+ super.recycle();
mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
mUpdateProcState = false;
mIsForward = false;
diff --git a/android/app/servertransaction/StopActivityItem.java b/android/app/servertransaction/StopActivityItem.java
index b814d1ae..8db38d36 100644
--- a/android/app/servertransaction/StopActivityItem.java
+++ b/android/app/servertransaction/StopActivityItem.java
@@ -38,7 +38,8 @@ public class StopActivityItem extends ActivityLifecycleItem {
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions);
+ client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions,
+ true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -72,6 +73,7 @@ public class StopActivityItem extends ActivityLifecycleItem {
@Override
public void recycle() {
+ super.recycle();
mShowWindow = false;
mConfigChanges = 0;
ObjectPool.recycle(this);
diff --git a/android/app/servertransaction/TransactionExecutor.java b/android/app/servertransaction/TransactionExecutor.java
index 78b393a8..553c3ae1 100644
--- a/android/app/servertransaction/TransactionExecutor.java
+++ b/android/app/servertransaction/TransactionExecutor.java
@@ -24,6 +24,7 @@ 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 static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
@@ -48,11 +49,7 @@ public class 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);
+ private TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
/** Initialize an instance with transaction handler, that will execute all requested actions. */
public TransactionExecutor(ClientTransactionHandler clientTransactionHandler) {
@@ -89,13 +86,25 @@ public class TransactionExecutor {
final IBinder token = transaction.getActivityToken();
ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
+
+ // In case when post-execution state of the last callback matches the final state requested
+ // for the activity in this transaction, we won't do the last transition here and do it when
+ // moving to final state instead (because it may contain additional parameters from server).
+ final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
+ final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState()
+ : UNDEFINED;
+ // Index of the last callback that requests some post-execution state.
+ final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
+
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);
+ final int postExecutionState = item.getPostExecutionState();
+ final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+ item.getPostExecutionState());
+ if (closestPreExecutionState != UNDEFINED) {
+ cycleToPath(r, closestPreExecutionState);
}
item.execute(mTransactionHandler, token, mPendingActions);
@@ -105,9 +114,11 @@ public class TransactionExecutor {
r = mTransactionHandler.getActivityClient(token);
}
- final int postExecutionState = item.getPostExecutionState();
- if (postExecutionState != UNDEFINED) {
- cycleToPath(r, postExecutionState);
+ if (postExecutionState != UNDEFINED && r != null) {
+ // Skip the very last transition and perform it by explicit state request instead.
+ final boolean shouldExcludeLastTransition =
+ i == lastCallbackRequestingState && finalState == postExecutionState;
+ cycleToPath(r, postExecutionState, shouldExcludeLastTransition);
}
}
}
@@ -136,7 +147,10 @@ public class TransactionExecutor {
pw.println("Executor:");
dump(pw, prefix);
- Slog.wtf(TAG, stringWriter.toString());
+ Slog.w(TAG, stringWriter.toString());
+
+ // Ignore requests for non-existent client records for now.
+ return;
}
// Cycle to the state right before the final requested state.
@@ -162,15 +176,15 @@ public class TransactionExecutor {
boolean excludeLastState) {
final int start = r.getLifecycleState();
log("Cycle from: " + start + " to: " + finish + " excludeLastState:" + excludeLastState);
- initLifecyclePath(start, finish, excludeLastState);
- performLifecycleSequence(r);
+ final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState);
+ performLifecycleSequence(r, path);
}
/** Transition the client through previously initialized state sequence. */
- private void performLifecycleSequence(ActivityClientRecord r) {
- final int size = mLifecycleSequence.size();
+ private void performLifecycleSequence(ActivityClientRecord r, IntArray path) {
+ final int size = path.size();
for (int i = 0, state; i < size; i++) {
- state = mLifecycleSequence.get(i);
+ state = path.get(i);
log("Transitioning to state: " + state);
switch (state) {
case ON_CREATE:
@@ -180,21 +194,23 @@ public class TransactionExecutor {
mTransactionHandler.handleStartActivity(r, mPendingActions);
break;
case ON_RESUME:
- mTransactionHandler.handleResumeActivity(r.token, false /* clearHide */,
+ mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
break;
case ON_PAUSE:
mTransactionHandler.handlePauseActivity(r.token, false /* finished */,
- false /* userLeaving */, 0 /* configChanges */,
- true /* dontReport */, mPendingActions);
+ false /* userLeaving */, 0 /* configChanges */, mPendingActions,
+ "LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
mTransactionHandler.handleStopActivity(r.token, false /* show */,
- 0 /* configChanges */, mPendingActions);
+ 0 /* configChanges */, mPendingActions, false /* finalStateRequest */,
+ "LIFECYCLER_STOP_ACTIVITY");
break;
case ON_DESTROY:
mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */,
- 0 /* configChanges */, false /* getNonConfigInstance */);
+ 0 /* configChanges */, false /* getNonConfigInstance */,
+ "performLifecycleSequence. cycling to:" + path.get(size - 1));
break;
case ON_RESTART:
mTransactionHandler.performRestartActivity(r.token, false /* start */);
@@ -205,60 +221,6 @@ public class TransactionExecutor {
}
}
- /**
- * 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/TransactionExecutorHelper.java b/android/app/servertransaction/TransactionExecutorHelper.java
new file mode 100644
index 00000000..01b13a28
--- /dev/null
+++ b/android/app/servertransaction/TransactionExecutorHelper.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2018 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.PRE_ON_CREATE;
+import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+
+import android.app.ActivityThread.ActivityClientRecord;
+import android.util.IntArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * Helper class for {@link TransactionExecutor} that contains utils for lifecycle path resolution.
+ * @hide
+ */
+public class TransactionExecutorHelper {
+ // A penalty applied to path with destruction when looking for the shortest one.
+ private static final int DESTRUCTION_PENALTY = 10;
+
+ private static final int[] ON_RESUME_PRE_EXCUTION_STATES = new int[] { ON_START, ON_PAUSE };
+
+ // 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);
+
+ /**
+ * 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.
+ * <p>NOTE: The returned value is used internally in this class and is not a copy. It's contents
+ * may change after calling other methods of this class.</p>
+ */
+ @VisibleForTesting
+ public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) {
+ if (start == UNDEFINED || finish == UNDEFINED) {
+ throw new IllegalArgumentException("Can't resolve lifecycle path for undefined state");
+ }
+ if (start == ON_RESTART || finish == ON_RESTART) {
+ throw new IllegalArgumentException(
+ "Can't start or finish in intermittent RESTART state");
+ }
+ if (finish == PRE_ON_CREATE && start != finish) {
+ throw new IllegalArgumentException("Can only start in pre-onCreate state");
+ }
+
+ 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);
+ }
+
+ return mLifecycleSequence;
+ }
+
+ /**
+ * Pick a state that goes before provided post-execution state and would require the least
+ * lifecycle transitions to get to.
+ * It will also make sure to try avoiding a path with activity destruction and relaunch if
+ * possible.
+ * @param r An activity that we're trying to resolve the transition for.
+ * @param postExecutionState Post execution state to compute for.
+ * @return One of states that precede the provided post-execution state, or
+ * {@link ActivityLifecycleItem#UNDEFINED} if there is not path.
+ */
+ @VisibleForTesting
+ public int getClosestPreExecutionState(ActivityClientRecord r,
+ int postExecutionState) {
+ switch (postExecutionState) {
+ case UNDEFINED:
+ return UNDEFINED;
+ case ON_RESUME:
+ return getClosestOfStates(r, ON_RESUME_PRE_EXCUTION_STATES);
+ default:
+ throw new UnsupportedOperationException("Pre-execution states for state: "
+ + postExecutionState + " is not supported.");
+ }
+ }
+
+ /**
+ * Pick a state that would require the least lifecycle transitions to get to.
+ * It will also make sure to try avoiding a path with activity destruction and relaunch if
+ * possible.
+ * @param r An activity that we're trying to resolve the transition for.
+ * @param finalStates An array of valid final states.
+ * @return One of the provided final states, or {@link ActivityLifecycleItem#UNDEFINED} if none
+ * were provided or there is not path.
+ */
+ @VisibleForTesting
+ public int getClosestOfStates(ActivityClientRecord r, int[] finalStates) {
+ if (finalStates == null || finalStates.length == 0) {
+ return UNDEFINED;
+ }
+
+ final int currentState = r.getLifecycleState();
+ int closestState = UNDEFINED;
+ for (int i = 0, shortestPath = Integer.MAX_VALUE, pathLength; i < finalStates.length; i++) {
+ getLifecyclePath(currentState, finalStates[i], false /* excludeLastState */);
+ pathLength = mLifecycleSequence.size();
+ if (pathInvolvesDestruction(mLifecycleSequence)) {
+ pathLength += DESTRUCTION_PENALTY;
+ }
+ if (shortestPath > pathLength) {
+ shortestPath = pathLength;
+ closestState = finalStates[i];
+ }
+ }
+ return closestState;
+ }
+
+ /** Get the lifecycle state request to match the current state in the end of a transaction. */
+ public static ActivityLifecycleItem getLifecycleRequestForCurrentState(ActivityClientRecord r) {
+ final int prevState = r.getLifecycleState();
+ final ActivityLifecycleItem lifecycleItem;
+ switch (prevState) {
+ // TODO(lifecycler): Extend to support all possible states.
+ case ON_PAUSE:
+ lifecycleItem = PauseActivityItem.obtain();
+ break;
+ case ON_STOP:
+ lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(),
+ 0 /* configChanges */);
+ break;
+ default:
+ lifecycleItem = ResumeActivityItem.obtain(false /* isForward */);
+ break;
+ }
+
+ return lifecycleItem;
+ }
+
+ /**
+ * Check if there is a destruction involved in the path. We want to avoid a lifecycle sequence
+ * that involves destruction and recreation if there is another path.
+ */
+ private static boolean pathInvolvesDestruction(IntArray lifecycleSequence) {
+ final int size = lifecycleSequence.size();
+ for (int i = 0; i < size; i++) {
+ if (lifecycleSequence.get(i) == ON_DESTROY) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the index of the last callback that requests the state in which activity will be after
+ * execution. If there is a group of callbacks in the end that requests the same specific state
+ * or doesn't request any - we will find the first one from such group.
+ *
+ * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
+ * specific state. If there is a sequence
+ * Configuration - ActivityResult - Configuration - ActivityResult
+ * index 1 will be returned, because ActivityResult request on position 1 will be the last
+ * request that moves activity to the RESUMED state where it will eventually end.
+ */
+ static int lastCallbackRequestingState(ClientTransaction transaction) {
+ final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
+ if (callbacks == null || callbacks.size() == 0) {
+ return -1;
+ }
+
+ // Go from the back of the list to front, look for the request closes to the beginning that
+ // requests the state in which activity will end after all callbacks are executed.
+ int lastRequestedState = UNDEFINED;
+ int lastRequestingCallback = -1;
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ final ClientTransactionItem callback = callbacks.get(i);
+ final int postExecutionState = callback.getPostExecutionState();
+ if (postExecutionState != UNDEFINED) {
+ // Found a callback that requests some post-execution state.
+ if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
+ // It's either a first-from-end callback that requests state or it requests
+ // the same state as the last one. In both cases, we will use it as the new
+ // candidate.
+ lastRequestedState = postExecutionState;
+ lastRequestingCallback = i;
+ } else {
+ break;
+ }
+ }
+ }
+
+ return lastRequestingCallback;
+ }
+}
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index 5808f8b5..bf3398ad 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -65,20 +65,32 @@ public final class Slice implements Parcelable {
HINT_TOGGLE,
HINT_HORIZONTAL,
HINT_PARTIAL,
- HINT_SEE_MORE
+ HINT_SEE_MORE,
+ HINT_KEYWORDS,
+ HINT_ERROR,
+ HINT_TTL,
+ HINT_LAST_UPDATED,
+ HINT_PERMISSION_REQUEST,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SliceHint {}
-
/**
- * The meta-data key that allows an activity to easily be linked directly to a slice.
- * <p>
- * An activity can be statically linked to a slice uri by including a meta-data item
- * for this key that contains a valid slice uri for the same application declaring
- * the activity.
* @hide
*/
- public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+ @StringDef(prefix = { "SUBTYPE_" }, value = {
+ SUBTYPE_COLOR,
+ SUBTYPE_CONTENT_DESCRIPTION,
+ SUBTYPE_MAX,
+ SUBTYPE_MESSAGE,
+ SUBTYPE_PRIORITY,
+ SUBTYPE_RANGE,
+ SUBTYPE_SOURCE,
+ SUBTYPE_TOGGLE,
+ SUBTYPE_VALUE,
+ SUBTYPE_LAYOUT_DIRECTION,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SliceSubtype {}
/**
* Hint that this content is a title of other content in the slice. This can also indicate that
@@ -146,45 +158,98 @@ public final class Slice implements Parcelable {
*/
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";
- /**
* A hint representing that this item should be used to indicate that there's more
* content associated with this slice.
*/
public static final String HINT_SEE_MORE = "see_more";
/**
- * A hint used when implementing app-specific slice permissions.
- * Tells the system that for this slice the return value of
- * {@link SliceProvider#onBindSlice(Uri, List)} may be different depending on
- * {@link SliceProvider#getBindingPackage} and should not be cached for multiple
- * apps.
+ * @see Builder#setCallerNeeded
+ * @hide
*/
public static final String HINT_CALLER_NEEDED = "caller_needed";
/**
+ * A hint to indicate that the contents of this subslice represent a list of keywords
+ * related to the parent slice.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE}.
+ */
+ public static final String HINT_KEYWORDS = "keywords";
+ /**
+ * A hint to indicate that this slice represents an error.
+ */
+ public static final String HINT_ERROR = "error";
+ /**
+ * Hint indicating an item representing a time-to-live for the content.
+ */
+ public static final String HINT_TTL = "ttl";
+ /**
+ * Hint indicating an item representing when the content was created or last updated.
+ */
+ public static final String HINT_LAST_UPDATED = "last_updated";
+ /**
+ * A hint to indicate that this slice represents a permission request for showing
+ * slices.
+ */
+ public static final String HINT_PERMISSION_REQUEST = "permission_request";
+ /**
+ * Subtype to indicate that this item indicates the layout direction for content
+ * in the slice.
+ * Expected to be an item of format {@link SliceItem#FORMAT_INT}.
+ */
+ public static final String SUBTYPE_LAYOUT_DIRECTION = "layout_direction";
+ /**
* Key to retrieve an extra added to an intent when a control is changed.
*/
public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
/**
+ * Key to retrieve an extra added to an intent when the value of a slider is changed.
+ * @deprecated remove once support lib is update to use EXTRA_RANGE_VALUE instead
+ */
+ @Deprecated
+ public static final String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE";
+ /**
+ * Key to retrieve an extra added to an intent when the value of an input range is changed.
+ */
+ public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE";
+ /**
* Subtype to indicate that this is a message as part of a communication
* sequence in this slice.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE}.
*/
public static final String SUBTYPE_MESSAGE = "message";
/**
* Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT},
+ * {@link SliceItem#FORMAT_IMAGE} or an {@link SliceItem#FORMAT_SLICE} containing them.
*/
public static final String SUBTYPE_SOURCE = "source";
/**
* Subtype to tag an item as representing a color.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
*/
public static final String SUBTYPE_COLOR = "color";
/**
- * Subtype to tag an item represents a slider.
+ * Subtype to tag an item as representing a slider.
+ * @deprecated remove once support lib is update to use SUBTYPE_RANGE instead
*/
+ @Deprecated
public static final String SUBTYPE_SLIDER = "slider";
/**
+ * Subtype to tag an item as representing a range.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE} containing
+ * a {@link #SUBTYPE_VALUE} and possibly a {@link #SUBTYPE_MAX}.
+ */
+ public static final String SUBTYPE_RANGE = "range";
+ /**
+ * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_RANGE}.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
+ */
+ public static final String SUBTYPE_MAX = "max";
+ /**
+ * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_RANGE}.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
+ */
+ public static final String SUBTYPE_VALUE = "value";
+ /**
* 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}
@@ -193,12 +258,19 @@ public final class Slice implements Parcelable {
public static final String SUBTYPE_TOGGLE = "toggle";
/**
* Subtype to tag an item representing priority.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_INT}.
*/
public static final String SUBTYPE_PRIORITY = "priority";
/**
* Subtype to tag an item to use as a content description.
+ * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT}.
*/
public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
+ /**
+ * Subtype to tag an item as representing a time in milliseconds since midnight,
+ * January 1, 1970 UTC.
+ */
+ public static final String SUBTYPE_MILLIS = "millis";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
@@ -275,6 +347,14 @@ public final class Slice implements Parcelable {
}
/**
+ * Returns whether the caller for this slice matters.
+ * @see Builder#setCallerNeeded
+ */
+ public boolean isCallerNeeded() {
+ return hasHint(HINT_CALLER_NEEDED);
+ }
+
+ /**
* A Builder used to construct {@link Slice}s
*/
public static class Builder {
@@ -285,14 +365,24 @@ public final class Slice implements Parcelable {
private SliceSpec mSpec;
/**
- * Create a builder which will construct a {@link Slice} for the Given Uri.
- * @param uri Uri to tag for this slice.
+ * @deprecated TO BE REMOVED
*/
+ @Deprecated
public Builder(@NonNull Uri uri) {
mUri = uri;
}
/**
+ * Create a builder which will construct a {@link Slice} for the given Uri.
+ * @param uri Uri to tag for this slice.
+ * @param spec the spec for this slice.
+ */
+ public Builder(@NonNull Uri uri, SliceSpec spec) {
+ mUri = uri;
+ mSpec = spec;
+ }
+
+ /**
* Create a builder for a {@link Slice} that is a sub-slice of the slice
* being constructed by the provided builder.
* @param parent The builder constructing the parent slice
@@ -303,10 +393,17 @@ public final class Slice implements Parcelable {
}
/**
- * Add hints to the Slice being constructed
+ * Tells the system whether for this slice the return value of
+ * {@link SliceProvider#onBindSlice(Uri, List)} may be different depending on
+ * {@link SliceProvider#getCallingPackage()} and should not be cached for multiple
+ * apps.
*/
- public Builder addHints(@SliceHint String... hints) {
- mHints.addAll(Arrays.asList(hints));
+ public Builder setCallerNeeded(boolean callerNeeded) {
+ if (callerNeeded) {
+ mHints.add(HINT_CALLER_NEEDED);
+ } else {
+ mHints.remove(HINT_CALLER_NEEDED);
+ }
return this;
}
@@ -314,11 +411,12 @@ public final class Slice implements Parcelable {
* Add hints to the Slice being constructed
*/
public Builder addHints(@SliceHint List<String> hints) {
- return addHints(hints.toArray(new String[hints.size()]));
+ mHints.addAll(hints);
+ return this;
}
/**
- * Add the spec for this slice.
+ * @deprecated TO BE REMOVED
*/
public Builder setSpec(SliceSpec spec) {
mSpec = spec;
@@ -327,17 +425,10 @@ public final class Slice implements Parcelable {
/**
* Add a sub-slice to the slice being constructed
- */
- public Builder addSubSlice(@NonNull Slice slice) {
- return addSubSlice(slice, null);
- }
-
- /**
- * Add a sub-slice to the slice being constructed
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
*/
- public Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) {
+ public Builder addSubSlice(@NonNull Slice slice, @Nullable @SliceSubtype String subType) {
mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType,
slice.getHints().toArray(new String[slice.getHints().size()])));
return this;
@@ -345,18 +436,11 @@ public final class Slice implements Parcelable {
/**
* Add an action to the slice being constructed
- */
- public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
- return addAction(action, s, null);
- }
-
- /**
- * Add an action to the slice being constructed
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
*/
public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s,
- @Nullable String subType) {
+ @Nullable @SliceSubtype String subType) {
List<String> hints = s.getHints();
s.mSpec = null;
mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray(
@@ -369,103 +453,66 @@ public final class Slice implements Parcelable {
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
*/
- public Builder addText(CharSequence text, @Nullable String subType,
- @SliceHint String... hints) {
+ public Builder addText(CharSequence text, @Nullable @SliceSubtype String subType,
+ @SliceHint List<String> hints) {
mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints));
return this;
}
/**
- * Add text to the slice being constructed
- * @param subType Optional template-specific type information
- * @see {@link SliceItem#getSubType()}
- */
- public Builder addText(CharSequence text, @Nullable String subType,
- @SliceHint List<String> hints) {
- return addText(text, subType, hints.toArray(new String[hints.size()]));
- }
-
- /**
* Add an image to the slice being constructed
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
*/
- public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint String... hints) {
+ public Builder addIcon(Icon icon, @Nullable @SliceSubtype String subType,
+ @SliceHint List<String> hints) {
mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints));
return this;
}
/**
- * Add an image to the slice being constructed
- * @param subType Optional template-specific type information
- * @see {@link SliceItem#getSubType()}
- */
- public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint List<String> hints) {
- return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
- }
-
- /**
* Add remote input to the slice being constructed
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+ @Nullable @SliceSubtype String subType,
@SliceHint List<String> hints) {
- return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Add remote input to the slice being constructed
- * @param subType Optional template-specific type information
- * @see {@link SliceItem#getSubType()}
- */
- public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
- @SliceHint String... hints) {
mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT,
subType, hints));
return this;
}
/**
- * Add a color to the slice being constructed
+ * Add an integer 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) {
+ public Builder addInt(int value, @Nullable @SliceSubtype String subType,
+ @SliceHint List<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()}
+ * @deprecated TO BE REMOVED.
*/
- public Builder addInt(int value, @Nullable String subType,
+ @Deprecated
+ public Slice.Builder addTimestamp(long time, @Nullable @SliceSubtype 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()}
- */
- public Slice.Builder addTimestamp(long time, @Nullable String subType,
- @SliceHint String... hints) {
- mItems.add(new SliceItem(time, SliceItem.FORMAT_TIMESTAMP, subType,
- hints));
- return this;
+ return addLong(time, subType, hints);
}
/**
- * Add a timestamp to the slice being constructed
+ * Add a long to the slice being constructed
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ public Slice.Builder addLong(long value, @Nullable @SliceSubtype String subType,
@SliceHint List<String> hints) {
- return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
+ mItems.add(new SliceItem(value, SliceItem.FORMAT_LONG, subType,
+ hints.toArray(new String[hints.size()])));
+ return this;
}
/**
@@ -475,26 +522,14 @@ public final class Slice implements Parcelable {
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addBundle(Bundle bundle, @Nullable String subType,
- @SliceHint String... hints) {
+ public Slice.Builder addBundle(Bundle bundle, @Nullable @SliceSubtype String subType,
+ @SliceHint List<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 9eb2bb89..019ae492 100644
--- a/android/app/slice/SliceItem.java
+++ b/android/app/slice/SliceItem.java
@@ -67,7 +67,7 @@ public final class SliceItem implements Parcelable {
FORMAT_IMAGE,
FORMAT_ACTION,
FORMAT_INT,
- FORMAT_TIMESTAMP,
+ FORMAT_LONG,
FORMAT_REMOTE_INPUT,
FORMAT_BUNDLE,
})
@@ -98,9 +98,14 @@ public final class SliceItem implements Parcelable {
*/
public static final String FORMAT_INT = "int";
/**
- * A {@link SliceItem} that contains a timestamp.
+ * A {@link SliceItem} that contains a long.
*/
- public static final String FORMAT_TIMESTAMP = "timestamp";
+ public static final String FORMAT_LONG = "long";
+ /**
+ * @deprecated TO BE REMOVED
+ */
+ @Deprecated
+ public static final String FORMAT_TIMESTAMP = FORMAT_LONG;
/**
* A {@link SliceItem} that contains a {@link RemoteInput}.
*/
@@ -123,6 +128,14 @@ public final class SliceItem implements Parcelable {
* @hide
*/
public SliceItem(Object obj, @SliceType String format, String subType,
+ List<String> hints) {
+ this(obj, format, subType, hints.toArray(new String[hints.size()]));
+ }
+
+ /**
+ * @hide
+ */
+ public SliceItem(Object obj, @SliceType String format, String subType,
@Slice.SliceHint String[] hints) {
mHints = hints;
mFormat = format;
diff --git a/android/app/slice/SliceManager.java b/android/app/slice/SliceManager.java
index 2fa9d8e0..0285e9f9 100644
--- a/android/app/slice/SliceManager.java
+++ b/android/app/slice/SliceManager.java
@@ -16,24 +16,29 @@
package android.app.slice;
-import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.IContentProvider;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PermissionResult;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.ArrayMap;
+import android.os.UserHandle;
import android.util.Log;
-import android.util.Pair;
import com.android.internal.util.Preconditions;
@@ -42,7 +47,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* Class to handle interactions with {@link Slice}s.
@@ -60,10 +64,41 @@ public class SliceManager {
public static final String ACTION_REQUEST_SLICE_PERMISSION =
"android.intent.action.REQUEST_SLICE_PERMISSION";
+ /**
+ * Category used to resolve intents that can be rendered as slices.
+ * <p>
+ * This category should be included on intent filters on providers that extend
+ * {@link SliceProvider}.
+ * @see SliceProvider
+ * @see SliceProvider#onMapIntentToUri(Intent)
+ * @see #mapIntentToUri(Intent)
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE";
+
+ /**
+ * The meta-data key that allows an activity to easily be linked directly to a slice.
+ * <p>
+ * An activity can be statically linked to a slice uri by including a meta-data item
+ * for this key that contains a valid slice uri for the same application declaring
+ * the activity.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <activity android:name="com.example.mypkg.MyActivity">
+ * <meta-data android:name="android.metadata.SLICE_URI"
+ * android:value="content://com.example.mypkg/main_slice" />
+ * </activity>}
+ * </pre>
+ *
+ * @see #mapIntentToUri(Intent)
+ * @see SliceProvider#onMapIntentToUri(Intent)
+ */
+ public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+
private final ISliceManager mService;
private final Context mContext;
- private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup =
- new ArrayMap<>();
+ private final IBinder mToken = new Binder();
/**
* Permission denied.
@@ -91,117 +126,24 @@ public class SliceManager {
}
/**
- * @deprecated TO BE REMOVED.
- */
- @Deprecated
- public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
- @NonNull List<SliceSpec> specs) {
- registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback);
- }
-
- /**
- * @deprecated TO BE REMOVED.
- */
- @Deprecated
- public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
- @NonNull List<SliceSpec> specs, Executor executor) {
- registerSliceCallback(uri, specs, executor, callback);
- }
-
- /**
- * 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 List<SliceSpec> specs,
- @NonNull SliceCallback callback) {
- registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback);
- }
-
- /**
- * 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 List<SliceSpec> specs,
- @NonNull @CallbackExecutor Executor executor, @NonNull SliceCallback callback) {
- 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.
+ * <p>
+ * This may only be called by apps that are the default launcher for the device
+ * or the default voice interaction service. Otherwise will throw {@link SecurityException}.
*
* @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)
+ * @see Intent#ACTION_ASSIST
+ * @see Intent#CATEGORY_HOME
*/
public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) {
try {
mService.pinSlice(mContext.getPackageName(), uri,
- specs.toArray(new SliceSpec[specs.size()]));
+ specs.toArray(new SliceSpec[specs.size()]), mToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -211,14 +153,19 @@ public class SliceManager {
* Remove a pin for a slice.
* <p>
* If the slice has no other pins/callbacks then the slice will be unpinned.
+ * <p>
+ * This may only be called by apps that are the default launcher for the device
+ * or the default voice interaction service. Otherwise will throw {@link SecurityException}.
*
* @param uri The uri of the slice being unpinned.
* @see #pinSlice
* @see SliceProvider#onSliceUnpinned(Uri)
+ * @see Intent#ACTION_ASSIST
+ * @see Intent#CATEGORY_HOME
*/
public void unpinSlice(@NonNull Uri uri) {
try {
- mService.unpinSlice(mContext.getPackageName(), uri);
+ mService.unpinSlice(mContext.getPackageName(), uri, mToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -251,6 +198,18 @@ public class SliceManager {
}
/**
+ * Get the list of currently pinned slices for this app.
+ * @see SliceProvider#onSlicePinned
+ */
+ public @NonNull List<Uri> getPinnedSlices() {
+ try {
+ return Arrays.asList(mService.getPinnedSlices(mContext.getPackageName()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Obtains a list of slices that are descendants of the specified Uri.
* <p>
* Not all slice providers will implement this functionality, in which case,
@@ -262,17 +221,13 @@ public class SliceManager {
*/
public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) {
ContentResolver resolver = mContext.getContentResolver();
- IContentProvider provider = resolver.acquireProvider(uri);
- try {
+ try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
- final Bundle res = provider.call(resolver.getPackageName(),
- SliceProvider.METHOD_GET_DESCENDANTS, null, extras);
+ final Bundle res = provider.call(SliceProvider.METHOD_GET_DESCENDANTS, null, extras);
return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS);
} catch (RemoteException e) {
Log.e(TAG, "Unable to get slice descendants", e);
- } finally {
- resolver.releaseProvider(provider);
}
return Collections.emptyList();
}
@@ -288,17 +243,15 @@ public class SliceManager {
public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(uri, "uri");
ContentResolver resolver = mContext.getContentResolver();
- IContentProvider provider = resolver.acquireProvider(uri);
- if (provider == null) {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- try {
+ try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
new ArrayList<>(supportedSpecs));
- final Bundle res = provider.call(mContext.getPackageName(), SliceProvider.METHOD_SLICE,
- null, extras);
+ final Bundle res = provider.call(SliceProvider.METHOD_SLICE, null, extras);
Bundle.setDefusable(res, true);
if (res == null) {
return null;
@@ -308,8 +261,80 @@ public class SliceManager {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
- } finally {
- resolver.releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Turns a slice intent into a slice uri. Expects an explicit intent.
+ * <p>
+ * This goes through a several stage resolution process to determine if any slice
+ * can represent this intent.
+ * <ol>
+ * <li> If the intent contains data that {@link ContentResolver#getType} is
+ * {@link SliceProvider#SLICE_TYPE} then the data will be returned.</li>
+ * <li>If the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then
+ * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result
+ * will be returned.</li>
+ * <li>Lastly, if the intent explicitly points at an activity, and that activity has
+ * meta-data for key {@link #SLICE_METADATA_KEY}, then the Uri specified there will be
+ * returned.</li>
+ * <li>If no slice is found, then {@code null} is returned.</li>
+ * </ol>
+ * @param intent The intent associated with a slice.
+ * @return The Slice Uri provided by the app or null if none exists.
+ * @see Slice
+ * @see SliceProvider#onMapIntentToUri(Intent)
+ * @see Intent
+ */
+ public @Nullable Uri mapIntentToUri(@NonNull Intent intent) {
+ Preconditions.checkNotNull(intent, "intent");
+ Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
+ || intent.getData() != null,
+ "Slice intent must be explicit %s", intent);
+ ContentResolver resolver = mContext.getContentResolver();
+
+ // Check if the intent has data for the slice uri on it and use that
+ final Uri intentData = intent.getData();
+ if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
+ return intentData;
+ }
+ // Otherwise ask the app
+ Intent queryIntent = new Intent(intent);
+ if (!queryIntent.hasCategory(CATEGORY_SLICE)) {
+ queryIntent.addCategory(CATEGORY_SLICE);
+ }
+ List<ResolveInfo> providers =
+ mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0);
+ if (providers == null || providers.isEmpty()) {
+ // There are no providers, see if this activity has a direct link.
+ ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent,
+ PackageManager.GET_META_DATA);
+ if (resolve != null && resolve.activityInfo != null
+ && resolve.activityInfo.metaData != null
+ && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) {
+ return Uri.parse(
+ resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY));
+ }
+ return null;
+ }
+ String authority = providers.get(0).providerInfo.authority;
+ Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).build();
+ try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ Bundle extras = new Bundle();
+ extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ final Bundle res = provider.call(SliceProvider.METHOD_MAP_ONLY_INTENT, null, extras);
+ if (res == null) {
+ return null;
+ }
+ return res.getParcelable(SliceProvider.EXTRA_SLICE);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
}
}
@@ -328,8 +353,9 @@ public class SliceManager {
public @Nullable Slice bindSlice(@NonNull Intent intent,
@NonNull List<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(intent, "intent");
- Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
- "Slice intent must be explicit " + intent);
+ Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
+ || intent.getData() != null,
+ "Slice intent must be explicit %s", intent);
ContentResolver resolver = mContext.getContentResolver();
// Check if the intent has data for the slice uri on it and use that
@@ -340,23 +366,30 @@ public class SliceManager {
// Otherwise ask the app
List<ResolveInfo> providers =
mContext.getPackageManager().queryIntentContentProviders(intent, 0);
- if (providers == null) {
- throw new IllegalArgumentException("Unable to resolve intent " + intent);
+ if (providers == null || providers.isEmpty()) {
+ // There are no providers, see if this activity has a direct link.
+ ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent,
+ PackageManager.GET_META_DATA);
+ if (resolve != null && resolve.activityInfo != null
+ && resolve.activityInfo.metaData != null
+ && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) {
+ return bindSlice(Uri.parse(resolve.activityInfo.metaData
+ .getString(SLICE_METADATA_KEY)), supportedSpecs);
+ }
+ return null;
}
String authority = providers.get(0).providerInfo.authority;
Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority).build();
- IContentProvider provider = resolver.acquireProvider(uri);
- if (provider == null) {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- try {
+ try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) {
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
new ArrayList<>(supportedSpecs));
- final Bundle res = provider.call(mContext.getPackageName(),
- SliceProvider.METHOD_MAP_INTENT, null, extras);
+ final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
return null;
}
@@ -365,21 +398,82 @@ public class SliceManager {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
- } finally {
- resolver.releaseProvider(provider);
}
}
/**
+ * Determine whether a particular process and user ID has been granted
+ * permission to access a specific slice URI.
+ *
+ * @param uri The uri that is being checked.
+ * @param pid The process ID being checked against. Must be &gt; 0.
+ * @param uid The user ID being checked against. A uid of 0 is the root
+ * user, which will pass every permission check.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the given
+ * pid/uid is allowed to access that uri, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see #grantSlicePermission(String, Uri)
+ */
+ public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) {
+ // TODO: Switch off Uri permissions.
+ return mContext.checkUriPermission(uri, pid, uid,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ }
+
+ /**
+ * Grant permission to access a specific slice Uri to another package.
+ *
+ * @param toPackage The package you would like to allow to access the Uri.
+ * @param uri The Uri you would like to grant access to.
+ *
+ * @see #revokeSlicePermission
+ */
+ public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
+ // TODO: Switch off Uri permissions.
+ mContext.grantUriPermission(toPackage, uri,
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ }
+
+ /**
+ * Remove permissions to access a particular content provider Uri
+ * that were previously added with {@link #grantSlicePermission} for a specific target
+ * package. The given Uri will match all previously granted Uris that are the same or a
+ * sub-path of the given Uri. That is, revoking "content://foo/target" will
+ * revoke both "content://foo/target" and "content://foo/target/sub", but not
+ * "content://foo". It will not remove any prefix grants that exist at a
+ * higher level.
+ *
+ * @param toPackage The package you would like to allow to access the Uri.
+ * @param uri The Uri you would like to revoke access to.
+ *
+ * @see #grantSlicePermission
+ */
+ public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
+ // TODO: Switch off Uri permissions.
+ mContext.revokeUriPermission(toPackage, uri,
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ }
+
+ /**
* Does the permission check to see if a caller has access to a specific slice.
* @hide
*/
- public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) {
+ public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid,
+ String[] autoGrantPermissions) {
try {
+ if (UserHandle.isSameApp(uid, Process.myUid())) {
+ return;
+ }
if (pkg == null) {
throw new SecurityException("No pkg specified");
}
- int result = mService.checkSlicePermission(uri, pkg, pid, uid);
+ int result = mService.checkSlicePermission(uri, pkg, pid, uid, autoGrantPermissions);
if (result == PERMISSION_DENIED) {
throw new SecurityException("User " + uid + " does not have slice permission for "
+ uri + ".");
@@ -391,6 +485,8 @@ public class SliceManager {
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ // Notify a change has happened because we just granted a permission.
+ mContext.getContentResolver().notifyChange(uri, null);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -408,18 +504,4 @@ public class SliceManager {
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/SliceMetrics.java b/android/app/slice/SliceMetrics.java
new file mode 100644
index 00000000..20c1390b
--- /dev/null
+++ b/android/app/slice/SliceMetrics.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 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.content.Context;
+import android.net.Uri;
+
+import com.android.internal.logging.MetricsLogger;
+
+/**
+ * Metrics interface for slices.
+ *
+ * This is called by SliceView, so Slice developers should
+ * not need to reference this class.
+ *
+ * @see androidx.slice.widget.SliceView
+ */
+public class SliceMetrics {
+
+ private static final String TAG = "SliceMetrics";
+ private MetricsLogger mMetricsLogger;
+
+ /**
+ * An object to be used throughout the life of a slice to register events.
+ */
+ public SliceMetrics(@NonNull Context context, @NonNull Uri uri) {
+ mMetricsLogger = new MetricsLogger();
+ }
+
+ /**
+ * To be called whenever the slice becomes visible to the user.
+ */
+ public void logVisible() {
+ }
+
+ /**
+ * To be called whenever the slice becomes invisible to the user.
+ */
+ public void logHidden() {
+ }
+
+ /**
+ * To be called whenever the user invokes a discrete action via a slice.
+ *
+ * <P>
+ * Use this for discrete events like a tap or the end of a drag,
+ * not for a continuous streams of events, such as the motion during a gesture.
+ * </P>
+ *
+ * @see androidx.slice.widget.EventInfo#actionType
+ *
+ * @param actionType The type of the event.
+ * @param subSlice The URI of the sub-slice that is the subject of the interaction.
+ */
+ public void logTouch(int actionType, @NonNull Uri subSlice) {
+ }
+}
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 00e8ccad..fe5742d6 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -16,7 +16,6 @@
package android.app.slice;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -35,18 +34,16 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.Looper;
import android.os.Process;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
-import android.os.UserHandle;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
/**
* A SliceProvider allows an app to provide content to be displayed in system spaces. This content
@@ -65,8 +62,8 @@ import java.util.concurrent.CountDownLatch;
* <pre class="prettyprint">
* {@literal
* <provider
- * android:name="com.android.mypkg.MySliceProvider"
- * android:authorities="com.android.mypkg" />}
+ * android:name="com.example.mypkg.MySliceProvider"
+ * android:authorities="com.example.mypkg" />}
* </pre>
* <p>
* Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider
@@ -77,10 +74,11 @@ import java.util.concurrent.CountDownLatch;
* <pre class="prettyprint">
* {@literal
* <provider
- * android:name="com.android.mypkg.MySliceProvider"
- * android:authorities="com.android.mypkg">
+ * android:name="com.example.mypkg.MySliceProvider"
+ * android:authorities="com.example.mypkg">
* <intent-filter>
- * <action android:name="android.intent.action.MY_SLICE_INTENT" />
+ * <action android:name="com.example.mypkg.intent.action.MY_SLICE_INTENT" />
+ * <category android:name="android.app.slice.category.SLICE" />
* </intent-filter>
* </provider>}
* </pre>
@@ -114,6 +112,10 @@ public abstract class SliceProvider extends ContentProvider {
/**
* @hide
*/
+ public static final String METHOD_MAP_ONLY_INTENT = "map_only";
+ /**
+ * @hide
+ */
public static final String METHOD_PIN = "pin";
/**
* @hide
@@ -143,24 +145,33 @@ public abstract class SliceProvider extends ContentProvider {
* @hide
*/
public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
- /**
- * @hide
- */
- public static final String EXTRA_OVERRIDE_PKG = "override_pkg";
private static final boolean DEBUG = false;
- private String mBindingPkg;
+ private static final long SLICE_BIND_ANR = 2000;
+ private final String[] mAutoGrantPermissions;
+
+ private String mCallback;
private SliceManager mSliceManager;
/**
- * Return the package name of the caller that initiated the binding request
- * currently happening. The returned package will have been
- * verified to belong to the calling UID. Returns {@code null} if not
- * currently performing an {@link #onBindSlice(Uri, List)}.
+ * A version of constructing a SliceProvider that allows autogranting slice permissions
+ * to apps that hold specific platform permissions.
+ * <p>
+ * When an app tries to bind a slice from this provider that it does not have access to,
+ * This provider will check if the caller holds permissions to any of the autoGrantPermissions
+ * specified, if they do they will be granted persisted uri access to all slices of this
+ * provider.
+ *
+ * @param autoGrantPermissions List of permissions that holders are auto-granted access
+ * to slices.
*/
- public final @Nullable String getBindingPackage() {
- return mBindingPkg;
+ public SliceProvider(@NonNull String... autoGrantPermissions) {
+ mAutoGrantPermissions = autoGrantPermissions;
+ }
+
+ public SliceProvider() {
+ mAutoGrantPermissions = new String[0];
}
@Override
@@ -170,12 +181,12 @@ public abstract class SliceProvider extends ContentProvider {
}
/**
- * Implemented to create a slice. Will be called on the main thread.
+ * Implemented to create a slice.
* <p>
* onBindSlice should return as quickly as possible so that the UI tied
* to this slice can be responsive. No network or other IO will be allowed
* during onBindSlice. Any loading that needs to be done should happen
- * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+ * in the background with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
* when the app is ready to provide the complete data in onBindSlice.
* <p>
* The slice returned should have a spec that is compatible with one of
@@ -241,14 +252,36 @@ public abstract class SliceProvider extends ContentProvider {
* 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}.
*
+ * Any intent filter added to a slice provider should also contain
+ * {@link SliceManager#CATEGORY_SLICE}, because otherwise it will not be detected by
+ * {@link SliceManager#mapIntentToUri(Intent)}.
+ *
* @return Uri representing the slice associated with the provided intent.
- * @see {@link Slice}
+ * @see Slice
+ * @see SliceManager#mapIntentToUri(Intent)
*/
public @NonNull Uri onMapIntentToUri(Intent intent) {
throw new UnsupportedOperationException(
"This provider has not implemented intent to uri mapping");
}
+ /**
+ * Called when an app requests a slice it does not have write permission
+ * to the uri for.
+ * <p>
+ * The return value will be the action on a slice that prompts the user that
+ * the calling app wants to show slices from this app. The default implementation
+ * launches a dialog that allows the user to grant access to this slice. Apps
+ * that do not want to allow this user grant, can override this and instead
+ * launch their own dialog with different behavior.
+ *
+ * @param sliceUri the Uri of the slice attempting to be bound.
+ * @see #getCallingPackage()
+ */
+ public @NonNull PendingIntent onCreatePermissionRequest(Uri sliceUri) {
+ return createPermissionIntent(getContext(), sliceUri, getCallingPackage());
+ }
+
@Override
public final int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
@@ -302,13 +335,10 @@ public abstract class SliceProvider extends ContentProvider {
List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
String callingPackage = getCallingPackage();
- if (extras.containsKey(EXTRA_OVERRIDE_PKG)) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system can override calling pkg");
- }
- callingPackage = extras.getString(EXTRA_OVERRIDE_PKG);
- }
- Slice s = handleBindSlice(uri, supportedSpecs, callingPackage);
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+
+ Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid);
Bundle b = new Bundle();
b.putParcelable(EXTRA_SLICE, s);
return b;
@@ -319,12 +349,20 @@ public abstract class SliceProvider extends ContentProvider {
List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
Bundle b = new Bundle();
if (uri != null) {
- Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage());
+ Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage(),
+ Binder.getCallingUid(), Binder.getCallingPid());
b.putParcelable(EXTRA_SLICE, s);
} else {
b.putParcelable(EXTRA_SLICE, null);
}
return b;
+ } else if (method.equals(METHOD_MAP_ONLY_INTENT)) {
+ Intent intent = extras.getParcelable(EXTRA_INTENT);
+ if (intent == null) return null;
+ Uri uri = onMapIntentToUri(intent);
+ Bundle b = new Bundle();
+ b.putParcelable(EXTRA_SLICE, uri);
+ return b;
} else if (method.equals(METHOD_PIN)) {
Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
@@ -348,102 +386,80 @@ public abstract class SliceProvider extends ContentProvider {
}
private Collection<Uri> handleGetDescendants(Uri uri) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
+ mCallback = "onGetSliceDescendants";
+ Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);
+ try {
return onGetSliceDescendants(uri);
- } else {
- CountDownLatch latch = new CountDownLatch(1);
- Collection<Uri>[] output = new Collection[1];
- Handler.getMain().post(() -> {
- output[0] = onGetSliceDescendants(uri);
- latch.countDown();
- });
- try {
- latch.await();
- return output[0];
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ } finally {
+ Handler.getMain().removeCallbacks(mAnr);
}
}
private void handlePinSlice(Uri sliceUri) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
+ mCallback = "onSlicePinned";
+ Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);
+ try {
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);
- }
+ } finally {
+ Handler.getMain().removeCallbacks(mAnr);
}
}
private void handleUnpinSlice(Uri sliceUri) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
+ mCallback = "onSliceUnpinned";
+ Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);
+ try {
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);
- }
+ } finally {
+ Handler.getMain().removeCallbacks(mAnr);
}
}
private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,
- String callingPkg) {
+ String callingPkg, int callingUid, int callingPid) {
// This can be removed once Slice#bindSlice is removed and everyone is using
// SliceManager#bindSlice.
String pkg = callingPkg != null ? callingPkg
- : getContext().getPackageManager().getNameForUid(Binder.getCallingUid());
- if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
- try {
- mSliceManager.enforceSlicePermission(sliceUri, pkg,
- Binder.getCallingPid(), Binder.getCallingUid());
- } catch (SecurityException e) {
- return createPermissionSlice(getContext(), sliceUri, pkg);
- }
+ : getContext().getPackageManager().getNameForUid(callingUid);
+ try {
+ mSliceManager.enforceSlicePermission(sliceUri, pkg,
+ callingPid, callingUid, mAutoGrantPermissions);
+ } catch (SecurityException e) {
+ return createPermissionSlice(getContext(), sliceUri, pkg);
}
- if (Looper.myLooper() == Looper.getMainLooper()) {
- return onBindSliceStrict(sliceUri, supportedSpecs, pkg);
- } else {
- CountDownLatch latch = new CountDownLatch(1);
- Slice[] output = new Slice[1];
- Handler.getMain().post(() -> {
- output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg);
- latch.countDown();
- });
- try {
- latch.await();
- return output[0];
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
+ mCallback = "onBindSlice";
+ Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);
+ try {
+ return onBindSliceStrict(sliceUri, supportedSpecs);
+ } finally {
+ Handler.getMain().removeCallbacks(mAnr);
}
}
/**
* @hide
*/
- public static Slice createPermissionSlice(Context context, Uri sliceUri,
+ public Slice createPermissionSlice(Context context, Uri sliceUri,
String callingPackage) {
- return new Slice.Builder(sliceUri)
- .addAction(createPermissionIntent(context, sliceUri, callingPackage),
- new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build())
- .addText(getPermissionString(context, callingPackage), null)
- .build())
- .addHints(Slice.HINT_LIST_ITEM)
- .build();
+ PendingIntent action;
+ mCallback = "onCreatePermissionRequest";
+ Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);
+ try {
+ action = onCreatePermissionRequest(sliceUri);
+ } finally {
+ Handler.getMain().removeCallbacks(mAnr);
+ }
+ Slice.Builder parent = new Slice.Builder(sliceUri);
+ Slice.Builder childAction = new Slice.Builder(parent)
+ .addHints(Arrays.asList(Slice.HINT_TITLE, Slice.HINT_SHORTCUT))
+ .addAction(action, new Slice.Builder(parent).build(), null);
+
+ parent.addSubSlice(new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build())
+ .addText(getPermissionString(context, callingPackage), null,
+ Collections.emptyList())
+ .addSubSlice(childAction.build(), null)
+ .build(), null);
+ return parent.addHints(Arrays.asList(Slice.HINT_PERMISSION_REQUEST)).build();
}
/**
@@ -480,19 +496,21 @@ public abstract class SliceProvider extends ContentProvider {
}
}
- private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs,
- String callingPackage) {
+ private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyDeath()
.build());
- mBindingPkg = callingPackage;
return onBindSlice(sliceUri, supportedSpecs);
} finally {
- mBindingPkg = null;
StrictMode.setThreadPolicy(oldPolicy);
}
}
+
+ private final Runnable mAnr = () -> {
+ Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT);
+ Log.wtf(TAG, "Timed out while handling slice callback " + mCallback);
+ };
}
diff --git a/android/app/slice/SliceSpec.java b/android/app/slice/SliceSpec.java
index 8cc0384c..03ffe6df 100644
--- a/android/app/slice/SliceSpec.java
+++ b/android/app/slice/SliceSpec.java
@@ -89,7 +89,7 @@ public final class SliceSpec implements Parcelable {
*
* @param candidate candidate format of data.
* @return true if versions are compatible.
- * @see androidx.app.slice.widget.SliceView
+ * @see androidx.slice.widget.SliceView
*/
public boolean canRender(@NonNull SliceSpec candidate) {
if (!mType.equals(candidate.mType)) return false;
diff --git a/android/app/timezone/RulesManager.java b/android/app/timezone/RulesManager.java
index 0a38eb9a..fe83113b 100644
--- a/android/app/timezone/RulesManager.java
+++ b/android/app/timezone/RulesManager.java
@@ -36,7 +36,7 @@ import java.util.Arrays;
* <p>This interface is intended for use with the default APK-based time zone rules update
* application but it can also be used by OEMs if that mechanism is turned off using configuration.
* All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
- * permission.
+ * permission unless otherwise stated.
*
* <p>When using the default mechanism, when properly configured the Android system will send a
* {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
@@ -68,6 +68,23 @@ public final class RulesManager {
private static final String TAG = "timezone.RulesManager";
private static final boolean DEBUG = false;
+ /**
+ * The action of the intent that the Android system will broadcast when a time zone rules update
+ * operation has been successfully staged (i.e. to be applied next reboot) or unstaged.
+ *
+ * <p>See {@link #EXTRA_OPERATION_STAGED}
+ *
+ * <p>This is a protected intent that can only be sent by the system.
+ */
+ public static final String ACTION_RULES_UPDATE_OPERATION =
+ "com.android.intent.action.timezone.RULES_UPDATE_OPERATION";
+
+ /**
+ * The key for a boolean extra for the {@link #ACTION_RULES_UPDATE_OPERATION} intent used to
+ * indicate whether the operation was a "stage" or an "unstage".
+ */
+ public static final String EXTRA_OPERATION_STAGED = "staged";
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "SUCCESS", "ERROR_" }, value = {
SUCCESS,
@@ -103,9 +120,12 @@ public final class RulesManager {
/**
* Returns information about the current time zone rules state such as the IANA version of
- * the system and any currently installed distro. This method is intended to allow clients to
- * determine if the current state can be improved; for example by passing the information to a
- * server that may provide a new distro for download.
+ * the system and any currently installed distro. This method allows clients to determine the
+ * current device state, perhaps to see if it can be improved; for example by passing the
+ * information to a server that may provide a new distro for download.
+ *
+ * <p>Callers must possess the {@link android.Manifest.permission#QUERY_TIME_ZONE_RULES} system
+ * permission.
*/
public RulesState getRulesState() {
try {
diff --git a/android/app/usage/AppStandbyInfo.java b/android/app/usage/AppStandbyInfo.java
new file mode 100644
index 00000000..51fe0e27
--- /dev/null
+++ b/android/app/usage/AppStandbyInfo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A pair of {package, bucket} to denote the app standby bucket for a given package.
+ * Used as a vehicle of data across the binder IPC.
+ * @hide
+ */
+public final class AppStandbyInfo implements Parcelable {
+
+ public String mPackageName;
+ public @UsageStatsManager.StandbyBuckets int mStandbyBucket;
+
+ private AppStandbyInfo(Parcel in) {
+ mPackageName = in.readString();
+ mStandbyBucket = in.readInt();
+ }
+
+ public AppStandbyInfo(String packageName, int bucket) {
+ mPackageName = packageName;
+ mStandbyBucket = bucket;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mStandbyBucket);
+ }
+
+ public static final Creator<AppStandbyInfo> CREATOR = new Creator<AppStandbyInfo>() {
+ @Override
+ public AppStandbyInfo createFromParcel(Parcel source) {
+ return new AppStandbyInfo(source);
+ }
+
+ @Override
+ public AppStandbyInfo[] newArray(int size) {
+ return new AppStandbyInfo[size];
+ }
+ };
+}
diff --git a/android/app/usage/EventStats.java b/android/app/usage/EventStats.java
new file mode 100644
index 00000000..ea95a055
--- /dev/null
+++ b/android/app/usage/EventStats.java
@@ -0,0 +1,181 @@
+/**
+ * Copyright (C) 2018 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains usage statistics for an event type for a specific
+ * time range.
+ */
+public final class EventStats implements Parcelable {
+
+ /**
+ * {@hide}
+ */
+ public int mEventType;
+
+ /**
+ * {@hide}
+ */
+ public long mBeginTimeStamp;
+
+ /**
+ * {@hide}
+ */
+ public long mEndTimeStamp;
+
+ /**
+ * {@hide}
+ */
+ public long mLastEventTime;
+
+ /**
+ * {@hide}
+ */
+ public long mTotalTime;
+
+ /**
+ * {@hide}
+ */
+ public int mCount;
+
+ /**
+ * {@hide}
+ */
+ public EventStats() {
+ }
+
+ public EventStats(EventStats stats) {
+ mEventType = stats.mEventType;
+ mBeginTimeStamp = stats.mBeginTimeStamp;
+ mEndTimeStamp = stats.mEndTimeStamp;
+ mLastEventTime = stats.mLastEventTime;
+ mTotalTime = stats.mTotalTime;
+ mCount = stats.mCount;
+ }
+
+ /**
+ * Return the type of event this is usage for. May be one of the event
+ * constants in {@link UsageEvents.Event}.
+ */
+ public int getEventType() {
+ return mEventType;
+ }
+
+ /**
+ * Get the beginning of the time range this {@link android.app.usage.EventStats} represents,
+ * measured in milliseconds since the epoch.
+ * <p/>
+ * See {@link System#currentTimeMillis()}.
+ */
+ public long getFirstTimeStamp() {
+ return mBeginTimeStamp;
+ }
+
+ /**
+ * Get the end of the time range this {@link android.app.usage.EventStats} represents,
+ * measured in milliseconds since the epoch.
+ * <p/>
+ * See {@link System#currentTimeMillis()}.
+ */
+ public long getLastTimeStamp() {
+ return mEndTimeStamp;
+ }
+
+ /**
+ * Get the last time this event triggered, measured in milliseconds since the epoch.
+ * <p/>
+ * See {@link System#currentTimeMillis()}.
+ */
+ public long getLastEventTime() {
+ return mLastEventTime;
+ }
+
+ /**
+ * Return the number of times that this event occurred over the interval.
+ */
+ public int getCount() {
+ return mCount;
+ }
+
+ /**
+ * Get the total time this event was active, measured in milliseconds.
+ */
+ public long getTotalTime() {
+ return mTotalTime;
+ }
+
+ /**
+ * Add the statistics from the right {@link EventStats} to the left. The event type for
+ * both {@link UsageStats} objects must be the same.
+ * @param right The {@link EventStats} object to merge into this one.
+ * @throws java.lang.IllegalArgumentException if the event types of the two
+ * {@link UsageStats} objects are different.
+ */
+ public void add(EventStats right) {
+ if (mEventType != right.mEventType) {
+ throw new IllegalArgumentException("Can't merge EventStats for event #"
+ + mEventType + " with EventStats for event #" + right.mEventType);
+ }
+
+ // We use the mBeginTimeStamp due to a bug where UsageStats files can overlap with
+ // regards to their mEndTimeStamp.
+ if (right.mBeginTimeStamp > mBeginTimeStamp) {
+ mLastEventTime = Math.max(mLastEventTime, right.mLastEventTime);
+ }
+ mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
+ mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
+ mTotalTime += right.mTotalTime;
+ mCount += right.mCount;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mEventType);
+ dest.writeLong(mBeginTimeStamp);
+ dest.writeLong(mEndTimeStamp);
+ dest.writeLong(mLastEventTime);
+ dest.writeLong(mTotalTime);
+ dest.writeInt(mCount);
+ }
+
+ public static final Creator<EventStats> CREATOR = new Creator<EventStats>() {
+ @Override
+ public EventStats createFromParcel(Parcel in) {
+ EventStats stats = new EventStats();
+ stats.mEventType = in.readInt();
+ stats.mBeginTimeStamp = in.readLong();
+ stats.mEndTimeStamp = in.readLong();
+ stats.mLastEventTime = in.readLong();
+ stats.mTotalTime = in.readLong();
+ stats.mCount = in.readInt();
+ return stats;
+ }
+
+ @Override
+ public EventStats[] newArray(int size) {
+ return new EventStats[size];
+ }
+ };
+}
diff --git a/android/app/usage/NetworkStats.java b/android/app/usage/NetworkStats.java
index da36157d..7252f028 100644
--- a/android/app/usage/NetworkStats.java
+++ b/android/app/usage/NetworkStats.java
@@ -24,7 +24,6 @@ import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.IntArray;
import android.util.Log;
@@ -69,6 +68,11 @@ public final class NetworkStats implements AutoCloseable {
private int mTag = android.net.NetworkStats.TAG_NONE;
/**
+ * State in case it was not specified in the query.
+ */
+ private int mState = Bucket.STATE_ALL;
+
+ /**
* The session while the query requires it, null if all the stats have been collected or close()
* has been called.
*/
@@ -98,9 +102,8 @@ public final class NetworkStats implements AutoCloseable {
/** @hide */
NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp,
- long endTimestamp) throws RemoteException, SecurityException {
- final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ long endTimestamp, INetworkStatsService statsService)
+ throws RemoteException, SecurityException {
// Open network stats session
mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName());
mCloseGuard.open("close");
@@ -269,6 +272,15 @@ public final class NetworkStats implements AutoCloseable {
private long mTxBytes;
private long mTxPackets;
+ private static int convertSet(@State int state) {
+ switch (state) {
+ case STATE_ALL: return android.net.NetworkStats.SET_ALL;
+ case STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT;
+ case STATE_FOREGROUND: return android.net.NetworkStats.SET_FOREGROUND;
+ }
+ return 0;
+ }
+
private static @State int convertState(int networkStatsSet) {
switch (networkStatsSet) {
case android.net.NetworkStats.SET_ALL : return STATE_ALL;
@@ -529,20 +541,13 @@ public final class NetworkStats implements AutoCloseable {
/**
* Collects history results for uid and resets history enumeration index.
*/
- void startHistoryEnumeration(int uid) {
- startHistoryEnumeration(uid, android.net.NetworkStats.TAG_NONE);
- }
-
- /**
- * Collects history results for uid and resets history enumeration index.
- */
- void startHistoryEnumeration(int uid, int tag) {
+ void startHistoryEnumeration(int uid, int tag, int state) {
mHistory = null;
try {
mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid,
- android.net.NetworkStats.SET_ALL, tag,
- NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
- setSingleUidTag(uid, tag);
+ Bucket.convertSet(state), tag, NetworkStatsHistory.FIELD_ALL,
+ mStartTimeStamp, mEndTimeStamp);
+ setSingleUidTagState(uid, tag, state);
} catch (RemoteException e) {
Log.w(TAG, e);
// Leaving mHistory null
@@ -638,6 +643,7 @@ public final class NetworkStats implements AutoCloseable {
fillBucketFromSummaryEntry(bucket);
return bucket;
}
+
/**
* Getting the next item in a history enumeration.
* @param bucketOut Next item will be set here.
@@ -650,7 +656,7 @@ public final class NetworkStats implements AutoCloseable {
mRecycledHistoryEntry);
bucketOut.mUid = Bucket.convertUid(getUid());
bucketOut.mTag = Bucket.convertTag(mTag);
- bucketOut.mState = Bucket.STATE_ALL;
+ bucketOut.mState = mState;
bucketOut.mDefaultNetwork = Bucket.DEFAULT_NETWORK_ALL;
bucketOut.mMetered = Bucket.METERED_ALL;
bucketOut.mRoaming = Bucket.ROAMING_ALL;
@@ -693,9 +699,10 @@ public final class NetworkStats implements AutoCloseable {
return mUidOrUidIndex;
}
- private void setSingleUidTag(int uid, int tag) {
+ private void setSingleUidTagState(int uid, int tag, int state) {
mUidOrUidIndex = uid;
mTag = tag;
+ mState = state;
}
private void stepUid() {
diff --git a/android/app/usage/NetworkStatsManager.java b/android/app/usage/NetworkStatsManager.java
index 5576e86e..b2fe9586 100644
--- a/android/app/usage/NetworkStatsManager.java
+++ b/android/app/usage/NetworkStatsManager.java
@@ -37,6 +37,8 @@ import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Provides access to network usage history and statistics. Usage data is collected in
* discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details.
@@ -107,9 +109,15 @@ public class NetworkStatsManager {
* {@hide}
*/
public NetworkStatsManager(Context context) throws ServiceNotFoundException {
+ this(context, INetworkStatsService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)));
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public NetworkStatsManager(Context context, INetworkStatsService service) {
mContext = context;
- mService = INetworkStatsService.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE));
+ mService = service;
setPollOnOpen(true);
}
@@ -135,7 +143,8 @@ public class NetworkStatsManager {
public Bucket querySummaryForDevice(NetworkTemplate template,
long startTime, long endTime) throws SecurityException, RemoteException {
Bucket bucket = null;
- NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime);
+ NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime,
+ mService);
bucket = stats.getDeviceSummaryForNetwork();
stats.close();
@@ -208,7 +217,7 @@ public class NetworkStatsManager {
}
NetworkStats stats;
- stats = new NetworkStats(mContext, template, mFlags, startTime, endTime);
+ stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
stats.startSummaryEnumeration();
stats.close();
@@ -245,7 +254,7 @@ public class NetworkStatsManager {
}
NetworkStats result;
- result = new NetworkStats(mContext, template, mFlags, startTime, endTime);
+ result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
result.startSummaryEnumeration();
return result;
@@ -254,20 +263,31 @@ public class NetworkStatsManager {
/**
* Query network usage statistics details for a given uid.
*
- * #see queryDetailsForUidTag(int, String, long, long, int, int)
+ * #see queryDetailsForUidTagState(int, String, long, long, int, int, int)
*/
public NetworkStats queryDetailsForUid(int networkType, String subscriberId,
- long startTime, long endTime, int uid) throws SecurityException, RemoteException {
- return queryDetailsForUidTag(networkType, subscriberId, startTime, endTime, uid,
- NetworkStats.Bucket.TAG_NONE);
+ long startTime, long endTime, int uid) throws SecurityException {
+ return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
+ NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
+ }
+
+ /**
+ * Query network usage statistics details for a given uid and tag.
+ *
+ * #see queryDetailsForUidTagState(int, String, long, long, int, int, int)
+ */
+ public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId,
+ long startTime, long endTime, int uid, int tag) throws SecurityException {
+ return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
+ tag, NetworkStats.Bucket.STATE_ALL);
}
/**
- * Query network usage statistics details for a given uid and tag. Only usable for uids
- * belonging to calling user. Result is aggregated over state but not aggregated over time.
- * This means buckets' start and end timestamps are going to be between 'startTime' and
- * 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL}, uid the
- * same as the 'uid' parameter and tag the same as 'tag' parameter.
+ * Query network usage statistics details for a given uid, tag, and state. Only usable for uids
+ * belonging to calling user. Result is not aggregated over time. This means buckets' start and
+ * end timestamps are going to be between 'startTime' and 'endTime' parameters. The uid is going
+ * to be the same as the 'uid' parameter, the tag the same as the 'tag' parameter, and the state
+ * the same as the 'state' parameter.
* defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
* metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
* roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
@@ -288,17 +308,18 @@ public class NetworkStatsManager {
* @return Statistics object or null if an error happened during statistics collection.
* @throws SecurityException if permissions are insufficient to read network statistics.
*/
- public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId,
- long startTime, long endTime, int uid, int tag) throws SecurityException {
+ public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId,
+ long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
NetworkTemplate template;
template = createTemplate(networkType, subscriberId);
NetworkStats result;
try {
- result = new NetworkStats(mContext, template, mFlags, startTime, endTime);
- result.startHistoryEnumeration(uid, tag);
+ result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+ result.startHistoryEnumeration(uid, tag, state);
} catch (RemoteException e) {
- Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag, e);
+ Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag
+ + " state=" + state, e);
return null;
}
@@ -341,7 +362,7 @@ public class NetworkStatsManager {
}
NetworkStats result;
- result = new NetworkStats(mContext, template, mFlags, startTime, endTime);
+ result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
result.startUserUidEnumeration();
return result;
}
@@ -451,19 +472,20 @@ public class NetworkStatsManager {
}
private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
- NetworkTemplate template = null;
+ final NetworkTemplate template;
switch (networkType) {
- case ConnectivityManager.TYPE_MOBILE: {
- template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
- } break;
- case ConnectivityManager.TYPE_WIFI: {
+ case ConnectivityManager.TYPE_MOBILE:
+ template = subscriberId == null
+ ? NetworkTemplate.buildTemplateMobileWildcard()
+ : NetworkTemplate.buildTemplateMobileAll(subscriberId);
+ break;
+ case ConnectivityManager.TYPE_WIFI:
template = NetworkTemplate.buildTemplateWifiWildcard();
- } break;
- default: {
+ break;
+ default:
throw new IllegalArgumentException("Cannot create template for network type "
+ networkType + ", subscriberId '"
+ NetworkIdentity.scrubSubscriberId(subscriberId) + "'.");
- }
}
return template;
}
diff --git a/android/app/usage/TimeSparseArray.java b/android/app/usage/TimeSparseArray.java
index 7974fa70..9ef88e41 100644
--- a/android/app/usage/TimeSparseArray.java
+++ b/android/app/usage/TimeSparseArray.java
@@ -17,6 +17,7 @@
package android.app.usage;
import android.util.LongSparseArray;
+import android.util.Slog;
/**
* An array that indexes by a long timestamp, representing milliseconds since the epoch.
@@ -24,6 +25,8 @@ import android.util.LongSparseArray;
* {@hide}
*/
public class TimeSparseArray<E> extends LongSparseArray<E> {
+ private static final String TAG = TimeSparseArray.class.getSimpleName();
+
public TimeSparseArray() {
super();
}
@@ -70,6 +73,30 @@ public class TimeSparseArray<E> extends LongSparseArray<E> {
}
/**
+ * {@inheritDoc}
+ *
+ * Overridden to ensure no collisions. The key (time in milliseconds) is incremented till an
+ * empty place is found.
+ */
+ @Override
+ public void put(long key, E value) {
+ final long origKey = key;
+ int keyIndex = indexOfKey(key);
+ if (keyIndex >= 0) {
+ final long sz = size();
+ while (keyIndex < sz && keyAt(keyIndex) == key) {
+ key++;
+ keyIndex++;
+ }
+ if (key >= origKey + 10) {
+ Slog.w(TAG, "Value " + value + " supposed to be inserted at " + origKey
+ + " displaced to " + key);
+ }
+ }
+ super.put(key, value);
+ }
+
+ /**
* Finds the index of the first element whose timestamp is less than or equal to
* the given time.
*
diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java
index edb992bd..84f57a30 100644
--- a/android/app/usage/UsageEvents.java
+++ b/android/app/usage/UsageEvents.java
@@ -16,6 +16,7 @@
package android.app.usage;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.content.res.Configuration;
import android.os.Parcel;
import android.os.Parcelable;
@@ -80,6 +81,7 @@ public final class UsageEvents implements Parcelable {
* An event type denoting that a package was interacted with in some way by the system.
* @hide
*/
+ @SystemApi
public static final int SYSTEM_INTERACTION = 6;
/**
@@ -104,14 +106,64 @@ public final class UsageEvents implements Parcelable {
* An event type denoting that a notification was viewed by the user.
* @hide
*/
+ @SystemApi
public static final int NOTIFICATION_SEEN = 10;
/**
- * An event type denoting a change in App Standby Bucket.
- * @hide
+ * An event type denoting a change in App Standby Bucket. The new bucket can be
+ * retrieved by calling {@link #getStandbyBucket()}.
+ *
+ * @see UsageStatsManager#getAppStandbyBucket()
*/
public static final int STANDBY_BUCKET_CHANGED = 11;
+ /**
+ * An event type denoting that an app posted an interruptive notification. Visual and
+ * audible interruptions are included.
+ * @hide
+ */
+ @SystemApi
+ public static final int NOTIFICATION_INTERRUPTION = 12;
+
+ /**
+ * A Slice was pinned by the default launcher or the default assistant.
+ * @hide
+ */
+ @SystemApi
+ public static final int SLICE_PINNED_PRIV = 13;
+
+ /**
+ * A Slice was pinned by an app.
+ * @hide
+ */
+ @SystemApi
+ public static final int SLICE_PINNED = 14;
+
+ /**
+ * An event type denoting that the screen has gone in to an interactive state (turned
+ * on for full user interaction, not ambient display or other non-interactive state).
+ */
+ public static final int SCREEN_INTERACTIVE = 15;
+
+ /**
+ * An event type denoting that the screen has gone in to a non-interactive state
+ * (completely turned off or turned on only in a non-interactive state like ambient
+ * display).
+ */
+ public static final int SCREEN_NON_INTERACTIVE = 16;
+
+ /**
+ * An event type denoting that the screen's keyguard has been shown, whether or not
+ * the screen is off.
+ */
+ public static final int KEYGUARD_SHOWN = 17;
+
+ /**
+ * An event type denoting that the screen's keyguard has been hidden. This typically
+ * happens when the user unlocks their phone after turning it on.
+ */
+ public static final int KEYGUARD_HIDDEN = 18;
+
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
@@ -177,11 +229,20 @@ public final class UsageEvents implements Parcelable {
public String[] mContentAnnotations;
/**
- * The app standby bucket assigned.
+ * The app standby bucket assigned and reason. Bucket is the high order 16 bits, reason
+ * is the low order 16 bits.
* Only present for {@link #STANDBY_BUCKET_CHANGED} event types
* {@hide}
*/
- public int mBucket;
+ public int mBucketAndReason;
+
+ /**
+ * The id of the {@link android.app.NotificationChannel} to which an interruptive
+ * notification was posted.
+ * Only present for {@link #NOTIFICATION_INTERRUPTION} event types.
+ * {@hide}
+ */
+ public String mNotificationChannelId;
/** @hide */
@EventFlags
@@ -202,7 +263,8 @@ public final class UsageEvents implements Parcelable {
mContentType = orig.mContentType;
mContentAnnotations = orig.mContentAnnotations;
mFlags = orig.mFlags;
- mBucket = orig.mBucket;
+ mBucketAndReason = orig.mBucketAndReason;
+ mNotificationChannelId = orig.mNotificationChannelId;
}
/**
@@ -232,8 +294,11 @@ public final class UsageEvents implements Parcelable {
/**
* The event type.
*
- * See {@link #MOVE_TO_BACKGROUND}
- * See {@link #MOVE_TO_FOREGROUND}
+ * @see #MOVE_TO_BACKGROUND
+ * @see #MOVE_TO_FOREGROUND
+ * @see #CONFIGURATION_CHANGE
+ * @see #USER_INTERACTION
+ * @see #STANDBY_BUCKET_CHANGED
*/
public int getEventType() {
return mEventType;
@@ -257,6 +322,38 @@ public final class UsageEvents implements Parcelable {
return mShortcutId;
}
+ /**
+ * Returns the standby bucket of the app, if the event is of type
+ * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0.
+ * @return the standby bucket associated with the event.
+ *
+ */
+ public int getStandbyBucket() {
+ return (mBucketAndReason & 0xFFFF0000) >>> 16;
+ }
+
+ /**
+ * Returns the reason for the bucketing, if the event is of type
+ * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include
+ * the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there
+ * are sub-reasons for the main reason, such as REASON_SUB_USAGE_* when the main reason
+ * is REASON_MAIN_USAGE.
+ * @hide
+ */
+ public int getStandbyReason() {
+ return mBucketAndReason & 0x0000FFFF;
+ }
+
+ /**
+ * Returns the ID of the {@link android.app.NotificationChannel} for this event if the
+ * event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null;
+ * @hide
+ */
+ @SystemApi
+ public String getNotificationChannelId() {
+ return mNotificationChannelId;
+ }
+
/** @hide */
public Event getObfuscatedIfInstantApp() {
if ((mFlags & FLAG_IS_PACKAGE_INSTANT_APP) == 0) {
@@ -414,7 +511,10 @@ public final class UsageEvents implements Parcelable {
p.writeStringArray(event.mContentAnnotations);
break;
case Event.STANDBY_BUCKET_CHANGED:
- p.writeInt(event.mBucket);
+ p.writeInt(event.mBucketAndReason);
+ break;
+ case Event.NOTIFICATION_INTERRUPTION:
+ p.writeString(event.mNotificationChannelId);
break;
}
}
@@ -445,6 +545,7 @@ public final class UsageEvents implements Parcelable {
eventOut.mAction = null;
eventOut.mContentType = null;
eventOut.mContentAnnotations = null;
+ eventOut.mNotificationChannelId = null;
switch (eventOut.mEventType) {
case Event.CONFIGURATION_CHANGE:
@@ -460,7 +561,10 @@ public final class UsageEvents implements Parcelable {
eventOut.mContentAnnotations = p.createStringArray();
break;
case Event.STANDBY_BUCKET_CHANGED:
- eventOut.mBucket = p.readInt();
+ eventOut.mBucketAndReason = p.readInt();
+ break;
+ case Event.NOTIFICATION_INTERRUPTION:
+ eventOut.mNotificationChannelId = p.readString();
break;
}
}
diff --git a/android/app/usage/UsageStats.java b/android/app/usage/UsageStats.java
index 7eef85c4..2b14841f 100644
--- a/android/app/usage/UsageStats.java
+++ b/android/app/usage/UsageStats.java
@@ -16,6 +16,7 @@
package android.app.usage;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -61,6 +62,11 @@ public final class UsageStats implements Parcelable {
/**
* {@hide}
*/
+ public int mAppLaunchCount;
+
+ /**
+ * {@hide}
+ */
public int mLastEvent;
/**
@@ -81,6 +87,7 @@ public final class UsageStats implements Parcelable {
mLastTimeUsed = stats.mLastTimeUsed;
mTotalTimeInForeground = stats.mTotalTimeInForeground;
mLaunchCount = stats.mLaunchCount;
+ mAppLaunchCount = stats.mAppLaunchCount;
mLastEvent = stats.mLastEvent;
mChooserCounts = stats.mChooserCounts;
}
@@ -137,6 +144,16 @@ public final class UsageStats implements Parcelable {
}
/**
+ * Returns the number of times the app was launched as an activity from outside of the app.
+ * Excludes intra-app activity transitions.
+ * @hide
+ */
+ @SystemApi
+ public int getAppLaunchCount() {
+ return mAppLaunchCount;
+ }
+
+ /**
* Add the statistics from the right {@link UsageStats} to the left. The package name for
* both {@link UsageStats} objects must be the same.
* @param right The {@link UsageStats} object to merge into this one.
@@ -161,6 +178,7 @@ public final class UsageStats implements Parcelable {
mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
mTotalTimeInForeground += right.mTotalTimeInForeground;
mLaunchCount += right.mLaunchCount;
+ mAppLaunchCount += right.mAppLaunchCount;
if (mChooserCounts == null) {
mChooserCounts = right.mChooserCounts;
} else if (right.mChooserCounts != null) {
@@ -196,6 +214,7 @@ public final class UsageStats implements Parcelable {
dest.writeLong(mLastTimeUsed);
dest.writeLong(mTotalTimeInForeground);
dest.writeInt(mLaunchCount);
+ dest.writeInt(mAppLaunchCount);
dest.writeInt(mLastEvent);
Bundle allCounts = new Bundle();
if (mChooserCounts != null) {
@@ -224,6 +243,7 @@ public final class UsageStats implements Parcelable {
stats.mLastTimeUsed = in.readLong();
stats.mTotalTimeInForeground = in.readLong();
stats.mLaunchCount = in.readInt();
+ stats.mAppLaunchCount = in.readInt();
stats.mLastEvent = in.readInt();
Bundle allCounts = in.readBundle();
if (allCounts != null) {
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index edb6a74b..7fb97d3a 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -17,9 +17,11 @@
package android.app.usage;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
@@ -28,9 +30,11 @@ import android.util.ArrayMap;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* Provides access to device usage history and statistics. Usage data is aggregated into
@@ -51,10 +55,13 @@ import java.util.Map;
* </pre>
* A request for data in the middle of a time interval will include that interval.
* <p/>
- * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS.
- * However, declaring the permission implies intention to use the API and the user of the device
- * still needs to grant permission through the Settings application.
- * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}
+ * <b>NOTE:</b> Most methods on this API require the permission
+ * android.permission.PACKAGE_USAGE_STATS. However, declaring the permission implies intention to
+ * use the API and the user of the device still needs to grant permission through the Settings
+ * application.
+ * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}.
+ * Methods which only return the information for the calling package do not require this permission.
+ * E.g. {@link #getAppStandbyBucket()} and {@link #queryEventsForSelf(long, long)}.
*/
@SystemService(Context.USAGE_STATS_SERVICE)
public final class UsageStatsManager {
@@ -101,25 +108,35 @@ public final class UsageStatsManager {
public static final int STANDBY_BUCKET_EXEMPTED = 5;
/**
- * The app was used very recently, currently in use or likely to be used very soon.
+ * The app was used very recently, currently in use or likely to be used very soon. Standby
+ * bucket values that are &le; {@link #STANDBY_BUCKET_ACTIVE} will not be throttled by the
+ * system while they are in this bucket. Buckets &gt; {@link #STANDBY_BUCKET_ACTIVE} will most
+ * likely be restricted in some way. For instance, jobs and alarms may be deferred.
* @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.
+ * The app was used recently and/or likely to be used in the next few hours. Restrictions will
+ * apply to these apps, such as deferral of jobs and alarms.
* @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.
+ * Restrictions will apply to these apps, such as deferral of jobs and alarms. The delays may be
+ * greater than for apps in higher buckets (lower bucket value). Bucket values &gt;
+ * {@link #STANDBY_BUCKET_FREQUENT} may additionally have network access limited.
* @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.
+ * Apps in this bucket will have the most restrictions, including network restrictions, except
+ * during certain short periods (at a minimum, once a day) when they are allowed to execute
+ * jobs, access the network, etc.
* @see #getAppStandbyBucket()
*/
public static final int STANDBY_BUCKET_RARE = 40;
@@ -131,24 +148,47 @@ public final class UsageStatsManager {
@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 */
+ public static final int REASON_MAIN_MASK = 0xFF00;
+ /** @hide */
+ public static final int REASON_MAIN_DEFAULT = 0x0100;
+ /** @hide */
+ public static final int REASON_MAIN_TIMEOUT = 0x0200;
+ /** @hide */
+ public static final int REASON_MAIN_USAGE = 0x0300;
+ /** @hide */
+ public static final int REASON_MAIN_FORCED = 0x0400;
+ /** @hide */
+ public static final int REASON_MAIN_PREDICTED = 0x0500;
- /** {@hide} Reason for bucketing -- usage */
- public static final String REASON_USAGE = "usage";
+ /** @hide */
+ public static final int REASON_SUB_MASK = 0x00FF;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_SYSTEM_INTERACTION = 0x0001;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_NOTIFICATION_SEEN = 0x0002;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_USER_INTERACTION = 0x0003;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_MOVE_TO_FOREGROUND = 0x0004;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_MOVE_TO_BACKGROUND = 0x0005;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_SYSTEM_UPDATE = 0x0006;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_ACTIVE_TIMEOUT = 0x0007;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_SYNC_ADAPTER = 0x0008;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_SLICE_PINNED = 0x0009;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_SLICE_PINNED_PRIV = 0x000A;
+ /** @hide */
+ public static final int REASON_SUB_USAGE_EXEMPTED_SYNC_START = 0x000B;
- /** {@hide} Reason for bucketing -- forced by user / shell command */
- public static final String REASON_FORCED = "forced";
+ /** @hide */
+ public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001;
- /**
- * {@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 = {
@@ -162,6 +202,31 @@ public final class UsageStatsManager {
@Retention(RetentionPolicy.SOURCE)
public @interface StandbyBuckets {}
+ /**
+ * Observer id of the registered observer for the group of packages that reached the usage
+ * time limit. Included as an extra in the PendingIntent that was registered.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_OBSERVER_ID = "android.app.usage.extra.OBSERVER_ID";
+
+ /**
+ * Original time limit in milliseconds specified by the registered observer for the group of
+ * packages that reached the usage time limit. Included as an extra in the PendingIntent that
+ * was registered.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_TIME_LIMIT = "android.app.usage.extra.TIME_LIMIT";
+
+ /**
+ * Actual usage time in milliseconds for the group of packages that reached the specified time
+ * limit. Included as an extra in the PendingIntent that was registered.
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_TIME_USED = "android.app.usage.extra.TIME_USED";
+
private static final UsageEvents sEmptyResults = new UsageEvents();
private final Context mContext;
@@ -192,6 +257,8 @@ public final class UsageStatsManager {
* 2014 - com.example.charlie
* </pre>
*
+ * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+ *
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
@@ -221,6 +288,7 @@ public final class UsageStatsManager {
* Gets the hardware configurations the device was in for the given time range, aggregated by
* the specified interval. The results are ordered as in
* {@link #queryUsageStats(int, long, long)}.
+ * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
*
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
@@ -243,8 +311,49 @@ public final class UsageStatsManager {
}
/**
+ * Gets aggregated event stats for the given time range, aggregated by the specified interval.
+ * <p>The returned list will contain a {@link EventStats} object for each event type that
+ * is being aggregated and has data for an interval that is a subset of the time range given.
+ *
+ * <p>The current event types that will be aggregated here are:</p>
+ * <ul>
+ * <li>{@link UsageEvents.Event#SCREEN_INTERACTIVE}</li>
+ * <li>{@link UsageEvents.Event#SCREEN_NON_INTERACTIVE}</li>
+ * <li>{@link UsageEvents.Event#KEYGUARD_SHOWN}</li>
+ * <li>{@link UsageEvents.Event#KEYGUARD_HIDDEN}</li>
+ * </ul>
+ *
+ * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+ *
+ * @param intervalType The time interval by which the stats are aggregated.
+ * @param beginTime The inclusive beginning of the range of stats to include in the results.
+ * @param endTime The exclusive end of the range of stats to include in the results.
+ * @return A list of {@link EventStats}
+ *
+ * @see #INTERVAL_DAILY
+ * @see #INTERVAL_WEEKLY
+ * @see #INTERVAL_MONTHLY
+ * @see #INTERVAL_YEARLY
+ * @see #INTERVAL_BEST
+ */
+ public List<EventStats> queryEventStats(int intervalType, long beginTime, long endTime) {
+ try {
+ @SuppressWarnings("unchecked")
+ ParceledListSlice<EventStats> slice = mService.queryEventStats(intervalType, beginTime,
+ endTime, mContext.getOpPackageName());
+ if (slice != null) {
+ return slice.getList();
+ }
+ } catch (RemoteException e) {
+ // fallthrough and return the empty list.
+ }
+ return Collections.emptyList();
+ }
+
+ /**
* Query for events in the given time range. Events are only kept by the system for a few
* days.
+ * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
*
* @param beginTime The inclusive beginning of the range of events to include in the results.
* @param endTime The exclusive end of the range of events to include in the results.
@@ -264,9 +373,32 @@ public final class UsageStatsManager {
}
/**
+ * Like {@link #queryEvents(long, long)}, but only returns events for the calling package.
+ *
+ * @param beginTime The inclusive beginning of the range of events to include in the results.
+ * @param endTime The exclusive end of the range of events to include in the results.
+ * @return A {@link UsageEvents} object.
+ *
+ * @see #queryEvents(long, long)
+ */
+ public UsageEvents queryEventsForSelf(long beginTime, long endTime) {
+ try {
+ final UsageEvents events = mService.queryEventsForPackage(beginTime, endTime,
+ mContext.getOpPackageName());
+ if (events != null) {
+ return events;
+ }
+ } catch (RemoteException e) {
+ // fallthrough
+ }
+ return sEmptyResults;
+ }
+
+ /**
* A convenience method that queries for all stats in the given range (using the best interval
* for that range), merges the resulting data, and keys it by package name.
* See {@link #queryUsageStats(int, long, long)}.
+ * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
*
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
@@ -301,7 +433,7 @@ public final class UsageStatsManager {
*/
public boolean isAppInactive(String packageName) {
try {
- return mService.isAppInactive(packageName, UserHandle.myUserId());
+ return mService.isAppInactive(packageName, mContext.getUserId());
} catch (RemoteException e) {
// fall through and return default
}
@@ -313,7 +445,7 @@ public final class UsageStatsManager {
*/
public void setAppInactive(String packageName, boolean inactive) {
try {
- mService.setAppInactive(packageName, inactive, UserHandle.myUserId());
+ mService.setAppInactive(packageName, inactive, mContext.getUserId());
} catch (RemoteException e) {
// fall through
}
@@ -322,11 +454,19 @@ public final class UsageStatsManager {
/**
* 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.
+ * be restricted from running background tasks such as jobs and alarms.
* <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.
+ * <p>Apps in buckets &le; {@link #STANDBY_BUCKET_ACTIVE} have no standby restrictions imposed.
+ * Apps in buckets &gt; {@link #STANDBY_BUCKET_FREQUENT} may have network access restricted when
+ * running in the background.
+ * <p>The standby state of an app can change at any time either due to a user interaction or a
+ * system interaction or some algorithm determining that the app can be restricted for a period
+ * of time before the user has a need for it.
+ * <p>You can also query the recent history of standby bucket changes by calling
+ * {@link #queryEventsForSelf(long, long)} and searching for
+ * {@link UsageEvents.Event#STANDBY_BUCKET_CHANGED}.
*
* @return the current standby bucket of the calling app. One of STANDBY_BUCKET_* constants.
*/
@@ -388,8 +528,16 @@ public final class UsageStatsManager {
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
public Map<String, Integer> getAppStandbyBuckets() {
try {
- return (Map<String, Integer>) mService.getAppStandbyBuckets(
+ final ParceledListSlice<AppStandbyInfo> slice = mService.getAppStandbyBuckets(
mContext.getOpPackageName(), mContext.getUserId());
+ final List<AppStandbyInfo> bucketList = slice.getList();
+ final ArrayMap<String, Integer> bucketMap = new ArrayMap<>();
+ final int n = bucketList.size();
+ for (int i = 0; i < n; i++) {
+ final AppStandbyInfo bucketInfo = bucketList.get(i);
+ bucketMap.put(bucketInfo.mPackageName, bucketInfo.mStandbyBucket);
+ }
+ return bucketMap;
} catch (RemoteException e) {
}
return Collections.EMPTY_MAP;
@@ -404,13 +552,134 @@ public final class UsageStatsManager {
@SystemApi
@RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE)
public void setAppStandbyBuckets(Map<String, Integer> appBuckets) {
+ if (appBuckets == null) {
+ return;
+ }
+ final List<AppStandbyInfo> bucketInfoList = new ArrayList<>(appBuckets.size());
+ for (Map.Entry<String, Integer> bucketEntry : appBuckets.entrySet()) {
+ bucketInfoList.add(new AppStandbyInfo(bucketEntry.getKey(), bucketEntry.getValue()));
+ }
+ final ParceledListSlice<AppStandbyInfo> slice = new ParceledListSlice<>(bucketInfoList);
+ try {
+ mService.setAppStandbyBuckets(slice, mContext.getUserId());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ * Register an app usage limit observer that receives a callback on the provided intent when
+ * the sum of usages of apps in the packages array exceeds the {@code timeLimit} specified. The
+ * observer will automatically be unregistered when the time limit is reached and the intent
+ * is delivered. Registering an {@code observerId} that was already registered will override
+ * the previous one.
+ * @param observerId A unique id associated with the group of apps to be monitored. There can
+ * be multiple groups with common packages and different time limits.
+ * @param packages The list of packages to observe for foreground activity time. Cannot be null
+ * and must include at least one package.
+ * @param timeLimit The total time the set of apps can be in the foreground before the
+ * callbackIntent is delivered. Must be greater than 0.
+ * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+ * @param callbackIntent The PendingIntent that will be dispatched when the time limit is
+ * exceeded by the group of apps. The delivered Intent will also contain
+ * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
+ * {@link #EXTRA_TIME_USED}. Cannot be null.
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or
+ * is not the profile owner of this user.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+ public void registerAppUsageObserver(int observerId, @NonNull String[] packages, long timeLimit,
+ @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
try {
- mService.setAppStandbyBuckets(appBuckets, mContext.getUserId());
+ mService.registerAppUsageObserver(observerId, packages, timeUnit.toMillis(timeLimit),
+ callbackIntent, mContext.getOpPackageName());
} catch (RemoteException e) {
}
}
/**
+ * @hide
+ * Unregister the app usage observer specified by the {@code observerId}. This will only apply
+ * to any observer registered by this application. Unregistering an observer that was already
+ * unregistered or never registered will have no effect.
+ * @param observerId The id of the observer that was previously registered.
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or is
+ * not the profile owner of this user.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+ public void unregisterAppUsageObserver(int observerId) {
+ try {
+ mService.unregisterAppUsageObserver(observerId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** @hide */
+ public static String reasonToString(int standbyReason) {
+ StringBuilder sb = new StringBuilder();
+ switch (standbyReason & REASON_MAIN_MASK) {
+ case REASON_MAIN_DEFAULT:
+ sb.append("d");
+ break;
+ case REASON_MAIN_FORCED:
+ sb.append("f");
+ break;
+ case REASON_MAIN_PREDICTED:
+ sb.append("p");
+ switch (standbyReason & REASON_SUB_MASK) {
+ case REASON_SUB_PREDICTED_RESTORED:
+ sb.append("-r");
+ break;
+ }
+ break;
+ case REASON_MAIN_TIMEOUT:
+ sb.append("t");
+ break;
+ case REASON_MAIN_USAGE:
+ sb.append("u");
+ switch (standbyReason & REASON_SUB_MASK) {
+ case REASON_SUB_USAGE_SYSTEM_INTERACTION:
+ sb.append("-si");
+ break;
+ case REASON_SUB_USAGE_NOTIFICATION_SEEN:
+ sb.append("-ns");
+ break;
+ case REASON_SUB_USAGE_USER_INTERACTION:
+ sb.append("-ui");
+ break;
+ case REASON_SUB_USAGE_MOVE_TO_FOREGROUND:
+ sb.append("-mf");
+ break;
+ case REASON_SUB_USAGE_MOVE_TO_BACKGROUND:
+ sb.append("-mb");
+ break;
+ case REASON_SUB_USAGE_SYSTEM_UPDATE:
+ sb.append("-su");
+ break;
+ case REASON_SUB_USAGE_ACTIVE_TIMEOUT:
+ sb.append("-at");
+ break;
+ case REASON_SUB_USAGE_SYNC_ADAPTER:
+ sb.append("-sa");
+ break;
+ case REASON_SUB_USAGE_SLICE_PINNED:
+ sb.append("slp");
+ break;
+ case REASON_SUB_USAGE_SLICE_PINNED_PRIV:
+ sb.append("slpp");
+ break;
+ case REASON_SUB_USAGE_EXEMPTED_SYNC_START:
+ sb.append("es");
+ break;
+ }
+ break;
+ }
+ return sb.toString();
+ }
+
+ /**
* {@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
diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java
index bd978e3d..b8628a4d 100644
--- a/android/app/usage/UsageStatsManagerInternal.java
+++ b/android/app/usage/UsageStatsManagerInternal.java
@@ -59,6 +59,16 @@ public abstract class UsageStatsManagerInternal {
public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId);
/**
+ * Reports that an application has posted an interruptive notification.
+ *
+ * @param packageName The package name of the app that posted the notification
+ * @param channelId The ID of the NotificationChannel to which the notification was posted
+ * @param userId The user in which the notification was posted
+ */
+ public abstract void reportInterruptiveNotification(String packageName, String channelId,
+ @UserIdInt int userId);
+
+ /**
* Reports that an action equivalent to a ShortcutInfo is taken by the user.
*
* @param packageName The package name of the shortcut publisher
@@ -139,13 +149,21 @@ public abstract class UsageStatsManagerInternal {
/** Callback to inform listeners that the idle state has changed to a new bucket. */
public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
- boolean idle, int bucket);
+ boolean idle, int bucket, int reason);
/**
* Callback to inform listeners that the parole state has changed. This means apps are
* allowed to do work even if they're idle or in a low bucket.
*/
public abstract void onParoleStateChanged(boolean isParoleOn);
+
+ /**
+ * Optional callback to inform the listener that the app has transitioned into
+ * an active state due to user interaction.
+ */
+ public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
+ // No-op by default
+ }
}
/** Backup/Restore API */
@@ -212,4 +230,25 @@ public abstract class UsageStatsManagerInternal {
* indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}.
*/
public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId);
+
+ /**
+ * Report a few data points about an app's job state at the current time.
+ *
+ * @param packageName the app whose job state is being described
+ * @param userId which user the app is associated with
+ * @param numDeferredJobs the number of pending jobs that were deferred
+ * due to bucketing policy
+ * @param timeSinceLastJobRun number of milliseconds since the last time one of
+ * this app's jobs was executed
+ */
+ public abstract void reportAppJobState(String packageName, @UserIdInt int userId,
+ int numDeferredJobs, long timeSinceLastJobRun);
+
+ /**
+ * Report a sync that was scheduled by an active app is about to be executed.
+ *
+ * @param packageName name of the package that owns the sync adapter.
+ * @param userId which user the app is associated with
+ */
+ public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
}