summaryrefslogtreecommitdiff
path: root/android/app
diff options
context:
space:
mode:
authorJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
committerJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
commita192cc2a132cb0ee8588e2df755563ec7008c179 (patch)
tree380e4db22df19c819bd37df34bf06e7568916a50 /android/app
parent98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff)
downloadandroid-28-a192cc2a132cb0ee8588e2df755563ec7008c179.tar.gz
Update fullsdk to 4575844
/google/data/ro/projects/android/fetch_artifact \ --bid 4575844 \ --target sdk_phone_x86_64-sdk \ sdk-repo-linux-sources-4575844.zip Test: TreeHugger Change-Id: I81e0eb157b4ac3b38408d0ef86f9d6286471f87a
Diffstat (limited to 'android/app')
-rw-r--r--android/app/Activity.java109
-rw-r--r--android/app/ActivityManager.java66
-rw-r--r--android/app/ActivityManagerInternal.java25
-rw-r--r--android/app/ActivityManagerNative.java6
-rw-r--r--android/app/ActivityOptions.java61
-rw-r--r--android/app/ActivityThread.java68
-rw-r--r--android/app/ActivityView.java97
-rw-r--r--android/app/AppOpsManager.java248
-rw-r--r--android/app/ApplicationPackageManager.java68
-rw-r--r--android/app/ClientTransactionHandler.java8
-rw-r--r--android/app/ContextImpl.java16
-rw-r--r--android/app/Dialog.java35
-rw-r--r--android/app/Instrumentation.java11
-rw-r--r--android/app/KeyguardManager.java56
-rw-r--r--android/app/Notification.java579
-rw-r--r--android/app/NotificationChannel.java12
-rw-r--r--android/app/NotificationChannelGroup.java8
-rw-r--r--android/app/NotificationManager.java14
-rw-r--r--android/app/PendingIntent.java19
-rw-r--r--android/app/ProfilerInfo.java30
-rw-r--r--android/app/RemoteInput.java52
-rw-r--r--android/app/SharedPreferencesImpl.java170
-rw-r--r--android/app/StatsManager.java238
-rw-r--r--android/app/SystemServiceRegistry.java28
-rw-r--r--android/app/UiAutomation.java15
-rw-r--r--android/app/admin/ConnectEvent.java2
-rw-r--r--android/app/admin/DeviceAdminReceiver.java178
-rw-r--r--android/app/admin/DevicePolicyManager.java657
-rw-r--r--android/app/admin/DevicePolicyManagerInternal.java18
-rw-r--r--android/app/admin/DnsEvent.java2
-rw-r--r--android/app/assist/AssistStructure.java26
-rw-r--r--android/app/backup/BackupManager.java25
-rw-r--r--android/app/backup/BackupManagerMonitor.java6
-rw-r--r--android/app/backup/BackupTransport.java53
-rw-r--r--android/app/job/JobInfo.java27
-rw-r--r--android/app/job/JobParameters.java14
-rw-r--r--android/app/servertransaction/ActivityLifecycleItem.java38
-rw-r--r--android/app/servertransaction/ClientTransaction.java14
-rw-r--r--android/app/servertransaction/DestroyActivityItem.java2
-rw-r--r--android/app/servertransaction/PauseActivityItem.java2
-rw-r--r--android/app/servertransaction/ResumeActivityItem.java2
-rw-r--r--android/app/servertransaction/StopActivityItem.java2
-rw-r--r--android/app/servertransaction/TransactionExecutor.java22
-rw-r--r--android/app/slice/Slice.java115
-rw-r--r--android/app/slice/SliceItem.java12
-rw-r--r--android/app/slice/SliceManager.java240
-rw-r--r--android/app/slice/SliceProvider.java210
-rw-r--r--android/app/timezone/RulesState.java3
-rw-r--r--android/app/trust/TrustManager.java29
-rw-r--r--android/app/usage/NetworkStats.java52
-rw-r--r--android/app/usage/NetworkStatsManager.java114
-rw-r--r--android/app/usage/UsageEvents.java20
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java87
53 files changed, 3401 insertions, 610 deletions
diff --git a/android/app/Activity.java b/android/app/Activity.java
index aa099eb1..cd029c06 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
@@ -98,6 +100,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.RemoteAnimationDefinition;
import android.view.SearchEvent;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
@@ -857,6 +860,7 @@ public class Activity extends ContextThemeWrapper
private boolean mHasCurrentPermissionsRequest;
private boolean mAutoFillResetNeeded;
+ private boolean mAutoFillIgnoreFirstResumePause;
/** The last autofill id that was returned from {@link #getNextAutofillId()} */
private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
@@ -1253,10 +1257,7 @@ public class Activity extends ContextThemeWrapper
getApplication().dispatchActivityStarted(this);
if (mAutoFillResetNeeded) {
- AutofillManager afm = getAutofillManager();
- if (afm != null) {
- afm.onVisibleForAutofill();
- }
+ getAutofillManager().onVisibleForAutofill();
}
}
@@ -1320,6 +1321,20 @@ public class Activity extends ContextThemeWrapper
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
mActivityTransitionState.onResume(this, isTopOfTask());
+ if (mAutoFillResetNeeded) {
+ if (!mAutoFillIgnoreFirstResumePause) {
+ View focus = getCurrentFocus();
+ if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
+ // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest#
+ // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial
+ // window visibility after recreation is INVISIBLE in onResume() and next frame
+ // ViewRootImpl.performTraversals() changes window visibility to VISIBLE.
+ // So we cannot call View.notifyEnterOrExited() which will do nothing
+ // when View.isVisibleToUser() is false.
+ getAutofillManager().notifyViewEntered(focus);
+ }
+ }
+ }
mCalled = true;
}
@@ -1681,6 +1696,19 @@ public class Activity extends ContextThemeWrapper
protected void onPause() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
getApplication().dispatchActivityPaused(this);
+ if (mAutoFillResetNeeded) {
+ if (!mAutoFillIgnoreFirstResumePause) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill notifyViewExited " + this);
+ View focus = getCurrentFocus();
+ if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
+ getAutofillManager().notifyViewExited(focus);
+ }
+ } else {
+ // reset after first pause()
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill got first pause " + this);
+ mAutoFillIgnoreFirstResumePause = false;
+ }
+ }
mCalled = true;
}
@@ -1871,6 +1899,10 @@ public class Activity extends ContextThemeWrapper
mTranslucentCallback = null;
mCalled = true;
+ if (mAutoFillResetNeeded) {
+ getAutofillManager().onInvisibleForAutofill();
+ }
+
if (isFinishing()) {
if (mAutoFillResetNeeded) {
getAutofillManager().onActivityFinished();
@@ -2587,6 +2619,7 @@ public class Activity extends ContextThemeWrapper
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Activity#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -2594,6 +2627,30 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Finds a view that was identified by the {@code android:id} XML attribute that was processed
+ * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, or there is
+ * no matching view in the hierarchy.
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see Activity#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Activity");
+ }
+ return view;
+ }
+
+ /**
* Retrieve a reference to this activity's ActionBar.
*
* @return The Activity's ActionBar, or null if it does not have one.
@@ -4640,6 +4697,7 @@ 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
@@ -4648,7 +4706,7 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
- boolean ignoreTargetSecurity, int userId) {
+ IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
@@ -4656,7 +4714,7 @@ public class Activity extends ContextThemeWrapper
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
- intent, -1, options, ignoreTargetSecurity, userId);
+ intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, -1, ar.getResultCode(),
@@ -6266,7 +6324,7 @@ public class Activity extends ContextThemeWrapper
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
- final AutofillManager afm = getAutofillManager();
+ final AutofillManager afm = mAutofillManager;
if (afm != null) {
afm.dump(prefix, writer);
} else {
@@ -6616,7 +6674,6 @@ public class Activity extends ContextThemeWrapper
* to run as a {@link android.service.vr.VrListenerService} is not installed, or has
* not been enabled in user settings.
*
- * @see android.content.pm.PackageManager#FEATURE_VR_MODE
* @see android.content.pm.PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE
* @see android.service.vr.VrListenerService
* @see android.provider.Settings#ACTION_VR_LISTENER_SETTINGS
@@ -7120,13 +7177,23 @@ public class Activity extends ContextThemeWrapper
}
}
- final void performResume() {
+ final void performResume(boolean followedByPause) {
performRestart(true /* start */);
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
+ if (mAutoFillResetNeeded) {
+ // When Activity is destroyed in paused state, and relaunch activity, there will be
+ // extra onResume and onPause event, ignore the first onResume and onPause.
+ // see ActivityThread.handleRelaunchActivity()
+ mAutoFillIgnoreFirstResumePause = followedByPause;
+ if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) {
+ Slog.v(TAG, "autofill will ignore first pause when relaunching " + this);
+ }
+ }
+
mCalled = false;
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this);
@@ -7311,7 +7378,7 @@ public class Activity extends ContextThemeWrapper
}
} else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
- getAutofillManager().onAuthenticationResult(requestCode, resultData);
+ getAutofillManager().onAuthenticationResult(requestCode, resultData, getCurrentFocus());
} else {
Fragment frag = mFragments.findFragmentByWho(who);
if (frag != null) {
@@ -7585,6 +7652,12 @@ public class Activity extends ContextThemeWrapper
return !mStopped;
}
+ /** @hide */
+ @Override
+ public boolean isDisablingEnterExitEventForAutofill() {
+ return mAutoFillIgnoreFirstResumePause || !mResumed;
+ }
+
/**
* If set to true, this indicates to the system that it should never take a
* screenshot of the activity to be used as a representation while it is not in a started state.
@@ -7659,6 +7732,22 @@ public class Activity extends ContextThemeWrapper
}
}
+ /**
+ * Registers remote animations per transition type for this activity.
+ *
+ * @param definition The remote animation definition that defines which transition whould run
+ * which remote animation.
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+ try {
+ ActivityManager.getService().registerRemoteAnimations(mToken, definition);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call registerRemoteAnimations", e);
+ }
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 1adae7a8..80350584 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -60,6 +60,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.WorkSource;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -180,7 +181,8 @@ public class ActivityManager {
BUGREPORT_OPTION_INTERACTIVE,
BUGREPORT_OPTION_REMOTE,
BUGREPORT_OPTION_WEAR,
- BUGREPORT_OPTION_TELEPHONY
+ BUGREPORT_OPTION_TELEPHONY,
+ BUGREPORT_OPTION_WIFI
})
public @interface BugreportMode {}
/**
@@ -215,6 +217,12 @@ public class ActivityManager {
public static final int BUGREPORT_OPTION_TELEPHONY = 4;
/**
+ * Takes a lightweight bugreport that only includes a few sections related to Wifi.
+ * @hide
+ */
+ public static final int BUGREPORT_OPTION_WIFI = 5;
+
+ /**
* <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
* <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
* uninstalled in lieu of the declaring one. The package named here must be
@@ -442,6 +450,31 @@ 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;
@@ -484,11 +517,11 @@ public class ActivityManager {
* all activities that are visible to the user. */
public static final int PROCESS_STATE_TOP = 2;
- /** @hide Process is hosting a foreground service due to a system binding. */
- public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;
-
/** @hide Process is hosting a foreground service. */
- public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+
+ /** @hide Process is hosting a foreground service due to a system binding. */
+ public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 4;
/** @hide Process is important to the user, and something they are aware of. */
public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
@@ -3085,11 +3118,11 @@ public class ActivityManager {
} else if (importance >= IMPORTANCE_VISIBLE) {
return PROCESS_STATE_IMPORTANT_FOREGROUND;
} else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) {
- return PROCESS_STATE_FOREGROUND_SERVICE;
+ return PROCESS_STATE_IMPORTANT_FOREGROUND;
} else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) {
return PROCESS_STATE_FOREGROUND_SERVICE;
} else {
- return PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ return PROCESS_STATE_TOP;
}
}
@@ -3911,10 +3944,10 @@ public class ActivityManager {
/**
* @hide
*/
- public static void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
- String tag) {
+ public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String sourcePkg, String tag) {
try {
- getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+ getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource,
sourceUid, sourcePkg, tag);
} catch (RemoteException ex) {
}
@@ -3923,19 +3956,24 @@ public class ActivityManager {
/**
* @hide
*/
- public static void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
+ public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
try {
- getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+ getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
} catch (RemoteException ex) {
}
}
+
/**
* @hide
*/
- public static void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
+ public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid,
+ String tag) {
try {
- getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
+ getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource,
+ sourceUid, tag);
} catch (RemoteException ex) {
}
}
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
index 60a5a110..da9f7285 100644
--- a/android/app/ActivityManagerInternal.java
+++ b/android/app/ActivityManagerInternal.java
@@ -319,4 +319,29 @@ public abstract class ActivityManagerInternal {
}
public abstract void registerScreenObserver(ScreenObserver observer);
+
+ /**
+ * Returns if more users can be started without stopping currently running users.
+ */
+ public abstract boolean canStartMoreUsers();
+
+ /**
+ * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}.
+ */
+ public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage);
+
+ /**
+ * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}.
+ */
+ public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage);
+
+ /**
+ * Returns maximum number of users that can run simultaneously.
+ */
+ public abstract int getMaxRunningUsers();
+
+ /**
+ * Returns is the caller has the same uid as the Recents component
+ */
+ public abstract boolean isCallerRecents(int callingUid);
}
diff --git a/android/app/ActivityManagerNative.java b/android/app/ActivityManagerNative.java
index c09403c2..4c558f37 100644
--- a/android/app/ActivityManagerNative.java
+++ b/android/app/ActivityManagerNative.java
@@ -75,20 +75,20 @@ public abstract class ActivityManagerNative {
*/
static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
String tag) {
- ActivityManager.noteWakeupAlarm(ps, sourceUid, sourcePkg, tag);
+ ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag);
}
/**
* @deprecated use ActivityManager.noteAlarmStart instead.
*/
static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
- ActivityManager.noteAlarmStart(ps, sourceUid, tag);
+ ActivityManager.noteAlarmStart(ps, null, sourceUid, tag);
}
/**
* @deprecated use ActivityManager.noteAlarmFinish instead.
*/
static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
- ActivityManager.noteAlarmFinish(ps, sourceUid, tag);
+ ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag);
}
}
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index e61c5b7c..fee58274 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -16,12 +16,14 @@
package android.app;
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
@@ -44,6 +46,7 @@ import android.util.Pair;
import android.util.Slog;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.RemoteAnimationAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -204,6 +207,12 @@ public class ActivityOptions {
"android.activity.taskOverlayCanResume";
/**
+ * See {@link #setAvoidMoveToFront()}.
+ * @hide
+ */
+ private static final String KEY_AVOID_MOVE_TO_FRONT = "android.activity.avoidMoveToFront";
+
+ /**
* Where the split-screen-primary stack should be positioned.
* @hide
*/
@@ -241,6 +250,8 @@ public class ActivityOptions {
private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE
= "android:instantapps.installerbundle";
private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture";
+ private static final String KEY_REMOTE_ANIMATION_ADAPTER
+ = "android:activity.remoteAnimationAdapter";
/** @hide */
public static final int ANIM_NONE = 0;
@@ -268,6 +279,8 @@ public class ActivityOptions {
public static final int ANIM_CLIP_REVEAL = 11;
/** @hide */
public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
+ /** @hide */
+ public static final int ANIM_REMOTE_ANIMATION = 13;
private String mPackageName;
private Rect mLaunchBounds;
@@ -300,10 +313,12 @@ public class ActivityOptions {
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
+ private boolean mAvoidMoveToFront;
private AppTransitionAnimationSpec mAnimSpecs[];
private int mRotationAnimationHint = -1;
private Bundle mAppVerificationBundle;
private IAppTransitionAnimationSpecsFuture mSpecsFuture;
+ private RemoteAnimationAdapter mRemoteAnimationAdapter;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -826,6 +841,20 @@ public class ActivityOptions {
return opts;
}
+ /**
+ * Create an {@link ActivityOptions} instance that lets the application control the entire
+ * animation using a {@link RemoteAnimationAdapter}.
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public static ActivityOptions makeRemoteAnimation(
+ RemoteAnimationAdapter remoteAnimationAdapter) {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
+ opts.mAnimationType = ANIM_REMOTE_ANIMATION;
+ return opts;
+ }
+
/** @hide */
public boolean getLaunchTaskBehind() {
return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
@@ -901,6 +930,7 @@ public class ActivityOptions {
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
+ mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false);
mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
@@ -922,6 +952,7 @@ public class ActivityOptions {
mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder(
KEY_SPECS_FUTURE));
}
+ mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER);
}
/**
@@ -1070,6 +1101,11 @@ public class ActivityOptions {
}
/** @hide */
+ public RemoteAnimationAdapter getRemoteAnimationAdapter() {
+ return mRemoteAnimationAdapter;
+ }
+
+ /** @hide */
public static ActivityOptions fromBundle(Bundle bOptions) {
return bOptions != null ? new ActivityOptions(bOptions) : null;
}
@@ -1211,6 +1247,25 @@ public class ActivityOptions {
return mTaskOverlayCanResume;
}
+ /**
+ * Sets whether the activity launched should not cause the activity stack it is contained in to
+ * be moved to the front as a part of launching.
+ *
+ * @hide
+ */
+ public void setAvoidMoveToFront() {
+ mAvoidMoveToFront = true;
+ }
+
+ /**
+ * @return whether the activity launch should prevent moving the associated activity stack to
+ * the front.
+ * @hide
+ */
+ public boolean getAvoidMoveToFront() {
+ return mAvoidMoveToFront;
+ }
+
/** @hide */
public int getSplitScreenCreateMode() {
return mSplitScreenCreateMode;
@@ -1309,6 +1364,7 @@ public class ActivityOptions {
mAnimSpecs = otherOptions.mAnimSpecs;
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
+ mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
}
/**
@@ -1387,6 +1443,7 @@ public class ActivityOptions {
b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
+ b.putBoolean(KEY_AVOID_MOVE_TO_FRONT, mAvoidMoveToFront);
b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
mDisallowEnterPictureInPictureWhileLaunching);
@@ -1403,7 +1460,9 @@ public class ActivityOptions {
if (mAppVerificationBundle != null) {
b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle);
}
-
+ if (mRemoteAnimationAdapter != null) {
+ b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter);
+ }
return b;
}
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index aaa6bf03..934b0f3c 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -166,6 +166,7 @@ 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.List;
@@ -220,6 +221,9 @@ public final class ActivityThread extends ClientTransactionHandler {
// Whether to invoke an activity callback after delivering new configuration.
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;
+
/**
* Denotes an invalid sequence number corresponding to a process state change.
*/
@@ -252,6 +256,8 @@ 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<>();
+
// List of new activities (via ActivityRecord.nextIdle) that should
// be reported when next we idle.
ActivityClientRecord mNewActivities = null;
@@ -1752,9 +1758,11 @@ public final class ActivityThread extends ClientTransactionHandler {
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
(IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
break;
- case ATTACH_AGENT:
- handleAttachAgent((String) msg.obj);
+ case ATTACH_AGENT: {
+ Application app = getApplication();
+ handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null);
break;
+ }
case APPLICATION_INFO_CHANGED:
mUpdatingSystemConfig = true;
try {
@@ -1770,7 +1778,12 @@ public final class ActivityThread extends ClientTransactionHandler {
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
- transaction.recycle();
+ if (isSystem()) {
+ // Client transactions inside system process are recycled on the client side
+ // instead of ClientLifecycleManager to avoid being cleared before this
+ // message is handled.
+ transaction.recycle();
+ }
break;
}
Object obj = msg.obj;
@@ -2161,6 +2174,18 @@ public final class ActivityThread extends ClientTransactionHandler {
pw.println(String.format(format, objs));
}
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "mActivities:");
+
+ for (ArrayMap.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
+ pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:"
+ + entry.getValue().toString() + "]");
+ }
+
+ pw.println(prefix + "mRecentTokens:" + mRecentTokens);
+ }
+
public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
int pid, String processName,
@@ -2845,6 +2870,11 @@ 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;
@@ -3066,7 +3096,7 @@ public final class ActivityThread extends ClientTransactionHandler {
checkAndBlockForNetworkAccess();
deliverNewIntents(r, intents);
if (resumed) {
- r.activity.performResume();
+ r.activity.performResume(false);
r.activity.mTemporaryPause = false;
}
@@ -3241,11 +3271,23 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- static final void handleAttachAgent(String agent) {
+ private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) {
try {
- VMDebug.attachAgent(agent);
+ VMDebug.attachAgent(agent, classLoader);
+ return true;
} catch (IOException e) {
- Slog.e(TAG, "Attaching agent failed: " + agent);
+ Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent);
+ return false;
+ }
+ }
+
+ static void handleAttachAgent(String agent, LoadedApk loadedApk) {
+ ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null;
+ if (attemptAttachAgent(agent, classLoader)) {
+ return;
+ }
+ if (classLoader != null) {
+ attemptAttachAgent(agent, null);
}
}
@@ -3676,7 +3718,7 @@ public final class ActivityThread extends ClientTransactionHandler {
deliverResults(r, r.pendingResults);
r.pendingResults = null;
}
- r.activity.performResume();
+ r.activity.performResume(r.startsNotResumed);
synchronized (mResourcesManager) {
// If there is a pending local relaunch that was requested when the activity was
@@ -4395,7 +4437,7 @@ public final class ActivityThread extends ClientTransactionHandler {
checkAndBlockForNetworkAccess();
deliverResults(r, results);
if (resumed) {
- r.activity.performResume();
+ r.activity.performResume(false);
r.activity.mTemporaryPause = false;
}
}
@@ -5537,12 +5579,16 @@ public final class ActivityThread extends ClientTransactionHandler {
mCompatConfiguration = new Configuration(data.config);
mProfiler = new Profiler();
+ String agent = null;
if (data.initProfilerInfo != null) {
mProfiler.profileFile = data.initProfilerInfo.profileFile;
mProfiler.profileFd = data.initProfilerInfo.profileFd;
mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
+ if (data.initProfilerInfo.attachAgentDuringBind) {
+ agent = data.initProfilerInfo.agent;
+ }
}
// send up app name; do this *before* waiting for debugger
@@ -5592,6 +5638,10 @@ public final class ActivityThread extends ClientTransactionHandler {
data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo);
+ if (agent != null) {
+ handleAttachAgent(agent, data.loadedApk);
+ }
+
/**
* Switch this process to density compatibility mode if needed.
*/
diff --git a/android/app/ActivityView.java b/android/app/ActivityView.java
index 9f1e9839..5d0143a5 100644
--- a/android/app/ActivityView.java
+++ b/android/app/ActivityView.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.NonNull;
+import android.app.ActivityManager.StackInfo;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
@@ -34,9 +35,12 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import dalvik.system.CloseGuard;
+import java.util.List;
+
/**
* Activity container that allows launching activities into itself and does input forwarding.
* <p>Creation of this view is only allowed to callers who have
@@ -57,7 +61,12 @@ public class ActivityView extends ViewGroup {
private final SurfaceCallback mSurfaceCallback;
private StateCallback mActivityViewCallback;
+ private IActivityManager mActivityManager;
private IInputForwarder mInputForwarder;
+ // Temp container to store view coordinates on screen.
+ private final int[] mLocationOnScreen = new int[2];
+
+ private TaskStackListener mTaskStackListener;
private final CloseGuard mGuard = CloseGuard.get();
private boolean mOpened; // Protected by mGuard.
@@ -73,6 +82,7 @@ public class ActivityView extends ViewGroup {
public ActivityView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mActivityManager = ActivityManager.getService();
mSurfaceView = new SurfaceView(context);
mSurfaceCallback = new SurfaceCallback();
mSurfaceView.getHolder().addCallback(mSurfaceCallback);
@@ -198,11 +208,30 @@ public class ActivityView extends ViewGroup {
performRelease();
}
+ /**
+ * Triggers an update of {@link ActivityView}'s location on screen to properly set touch exclude
+ * regions and avoid focus switches by touches on this view.
+ */
+ public void onLocationChanged() {
+ updateLocation();
+ }
+
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
}
+ /** Send current location and size to the WM to set tap exclude region for this view. */
+ private void updateLocation() {
+ try {
+ getLocationOnScreen(mLocationOnScreen);
+ WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+ mLocationOnScreen[0], mLocationOnScreen[1], getWidth(), getHeight());
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
return injectInputEvent(event) || super.onTouchEvent(event);
@@ -241,6 +270,7 @@ public class ActivityView extends ViewGroup {
} else {
mVirtualDisplay.setSurface(surfaceHolder.getSurface());
}
+ updateLocation();
}
@Override
@@ -248,6 +278,7 @@ public class ActivityView extends ViewGroup {
if (mVirtualDisplay != null) {
mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
}
+ updateLocation();
}
@Override
@@ -257,6 +288,7 @@ public class ActivityView extends ViewGroup {
if (mVirtualDisplay != null) {
mVirtualDisplay.setSurface(null);
}
+ cleanTapExcludeRegion();
}
}
@@ -278,6 +310,12 @@ public class ActivityView extends ViewGroup {
mInputForwarder = InputManager.getInstance().createInputForwarder(
mVirtualDisplay.getDisplay().getDisplayId());
+ mTaskStackListener = new TaskBackgroundChangeListener();
+ try {
+ mActivityManager.registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register task stack listener", e);
+ }
}
private void performRelease() {
@@ -290,6 +328,16 @@ public class ActivityView extends ViewGroup {
if (mInputForwarder != null) {
mInputForwarder = null;
}
+ cleanTapExcludeRegion();
+
+ if (mTaskStackListener != null) {
+ try {
+ mActivityManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister task stack listener", e);
+ }
+ mTaskStackListener = null;
+ }
final boolean displayReleased;
if (mVirtualDisplay != null) {
@@ -313,6 +361,17 @@ public class ActivityView extends ViewGroup {
mOpened = false;
}
+ /** Report to server that tap exclude region on hosting display should be cleared. */
+ private void cleanTapExcludeRegion() {
+ // Update tap exclude region with an empty rect to clean the state on server.
+ try {
+ WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+ 0 /* left */, 0 /* top */, 0 /* width */, 0 /* height */);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
/** Get density of the hosting display. */
private int getBaseDisplayDensity() {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
@@ -332,4 +391,42 @@ public class ActivityView extends ViewGroup {
super.finalize();
}
}
+
+ /**
+ * A task change listener that detects background color change of the topmost stack on our
+ * virtual display and updates the background of the surface view. This background will be shown
+ * when surface view is resized, but the app hasn't drawn its content in new size yet.
+ */
+ private class TaskBackgroundChangeListener extends TaskStackListener {
+
+ @Override
+ public void onTaskDescriptionChanged(int taskId, ActivityManager.TaskDescription td)
+ throws RemoteException {
+ if (mVirtualDisplay == null) {
+ return;
+ }
+
+ // Find the topmost task on our virtual display - it will define the background
+ // color of the surface view during resizing.
+ final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+ final List<StackInfo> stackInfoList = mActivityManager.getAllStackInfos();
+
+ // Iterate through stacks from top to bottom.
+ final int stackCount = stackInfoList.size();
+ for (int i = 0; i < stackCount; i++) {
+ final StackInfo stackInfo = stackInfoList.get(i);
+ // Only look for stacks on our virtual display.
+ if (stackInfo.displayId != displayId) {
+ continue;
+ }
+ // Found the topmost stack on target display. Now check if the topmost task's
+ // description changed.
+ if (taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+ mSurfaceView.setResizeBackgroundColor(td.getBackgroundColor());
+ }
+ break;
+ }
+ }
+ }
+
}
diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java
index ea22d332..e923fb21 100644
--- a/android/app/AppOpsManager.java
+++ b/android/app/AppOpsManager.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.media.AudioAttributes.AttributeUsage;
@@ -37,6 +38,7 @@ import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -106,6 +108,7 @@ public class AppOpsManager {
// when adding one of these:
// - increment _NUM_OP
+ // - define an OPSTR_* constant (marked as @SystemApi)
// - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
// - add descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -260,8 +263,10 @@ public class AppOpsManager {
public static final int OP_REQUEST_DELETE_PACKAGES = 72;
/** @hide Bind an accessibility service. */
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 */
- public static final int _NUM_OP = 74;
+ public static final int _NUM_OP = 75;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -278,7 +283,7 @@ public class AppOpsManager {
public static final String OPSTR_GET_USAGE_STATS
= "android:get_usage_stats";
/** Activate a VPN connection without user intervention. @hide */
- @SystemApi
+ @SystemApi @TestApi
public static final String OPSTR_ACTIVATE_VPN
= "android:activate_vpn";
/** Allows an application to read the user's contacts data. */
@@ -360,6 +365,7 @@ public class AppOpsManager {
public static final String OPSTR_WRITE_SETTINGS
= "android:write_settings";
/** @hide Get device accounts. */
+ @SystemApi @TestApi
public static final String OPSTR_GET_ACCOUNTS
= "android:get_accounts";
public static final String OPSTR_READ_PHONE_NUMBERS
@@ -368,11 +374,133 @@ public class AppOpsManager {
public static final String OPSTR_PICTURE_IN_PICTURE
= "android:picture_in_picture";
/** @hide */
+ @SystemApi @TestApi
public static final String OPSTR_INSTANT_APP_START_FOREGROUND
= "android:instant_app_start_foreground";
/** Answer incoming phone calls */
public static final String OPSTR_ANSWER_PHONE_CALLS
= "android:answer_phone_calls";
+ /**
+ * Accept call handover
+ * @hide
+ */
+ @SystemApi @TestApi
+ public static final String OPSTR_ACCEPT_HANDOVER
+ = "android:accept_handover";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_GPS = "android:gps";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_VIBRATE = "android:vibrate";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_WIFI_SCAN = "android:wifi_scan";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_WRITE_SMS = "android:write_sms";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST =
+ "android:receive_emergency_broadcast";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_TAKE_MEDIA_BUTTONS = "android:take_media_buttons";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_TAKE_AUDIO_FOCUS = "android:take_audio_focus";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_MASTER_VOLUME = "android:audio_master_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_VOICE_VOLUME = "android:audio_voice_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_RING_VOLUME = "android:audio_ring_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_MEDIA_VOLUME = "android:audio_media_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_ALARM_VOLUME = "android:audio_alarm_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_NOTIFICATION_VOLUME =
+ "android:audio_notification_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_BLUETOOTH_VOLUME = "android:audio_bluetooth_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_WAKE_LOCK = "android:wake_lock";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_TOAST_WINDOW = "android:toast_window";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_ASSIST_STRUCTURE = "android:assist_structure";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_TURN_SCREEN_ON = "android:turn_screen_on";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_RUN_IN_BACKGROUND = "android:run_in_background";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_AUDIO_ACCESSIBILITY_VOLUME =
+ "android:audio_accessibility_volume";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
+ /** @hide */
+ @SystemApi @TestApi
+ 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";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_REQUEST_DELETE_PACKAGES = "request_delete_packages";
+ /** @hide */
+ @SystemApi @TestApi
+ public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "bind_accessibility_service";
// Warning: If an permission is added here it also has to be added to
// com.android.packageinstaller.permission.utils.EventLogger
@@ -408,6 +536,7 @@ public class AppOpsManager {
OP_USE_SIP,
OP_PROCESS_OUTGOING_CALLS,
OP_ANSWER_PHONE_CALLS,
+ OP_ACCEPT_HANDOVER,
// Microphone
OP_RECORD_AUDIO,
// Camera
@@ -506,64 +635,64 @@ public class AppOpsManager {
OP_CHANGE_WIFI_STATE,
OP_REQUEST_DELETE_PACKAGES,
OP_BIND_ACCESSIBILITY_SERVICE,
+ OP_ACCEPT_HANDOVER,
};
/**
* This maps each operation to the public string constant for it.
- * If it doesn't have a public string constant, it maps to null.
*/
- private static String[] sOpToString = new String[] {
+ private static String[] sOpToString = new String[]{
OPSTR_COARSE_LOCATION,
OPSTR_FINE_LOCATION,
- null,
- null,
+ OPSTR_GPS,
+ OPSTR_VIBRATE,
OPSTR_READ_CONTACTS,
OPSTR_WRITE_CONTACTS,
OPSTR_READ_CALL_LOG,
OPSTR_WRITE_CALL_LOG,
OPSTR_READ_CALENDAR,
OPSTR_WRITE_CALENDAR,
- null,
- null,
- null,
+ OPSTR_WIFI_SCAN,
+ OPSTR_POST_NOTIFICATION,
+ OPSTR_NEIGHBORING_CELLS,
OPSTR_CALL_PHONE,
OPSTR_READ_SMS,
- null,
+ OPSTR_WRITE_SMS,
OPSTR_RECEIVE_SMS,
- null,
+ OPSTR_RECEIVE_EMERGENCY_BROADCAST,
OPSTR_RECEIVE_MMS,
OPSTR_RECEIVE_WAP_PUSH,
OPSTR_SEND_SMS,
- null,
- null,
+ OPSTR_READ_ICC_SMS,
+ OPSTR_WRITE_ICC_SMS,
OPSTR_WRITE_SETTINGS,
OPSTR_SYSTEM_ALERT_WINDOW,
- null,
+ OPSTR_ACCESS_NOTIFICATIONS,
OPSTR_CAMERA,
OPSTR_RECORD_AUDIO,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
+ OPSTR_PLAY_AUDIO,
+ OPSTR_READ_CLIPBOARD,
+ OPSTR_WRITE_CLIPBOARD,
+ OPSTR_TAKE_MEDIA_BUTTONS,
+ OPSTR_TAKE_AUDIO_FOCUS,
+ OPSTR_AUDIO_MASTER_VOLUME,
+ OPSTR_AUDIO_VOICE_VOLUME,
+ OPSTR_AUDIO_RING_VOLUME,
+ OPSTR_AUDIO_MEDIA_VOLUME,
+ OPSTR_AUDIO_ALARM_VOLUME,
+ OPSTR_AUDIO_NOTIFICATION_VOLUME,
+ OPSTR_AUDIO_BLUETOOTH_VOLUME,
+ OPSTR_WAKE_LOCK,
OPSTR_MONITOR_LOCATION,
OPSTR_MONITOR_HIGH_POWER_LOCATION,
OPSTR_GET_USAGE_STATS,
- null,
- null,
- null,
+ OPSTR_MUTE_MICROPHONE,
+ OPSTR_TOAST_WINDOW,
+ OPSTR_PROJECT_MEDIA,
OPSTR_ACTIVATE_VPN,
- null,
- null,
- null,
+ OPSTR_WRITE_WALLPAPER,
+ OPSTR_ASSIST_STRUCTURE,
+ OPSTR_ASSIST_SCREENSHOT,
OPSTR_READ_PHONE_STATE,
OPSTR_ADD_VOICEMAIL,
OPSTR_USE_SIP,
@@ -574,19 +703,20 @@ public class AppOpsManager {
OPSTR_MOCK_LOCATION,
OPSTR_READ_EXTERNAL_STORAGE,
OPSTR_WRITE_EXTERNAL_STORAGE,
- null,
+ OPSTR_TURN_SCREEN_ON,
OPSTR_GET_ACCOUNTS,
- null,
- null, // OP_AUDIO_ACCESSIBILITY_VOLUME
+ OPSTR_RUN_IN_BACKGROUND,
+ OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
OPSTR_READ_PHONE_NUMBERS,
- null, // OP_REQUEST_INSTALL_PACKAGES
+ OPSTR_REQUEST_INSTALL_PACKAGES,
OPSTR_PICTURE_IN_PICTURE,
OPSTR_INSTANT_APP_START_FOREGROUND,
OPSTR_ANSWER_PHONE_CALLS,
- null, // OP_RUN_ANY_IN_BACKGROUND
- null, // OP_CHANGE_WIFI_STATE
- null, // OP_REQUEST_DELETE_PACKAGES
- null, // OP_BIND_ACCESSIBILITY_SERVICE
+ OPSTR_RUN_ANY_IN_BACKGROUND,
+ OPSTR_CHANGE_WIFI_STATE,
+ OPSTR_REQUEST_DELETE_PACKAGES,
+ OPSTR_BIND_ACCESSIBILITY_SERVICE,
+ OPSTR_ACCEPT_HANDOVER,
};
/**
@@ -668,6 +798,7 @@ public class AppOpsManager {
"CHANGE_WIFI_STATE",
"REQUEST_DELETE_PACKAGES",
"BIND_ACCESSIBILITY_SERVICE",
+ "ACCEPT_HANDOVER",
};
/**
@@ -749,6 +880,7 @@ public class AppOpsManager {
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.REQUEST_DELETE_PACKAGES,
Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
+ Manifest.permission.ACCEPT_HANDOVER,
};
/**
@@ -831,6 +963,7 @@ public class AppOpsManager {
null, // OP_CHANGE_WIFI_STATE
null, // REQUEST_DELETE_PACKAGES
null, // OP_BIND_ACCESSIBILITY_SERVICE
+ null, // ACCEPT_HANDOVER
};
/**
@@ -912,6 +1045,7 @@ public class AppOpsManager {
false, // OP_CHANGE_WIFI_STATE
false, // OP_REQUEST_DELETE_PACKAGES
false, // OP_BIND_ACCESSIBILITY_SERVICE
+ false, // ACCEPT_HANDOVER
};
/**
@@ -992,6 +1126,7 @@ public class AppOpsManager {
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
};
/**
@@ -1076,6 +1211,7 @@ public class AppOpsManager {
false, // OP_CHANGE_WIFI_STATE
false, // OP_REQUEST_DELETE_PACKAGES
false, // OP_BIND_ACCESSIBILITY_SERVICE
+ false, // ACCEPT_HANDOVER
};
/**
@@ -1207,6 +1343,25 @@ public class AppOpsManager {
}
/**
+ * Retrieve the human readable mode.
+ * @hide
+ */
+ public static String modeToString(int mode) {
+ switch (mode) {
+ case MODE_ALLOWED:
+ return "allow";
+ case MODE_IGNORED:
+ return "ignore";
+ case MODE_ERRORED:
+ return "deny";
+ case MODE_DEFAULT:
+ return "default";
+ default:
+ return "mode=" + mode;
+ }
+ }
+
+ /**
* Retrieve whether the op allows itself to be reset.
* @hide
*/
@@ -1482,6 +1637,7 @@ public class AppOpsManager {
}
/** @hide */
+ @TestApi
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
@@ -1997,4 +2153,14 @@ public class AppOpsManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns all supported operation names.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static String[] getOpStrs() {
+ return Arrays.copyOf(sOpToString, sOpToString.length);
+ }
}
diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java
index 8641a21a..cc68c051 100644
--- a/android/app/ApplicationPackageManager.java
+++ b/android/app/ApplicationPackageManager.java
@@ -64,7 +64,6 @@ import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -223,9 +222,18 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public Intent getLeanbackLaunchIntentForPackage(String packageName) {
- // Try to find a main leanback_launcher activity.
+ return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_LEANBACK_LAUNCHER);
+ }
+
+ @Override
+ public Intent getCarLaunchIntentForPackage(String packageName) {
+ return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_CAR_LAUNCHER);
+ }
+
+ private Intent getLaunchIntentForPackageAndCategory(String packageName, String category) {
+ // Try to find a main launcher activity for the given categories.
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
- intentToResolve.addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER);
+ intentToResolve.addCategory(category);
intentToResolve.setPackage(packageName);
List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
@@ -691,6 +699,26 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ try {
+ return mPM.hasSigningCertificate(packageName, certificate, type);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean hasSigningCertificate(
+ int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ try {
+ return mPM.hasUidSigningCertificate(uid, certificate, type);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public String[] getPackagesForUid(int uid) {
try {
return mPM.getPackagesForUid(uid);
@@ -1683,22 +1711,6 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
- public void installPackage(Uri packageURI,
- PackageInstallObserver observer, int flags, String installerPackageName) {
- if (!"file".equals(packageURI.getScheme())) {
- throw new UnsupportedOperationException("Only file:// URIs are supported");
- }
-
- final String originPath = packageURI.getPath();
- try {
- mPM.installPackageAsUser(originPath, observer.getBinder(), flags, installerPackageName,
- mContext.getUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- @Override
public int installExistingPackage(String packageName) throws NameNotFoundException {
return installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN);
}
@@ -2755,6 +2767,24 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
+ public CharSequence getHarmfulAppWarning(String packageName) {
+ try {
+ return mPM.getHarmfulAppWarning(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void setHarmfulAppWarning(String packageName, CharSequence warning) {
+ try {
+ mPM.setHarmfulAppWarning(packageName, warning, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
public ArtManager getArtManager() {
synchronized (mLock) {
if (mArtManager == null) {
diff --git a/android/app/ClientTransactionHandler.java b/android/app/ClientTransactionHandler.java
index 45c0e0cd..0f66652a 100644
--- a/android/app/ClientTransactionHandler.java
+++ b/android/app/ClientTransactionHandler.java
@@ -24,6 +24,7 @@ import android.os.IBinder;
import com.android.internal.content.ReferrerIntent;
+import java.io.PrintWriter;
import java.util.List;
/**
@@ -121,4 +122,11 @@ public abstract class ClientTransactionHandler {
* provided token.
*/
public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token);
+
+ /**
+ * Debugging output.
+ * @param pw {@link PrintWriter} to write logs to.
+ * @param prefix Prefix to prepend to output.
+ */
+ public abstract void dump(PrintWriter pw, String prefix);
}
diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java
index 16534305..4914ffaf 100644
--- a/android/app/ContextImpl.java
+++ b/android/app/ContextImpl.java
@@ -872,13 +872,19 @@ class ContextImpl extends Context {
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
- // be launched in.
- if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
- && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
+ // be launched in. A bug was existed between N and O-MR1 which allowed this to work. We
+ // maintain this for backwards compatibility.
+ final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
+
+ if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+ && (targetSdkVersion < Build.VERSION_CODES.N
+ || targetSdkVersion >= Build.VERSION_CODES.P)
+ && (options == null
+ || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
- + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
- + " Is this really what you want?");
+ + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ + " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
diff --git a/android/app/Dialog.java b/android/app/Dialog.java
index b162cb16..2b648ea6 100644
--- a/android/app/Dialog.java
+++ b/android/app/Dialog.java
@@ -16,10 +16,6 @@
package android.app;
-import com.android.internal.R;
-import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.PhoneWindow;
-
import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
@@ -32,8 +28,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
-import android.content.res.Configuration;
import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -62,6 +58,10 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.policy.PhoneWindow;
+
import java.lang.ref.WeakReference;
/**
@@ -512,6 +512,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
+ * @see Dialog#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
@@ -519,6 +520,30 @@ public class Dialog implements DialogInterface, Window.Callback,
}
/**
+ * Finds the first descendant view with the given ID or throws an IllegalArgumentException if
+ * the ID is invalid (< 0), there is no matching view in the hierarchy, or the dialog has not
+ * yet been fully created (for example, via {@link #show()} or {@link #create()}).
+ * <p>
+ * <strong>Note:</strong> In most cases -- depending on compiler support --
+ * the resulting view is automatically cast to the target class type. If
+ * the target class type is unconstrained, an explicit cast may be
+ * necessary.
+ *
+ * @param id the ID to search for
+ * @return a view with given ID
+ * @see View#requireViewById(int)
+ * @see Dialog#findViewById(int)
+ */
+ @NonNull
+ public final <T extends View> T requireViewById(@IdRes int id) {
+ T view = findViewById(id);
+ if (view == null) {
+ throw new IllegalArgumentException("ID does not reference a View inside this Dialog");
+ }
+ return view;
+ }
+
+ /**
* Set the screen content from a layout resource. The resource will be
* inflated, adding all top-level views to the screen.
*
diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java
index b469de56..3c38a4ec 100644
--- a/android/app/Instrumentation.java
+++ b/android/app/Instrumentation.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -458,7 +459,8 @@ public class Instrumentation {
*
* @see Context#startActivity(Intent, Bundle)
*/
- public Activity startActivitySync(Intent intent, @Nullable Bundle options) {
+ @NonNull
+ public Activity startActivitySync(@NonNull Intent intent, @Nullable Bundle options) {
validateNotAppThread();
synchronized (mSync) {
@@ -1872,8 +1874,8 @@ public class Instrumentation {
*/
public ActivityResult execStartActivityAsCaller(
Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity,
- int userId) {
+ Intent intent, int requestCode, Bundle options, IBinder permissionToken,
+ boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1904,7 +1906,8 @@ public class Instrumentation {
.startActivityAsCaller(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
- requestCode, 0, null, options, ignoreTargetSecurity, userId);
+ requestCode, 0, null, options, permissionToken,
+ 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 d0f84c8e..553099f2 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.trust.ITrustManager;
import android.content.Context;
@@ -166,24 +167,26 @@ public class KeyguardManager {
* clicking this button, the activity returns
* {@link #RESULT_ALTERNATE}
*
- * @return the intent for launching the activity or null if the credential of the previous
- * owner can not be verified (e.g. because there was none, or the device does not support
- * verifying credentials after a factory reset, or device setup has already been completed).
- *
+ * @return the intent for launching the activity or null if the previous owner of the device
+ * did not set a credential.
+ * @throws UnsupportedOperationException if the device does not support factory reset
+ * credentials
+ * @throws IllegalStateException if the device has already been provisioned
* @hide
*/
+ @SystemApi
public Intent createConfirmFactoryResetCredentialIntent(
CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
if (!LockPatternUtils.frpCredentialEnabled(mContext)) {
Log.w(TAG, "Factory reset credentials not supported.");
- return null;
+ throw new UnsupportedOperationException("not supported on this device");
}
// Cannot verify credential if the device is provisioned
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
Log.e(TAG, "Factory reset credential cannot be verified after provisioning.");
- return null;
+ throw new IllegalStateException("must not be provisioned yet");
}
// Make sure we have a credential
@@ -192,8 +195,10 @@ public class KeyguardManager {
ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE));
if (pdb == null) {
Log.e(TAG, "No persistent data block service");
- return null;
+ throw new UnsupportedOperationException("not supported on this device");
}
+ // The following will throw an UnsupportedOperationException if the device does not
+ // support factory reset credentials (or something went wrong retrieving it).
if (!pdb.hasFrpCredentialHandle()) {
Log.i(TAG, "The persistent data block does not have a factory reset credential.");
return null;
@@ -475,6 +480,39 @@ public class KeyguardManager {
*/
public void requestDismissKeyguard(@NonNull Activity activity,
@Nullable KeyguardDismissCallback callback) {
+ requestDismissKeyguard(activity, null /* message */, callback);
+ }
+
+ /**
+ * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
+ * be dismissed.
+ * <p>
+ * If the Keyguard is not secure or the device is currently in a trusted state, calling this
+ * method will immediately dismiss the Keyguard without any user interaction.
+ * <p>
+ * If the Keyguard is secure and the device is not in a trusted state, this will bring up the
+ * UI so the user can enter their credentials.
+ * <p>
+ * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true,
+ * the screen will turn on when the keyguard is dismissed.
+ *
+ * @param activity The activity requesting the dismissal. The activity must be either visible
+ * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in
+ * which it would be visible if Keyguard would not be hiding it. If that's not
+ * the case, the request will fail immediately and
+ * {@link KeyguardDismissCallback#onDismissError} will be invoked.
+ * @param message A message that will be shown in the keyguard explaining why the user
+ * would want to dismiss it.
+ * @param callback The callback to be called if the request to dismiss Keyguard was successful
+ * or {@code null} if the caller isn't interested in knowing the result. The
+ * callback will not be invoked if the activity was destroyed before the
+ * callback was received.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SHOW_KEYGUARD_MESSAGE)
+ @SystemApi
+ public void requestDismissKeyguard(@NonNull Activity activity, @Nullable CharSequence message,
+ @Nullable KeyguardDismissCallback callback) {
try {
mAm.dismissKeyguard(activity.getActivityToken(), new IKeyguardDismissCallback.Stub() {
@Override
@@ -497,9 +535,9 @@ public class KeyguardManager {
activity.mHandler.post(callback::onDismissCancelled);
}
}
- });
+ }, message);
} catch (RemoteException e) {
- Log.i(TAG, "Failed to dismiss keyguard: " + e);
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/android/app/Notification.java b/android/app/Notification.java
index 85c3be82..d6fddfca 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -1022,10 +1022,18 @@ public class Notification implements Parcelable
/**
* {@link #extras} key: A String array containing the people that this notification relates to,
* each of which was supplied to {@link Builder#addPerson(String)}.
+ *
+ * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST}
*/
public static final String EXTRA_PEOPLE = "android.people";
/**
+ * {@link #extras} key: An arrayList of {@link Person} objects containing the people that
+ * this notification relates to.
+ */
+ public static final String EXTRA_PEOPLE_LIST = "android.people.list";
+
+ /**
* Allow certain system-generated notifications to appear before the device is provisioned.
* Only available to notifications coming from the android package.
* @hide
@@ -1063,10 +1071,20 @@ public class Notification implements Parcelable
* direct replies
* {@link android.app.Notification.MessagingStyle} notification. This extra is a
* {@link CharSequence}
+ *
+ * @deprecated use {@link #EXTRA_MESSAGING_PERSON}
*/
public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
/**
+ * {@link #extras} key: the person to be displayed for all messages sent by the user including
+ * direct replies
+ * {@link android.app.Notification.MessagingStyle} notification. This extra is a
+ * {@link Person}
+ */
+ public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser";
+
+ /**
* {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation
* represented by a {@link android.app.Notification.MessagingStyle}
*/
@@ -1250,10 +1268,67 @@ public class Notification implements Parcelable
*/
private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
+ /**
+ * {@link }: No semantic action defined.
+ */
+ public static final int SEMANTIC_ACTION_NONE = 0;
+
+ /**
+ * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
+ * may be appropriate.
+ */
+ public static final int SEMANTIC_ACTION_REPLY = 1;
+
+ /**
+ * {@code SemanticAction}: Mark content as read.
+ */
+ public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
+
+ /**
+ * {@code SemanticAction}: Mark content as unread.
+ */
+ public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
+
+ /**
+ * {@code SemanticAction}: Delete the content associated with the notification. This
+ * could mean deleting an email, message, etc.
+ */
+ public static final int SEMANTIC_ACTION_DELETE = 4;
+
+ /**
+ * {@code SemanticAction}: Archive the content associated with the notification. This
+ * could mean archiving an email, message, etc.
+ */
+ public static final int SEMANTIC_ACTION_ARCHIVE = 5;
+
+ /**
+ * {@code SemanticAction}: Mute the content associated with the notification. This could
+ * mean silencing a conversation or currently playing media.
+ */
+ public static final int SEMANTIC_ACTION_MUTE = 6;
+
+ /**
+ * {@code SemanticAction}: Unmute the content associated with the notification. This could
+ * mean un-silencing a conversation or currently playing media.
+ */
+ public static final int SEMANTIC_ACTION_UNMUTE = 7;
+
+ /**
+ * {@code SemanticAction}: Mark content with a thumbs up.
+ */
+ public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
+
+ /**
+ * {@code SemanticAction}: Mark content with a thumbs down.
+ */
+ public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
+
+
private final Bundle mExtras;
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
private boolean mAllowGeneratedReplies = true;
+ private final @SemanticAction int mSemanticAction;
/**
* Small icon representing the action.
@@ -1288,6 +1363,7 @@ public class Notification implements Parcelable
mExtras = Bundle.setDefusable(in.readBundle(), true);
mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
mAllowGeneratedReplies = in.readInt() == 1;
+ mSemanticAction = in.readInt();
}
/**
@@ -1295,12 +1371,14 @@ public class Notification implements Parcelable
*/
@Deprecated
public Action(int icon, CharSequence title, PendingIntent intent) {
- this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
+ this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
+ SEMANTIC_ACTION_NONE);
}
/** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
- RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
+ RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
+ @SemanticAction int semanticAction) {
this.mIcon = icon;
if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
this.icon = icon.getResId();
@@ -1310,6 +1388,7 @@ public class Notification implements Parcelable
this.mExtras = extras != null ? extras : new Bundle();
this.mRemoteInputs = remoteInputs;
this.mAllowGeneratedReplies = allowGeneratedReplies;
+ this.mSemanticAction = semanticAction;
}
/**
@@ -1348,6 +1427,15 @@ public class Notification implements Parcelable
}
/**
+ * Returns the {@code SemanticAction} associated with this {@link Action}. A
+ * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
+ * (eg. reply, mark as read, delete, etc).
+ */
+ public @SemanticAction int getSemanticAction() {
+ return mSemanticAction;
+ }
+
+ /**
* Get the list of inputs to be collected from the user that ONLY accept data when this
* action is sent. These remote inputs are guaranteed to return true on a call to
* {@link RemoteInput#isDataOnly}.
@@ -1371,6 +1459,7 @@ public class Notification implements Parcelable
private boolean mAllowGeneratedReplies = true;
private final Bundle mExtras;
private ArrayList<RemoteInput> mRemoteInputs;
+ private @SemanticAction int mSemanticAction;
/**
* Construct a new builder for {@link Action} object.
@@ -1390,7 +1479,7 @@ public class Notification implements Parcelable
* @param intent the {@link PendingIntent} to fire when users trigger this action
*/
public Builder(Icon icon, CharSequence title, PendingIntent intent) {
- this(icon, title, intent, new Bundle(), null, true);
+ this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
}
/**
@@ -1401,11 +1490,12 @@ public class Notification implements Parcelable
public Builder(Action action) {
this(action.getIcon(), action.title, action.actionIntent,
new Bundle(action.mExtras), action.getRemoteInputs(),
- action.getAllowGeneratedReplies());
+ action.getAllowGeneratedReplies(), action.getSemanticAction());
}
private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
- RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
+ RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
+ @SemanticAction int semanticAction) {
mIcon = icon;
mTitle = title;
mIntent = intent;
@@ -1415,6 +1505,7 @@ public class Notification implements Parcelable
Collections.addAll(mRemoteInputs, remoteInputs);
}
mAllowGeneratedReplies = allowGeneratedReplies;
+ mSemanticAction = semanticAction;
}
/**
@@ -1470,6 +1561,19 @@ public class Notification implements Parcelable
}
/**
+ * Sets the {@code SemanticAction} for this {@link Action}. A
+ * {@code SemanticAction} denotes what an {@link Action}'s
+ * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
+ * @param semanticAction a SemanticAction defined within {@link Action} with
+ * {@code SEMANTIC_ACTION_} prefixes
+ * @return this object for method chaining
+ */
+ public Builder setSemanticAction(@SemanticAction int semanticAction) {
+ mSemanticAction = semanticAction;
+ return this;
+ }
+
+ /**
* Apply an extender to this action builder. Extenders may be used to add
* metadata or change options on this builder.
*/
@@ -1510,7 +1614,7 @@ public class Notification implements Parcelable
RemoteInput[] textInputsArr = textInputs.isEmpty()
? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
- mAllowGeneratedReplies);
+ mAllowGeneratedReplies, mSemanticAction);
}
}
@@ -1522,12 +1626,15 @@ public class Notification implements Parcelable
actionIntent, // safe to alias
mExtras == null ? new Bundle() : new Bundle(mExtras),
getRemoteInputs(),
- getAllowGeneratedReplies());
+ getAllowGeneratedReplies(),
+ getSemanticAction());
}
+
@Override
public int describeContents() {
return 0;
}
+
@Override
public void writeToParcel(Parcel out, int flags) {
final Icon ic = getIcon();
@@ -1547,7 +1654,9 @@ public class Notification implements Parcelable
out.writeBundle(mExtras);
out.writeTypedArray(mRemoteInputs, flags);
out.writeInt(mAllowGeneratedReplies ? 1 : 0);
+ out.writeInt(mSemanticAction);
}
+
public static final Parcelable.Creator<Action> CREATOR =
new Parcelable.Creator<Action>() {
public Action createFromParcel(Parcel in) {
@@ -1809,6 +1918,29 @@ public class Notification implements Parcelable
return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
}
}
+
+ /**
+ * Provides meaning to an {@link Action} that hints at what the associated
+ * {@link PendingIntent} will do. For example, an {@link Action} with a
+ * {@link PendingIntent} that replies to a text message notification may have the
+ * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
+ SEMANTIC_ACTION_NONE,
+ SEMANTIC_ACTION_REPLY,
+ SEMANTIC_ACTION_MARK_AS_READ,
+ SEMANTIC_ACTION_MARK_AS_UNREAD,
+ SEMANTIC_ACTION_DELETE,
+ SEMANTIC_ACTION_ARCHIVE,
+ SEMANTIC_ACTION_MUTE,
+ SEMANTIC_ACTION_UNMUTE,
+ SEMANTIC_ACTION_THUMBS_UP,
+ SEMANTIC_ACTION_THUMBS_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SemanticAction {}
}
/**
@@ -2819,7 +2951,7 @@ public class Notification implements Parcelable
private Bundle mUserExtras = new Bundle();
private Style mStyle;
private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
- private ArrayList<String> mPersonList = new ArrayList<String>();
+ private ArrayList<Person> mPersonList = new ArrayList<>();
private NotificationColorUtil mColorUtil;
private boolean mIsLegacy;
private boolean mIsLegacyInitialized;
@@ -2910,8 +3042,9 @@ public class Notification implements Parcelable
Collections.addAll(mActions, mN.actions);
}
- if (mN.extras.containsKey(EXTRA_PEOPLE)) {
- Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE));
+ if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) {
+ ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST);
+ mPersonList.addAll(people);
}
if (mN.getSmallIcon() == null && mN.icon != 0) {
@@ -3621,13 +3754,41 @@ public class Notification implements Parcelable
* URIs. The path part of these URIs must exist in the contacts database, in the
* appropriate column, or the reference will be discarded as invalid. Telephone schema
* URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
+ * It is also possible to provide a URI with the schema {@code name:} in order to uniquely
+ * identify a person without an entry in the contacts database.
* </P>
*
* @param uri A URI for the person.
* @see Notification#EXTRA_PEOPLE
+ * @deprecated use {@link #addPerson(Person)}
*/
public Builder addPerson(String uri) {
- mPersonList.add(uri);
+ addPerson(new Person().setUri(uri));
+ return this;
+ }
+
+ /**
+ * Add a person that is relevant to this notification.
+ *
+ * <P>
+ * Depending on user preferences, this annotation may allow the notification to pass
+ * through interruption filters, if this notification is of category {@link #CATEGORY_CALL}
+ * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to
+ * appear more prominently in the user interface.
+ * </P>
+ *
+ * <P>
+ * A person should usually contain a uri in order to benefit from the ranking boost.
+ * However, even if no uri is provided, it's beneficial to provide other people in the
+ * notification, such that listeners and voice only devices can announce and handle them
+ * properly.
+ * </P>
+ *
+ * @param person the person to add.
+ * @see Notification#EXTRA_PEOPLE_LIST
+ */
+ public Builder addPerson(Person person) {
+ mPersonList.add(person);
return this;
}
@@ -3934,7 +4095,10 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.chronometer, View.GONE);
contentView.setViewVisibility(R.id.header_text, View.GONE);
contentView.setTextViewText(R.id.header_text, null);
+ contentView.setViewVisibility(R.id.header_text_secondary, View.GONE);
+ contentView.setTextViewText(R.id.header_text_secondary, null);
contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
+ contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE);
contentView.setViewVisibility(R.id.time_divider, View.GONE);
contentView.setViewVisibility(R.id.time, View.GONE);
contentView.setImageViewIcon(R.id.profile_badge, null);
@@ -3965,8 +4129,8 @@ public class Notification implements Parcelable
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
- bindNotificationHeader(contentView, p.ambient);
- bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
+ bindNotificationHeader(contentView, p.ambient, p.headerTextSecondary);
+ bindLargeIcon(contentView, p.hideLargeIcon || p.ambient, p.alwaysShowReply);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4248,12 +4412,14 @@ public class Notification implements Parcelable
return null;
}
- private void bindNotificationHeader(RemoteViews contentView, boolean ambient) {
+ private void bindNotificationHeader(RemoteViews contentView, boolean ambient,
+ CharSequence secondaryHeaderText) {
bindSmallIcon(contentView, ambient);
bindHeaderAppName(contentView, ambient);
if (!ambient) {
// Ambient view does not have these
bindHeaderText(contentView);
+ bindHeaderTextSecondary(contentView, secondaryHeaderText);
bindHeaderChronometerAndTime(contentView);
bindProfileBadge(contentView);
}
@@ -4322,6 +4488,17 @@ public class Notification implements Parcelable
}
}
+ private void bindHeaderTextSecondary(RemoteViews contentView, CharSequence secondaryText) {
+ if (!TextUtils.isEmpty(secondaryText)) {
+ contentView.setTextViewText(R.id.header_text_secondary, processTextSpans(
+ processLegacyText(secondaryText)));
+ setTextViewColorSecondary(contentView, R.id.header_text_secondary);
+ contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE);
+ contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE);
+ setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider);
+ }
+ }
+
/**
* @hide
*/
@@ -4555,7 +4732,7 @@ public class Notification implements Parcelable
ambient ? R.layout.notification_template_ambient_header
: R.layout.notification_template_header);
resetNotificationHeader(header);
- bindNotificationHeader(header, ambient);
+ bindNotificationHeader(header, ambient, null);
if (colorized != null) {
mN.extras.putBoolean(EXTRA_COLORIZED, colorized);
} else {
@@ -4968,8 +5145,7 @@ public class Notification implements Parcelable
mActions.toArray(mN.actions);
}
if (!mPersonList.isEmpty()) {
- mN.extras.putStringArray(EXTRA_PEOPLE,
- mPersonList.toArray(new String[mPersonList.size()]));
+ mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList);
}
if (mN.bigContentView != null || mN.contentView != null
|| mN.headsUpContentView != null) {
@@ -5965,7 +6141,7 @@ public class Notification implements Parcelable
*/
public static final int MAXIMUM_RETAINED_MESSAGES = 25;
- CharSequence mUserDisplayName;
+ @NonNull Person mUser;
@Nullable CharSequence mConversationTitle;
List<Message> mMessages = new ArrayList<>();
List<Message> mHistoricMessages = new ArrayList<>();
@@ -5979,23 +6155,54 @@ public class Notification implements Parcelable
* user before the posting app reposts the notification with those messages after they've
* been actually sent and in previous messages sent by the user added in
* {@link #addMessage(Notification.MessagingStyle.Message)}
+ *
+ * @deprecated use {@code MessagingStyle(Person)}
*/
public MessagingStyle(@NonNull CharSequence userDisplayName) {
- mUserDisplayName = userDisplayName;
+ this(new Person().setName(userDisplayName));
+ }
+
+ /**
+ * @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.
+ */
+ public MessagingStyle(@NonNull Person user) {
+ mUser = user;
+ if (user == null || user.getName() == null) {
+ throw new RuntimeException("user must be valid and have a name");
+ }
+ }
+
+ /**
+ * @return the user to be displayed for any replies sent by the user
+ */
+ public Person getUser() {
+ return mUser;
}
/**
* Returns the name to be displayed for any replies sent by the user
+ *
+ * @deprecated use {@link #getUser()} instead
*/
public CharSequence getUserDisplayName() {
- return mUserDisplayName;
+ return mUser.getName();
}
/**
* Sets the title to be displayed on this conversation. May be set to {@code null}.
*
- * @param conversationTitle A name for the conversation, or {@code null}
- * @return this object for method chaining.
+ * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your
+ * application's target version is less than {@link Build.VERSION_CODES#P}, setting a
+ * conversation title to a non-null value will make {@link #isGroupConversation()} return
+ * {@code true} and passing {@code null} will make it return {@code false}. In
+ * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)}
+ * to set group conversation status.
+ *
+ * @param conversationTitle Title displayed for this conversation
+ * @return this object for method chaining
*/
public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
mConversationTitle = conversationTitle;
@@ -6024,8 +6231,28 @@ public class Notification implements Parcelable
* @see Message#Message(CharSequence, long, CharSequence)
*
* @return this object for method chaining
+ *
+ * @deprecated use {@link #addMessage(CharSequence, long, Person)}
*/
public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
+ return addMessage(text, timestamp,
+ sender == null ? null : new Person().setName(sender));
+ }
+
+ /**
+ * Adds a message for display by this notification. Convenience call for a simple
+ * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
+ * @param text A {@link CharSequence} to be displayed as the message content
+ * @param timestamp Time at which the message arrived
+ * @param sender The {@link Person} who sent the message.
+ * Should be <code>null</code> for messages by the current user, in which case
+ * the platform will insert the user set in {@code MessagingStyle(Person)}.
+ *
+ * @see Message#Message(CharSequence, long, CharSequence)
+ *
+ * @return this object for method chaining
+ */
+ public MessagingStyle addMessage(CharSequence text, long timestamp, Person sender) {
return addMessage(new Message(text, timestamp, sender));
}
@@ -6083,6 +6310,7 @@ public class Notification implements Parcelable
/**
* Sets whether this conversation notification represents a group.
+ *
* @param isGroupConversation {@code true} if the conversation represents a group,
* {@code false} otherwise.
* @return this object for method chaining
@@ -6093,9 +6321,27 @@ public class Notification implements Parcelable
}
/**
- * Returns {@code true} if this notification represents a group conversation.
+ * Returns {@code true} if this notification represents a group conversation, otherwise
+ * {@code false}.
+ *
+ * <p> If the application that generated this {@link MessagingStyle} targets an SDK version
+ * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or
+ * not the conversation title is set; returning {@code true} if the conversation title is
+ * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward,
+ * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for
+ * named, non-group conversations.
+ *
+ * @see #setConversationTitle(CharSequence)
*/
public boolean isGroupConversation() {
+ // When target SDK version is < P, a non-null conversation title dictates if this is
+ // as group conversation.
+ if (mBuilder != null
+ && mBuilder.mContext.getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.P) {
+ return mConversationTitle != null;
+ }
+
return mIsGroupConversation;
}
@@ -6105,8 +6351,10 @@ public class Notification implements Parcelable
@Override
public void addExtras(Bundle extras) {
super.addExtras(extras);
- if (mUserDisplayName != null) {
- extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUserDisplayName);
+ if (mUser != null) {
+ // For legacy usages
+ extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName());
+ extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser);
}
if (mConversationTitle != null) {
extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
@@ -6126,14 +6374,15 @@ public class Notification implements Parcelable
Message m = findLatestIncomingMessage();
CharSequence text = (m == null) ? null : m.mText;
CharSequence sender = m == null ? null
- : TextUtils.isEmpty(m.mSender) ? mUserDisplayName : m.mSender;
+ : m.mSender == null || TextUtils.isEmpty(m.mSender.getName())
+ ? mUser.getName() : m.mSender.getName();
CharSequence title;
if (!TextUtils.isEmpty(mConversationTitle)) {
if (!TextUtils.isEmpty(sender)) {
BidiFormatter bidi = BidiFormatter.getInstance();
title = mBuilder.mContext.getString(
com.android.internal.R.string.notification_messaging_title_template,
- bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(m.mSender));
+ bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender));
} else {
title = mConversationTitle;
}
@@ -6156,7 +6405,11 @@ public class Notification implements Parcelable
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
- mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
+ mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON);
+ if (mUser == null) {
+ CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
+ mUser = new Person().setName(displayName);
+ }
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
mMessages = Message.getMessagesFromBundleArray(messages);
@@ -6172,7 +6425,7 @@ public class Notification implements Parcelable
public RemoteViews makeContentView(boolean increasedHeight) {
mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView();
+ RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
mBuilder.mActions = mBuilder.mOriginalActions;
mBuilder.mOriginalActions = null;
return remoteViews;
@@ -6191,7 +6444,7 @@ public class Notification implements Parcelable
for (int i = messages.size() - 1; i >= 0; i--) {
Message m = messages.get(i);
// Incoming messages have a non-empty sender.
- if (!TextUtils.isEmpty(m.mSender)) {
+ if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) {
return m;
}
}
@@ -6207,27 +6460,40 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
+ return makeBigContentView(false /* showRightIcon */);
+ }
+
+ @NonNull
+ private RemoteViews makeBigContentView(boolean showRightIcon) {
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
- if (isOneToOne) {
- // Let's add the conversationTitle in case we didn't have one before and all
- // messages are from the same sender
- conversationTitle = createConversationTitleFromMessages();
- } else if (hasOnlyWhiteSpaceSenders()) {
+ CharSequence nameReplacement = null;
+ if (hasOnlyWhiteSpaceSenders()) {
isOneToOne = true;
+ nameReplacement = conversationTitle;
+ conversationTitle = null;
}
- boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
- .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+ .hideLargeIcon(!showRightIcon || isOneToOne)
+ .headerTextSecondary(conversationTitle)
+ .alwaysShowReply(showRightIcon));
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) {
+ 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.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
mBuilder.mN.mLargeIcon);
+ contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
+ nameReplacement);
contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
isOneToOne);
contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
@@ -6238,8 +6504,8 @@ public class Notification implements Parcelable
private boolean hasOnlyWhiteSpaceSenders() {
for (int i = 0; i < mMessages.size(); i++) {
Message m = mMessages.get(i);
- CharSequence sender = m.getSender();
- if (!isWhiteSpace(sender)) {
+ Person sender = m.getSenderPerson();
+ if (sender != null && !isWhiteSpace(sender.getName())) {
return false;
}
}
@@ -6268,9 +6534,9 @@ public class Notification implements Parcelable
ArraySet<CharSequence> names = new ArraySet<>();
for (int i = 0; i < mMessages.size(); i++) {
Message m = mMessages.get(i);
- CharSequence sender = m.getSender();
+ Person sender = m.getSenderPerson();
if (sender != null) {
- names.add(sender);
+ names.add(sender.getName());
}
}
SpannableStringBuilder title = new SpannableStringBuilder();
@@ -6290,7 +6556,7 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- RemoteViews remoteViews = makeBigContentView();
+ RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
return remoteViews;
}
@@ -6305,13 +6571,15 @@ public class Notification implements Parcelable
static final String KEY_TEXT = "text";
static final String KEY_TIMESTAMP = "time";
static final String KEY_SENDER = "sender";
+ static final String KEY_SENDER_PERSON = "sender_person";
static final String KEY_DATA_MIME_TYPE = "type";
static final String KEY_DATA_URI= "uri";
static final String KEY_EXTRAS_BUNDLE = "extras";
private final CharSequence mText;
private final long mTimestamp;
- private final CharSequence mSender;
+ @Nullable
+ private final Person mSender;
private Bundle mExtras = new Bundle();
private String mDataMimeType;
@@ -6326,8 +6594,28 @@ public class Notification implements Parcelable
* the platform will insert {@link MessagingStyle#getUserDisplayName()}.
* Should be unique amongst all individuals in the conversation, and should be
* consistent during re-posts of the notification.
+ *
+ * @deprecated use {@code Message(CharSequence, long, Person)}
*/
public Message(CharSequence text, long timestamp, CharSequence sender){
+ this(text, timestamp, sender == null ? null : new Person().setName(sender));
+ }
+
+ /**
+ * Constructor
+ * @param text A {@link CharSequence} to be displayed as the message content
+ * @param timestamp Time at which the message arrived
+ * @param sender The {@link Person} who sent the message.
+ * Should be <code>null</code> for messages by the current user, in which case
+ * the platform will insert the user set in {@code MessagingStyle(Person)}.
+ * <p>
+ * The person provided should contain an Icon, set with {@link Person#setIcon(Icon)}
+ * and also have a name provided with {@link Person#setName(CharSequence)}. If multiple
+ * users have the same name, consider providing a key with {@link Person#setKey(String)}
+ * in order to differentiate between the different users.
+ * </p>
+ */
+ public Message(CharSequence text, long timestamp, @Nullable Person sender){
mText = text;
mTimestamp = timestamp;
mSender = sender;
@@ -6390,8 +6678,18 @@ public class Notification implements Parcelable
/**
* Get the text used to display the contact's name in the messaging experience
+ *
+ * @deprecated use {@link #getSenderPerson()}
*/
public CharSequence getSender() {
+ return mSender == null ? null : mSender.getName();
+ }
+
+ /**
+ * Get the sender associated with this message.
+ */
+ @Nullable
+ public Person getSenderPerson() {
return mSender;
}
@@ -6417,7 +6715,9 @@ public class Notification implements Parcelable
}
bundle.putLong(KEY_TIMESTAMP, mTimestamp);
if (mSender != null) {
- bundle.putCharSequence(KEY_SENDER, mSender);
+ // Legacy listeners need this
+ bundle.putCharSequence(KEY_SENDER, mSender.getName());
+ bundle.putParcelable(KEY_SENDER_PERSON, mSender);
}
if (mDataMimeType != null) {
bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
@@ -6466,8 +6766,20 @@ public class Notification implements Parcelable
if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
return null;
} else {
+
+ Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON);
+ if (senderPerson == null) {
+ // Legacy apps that use compat don't actually provide the sender objects
+ // We need to fix the compat version to provide people / use
+ // the native api instead
+ CharSequence senderName = bundle.getCharSequence(KEY_SENDER);
+ if (senderName != null) {
+ senderPerson = new Person().setName(senderName);
+ }
+ }
Message message = new Message(bundle.getCharSequence(KEY_TEXT),
- bundle.getLong(KEY_TIMESTAMP), bundle.getCharSequence(KEY_SENDER));
+ bundle.getLong(KEY_TIMESTAMP),
+ senderPerson);
if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
bundle.containsKey(KEY_DATA_URI)) {
message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
@@ -7102,6 +7414,176 @@ public class Notification implements Parcelable
}
}
+ /**
+ * A Person associated with this Notification.
+ */
+ public static final class Person implements Parcelable {
+ @Nullable private CharSequence mName;
+ @Nullable private Icon mIcon;
+ @Nullable private String mUri;
+ @Nullable private String mKey;
+
+ protected Person(Parcel in) {
+ mName = in.readCharSequence();
+ if (in.readInt() != 0) {
+ mIcon = Icon.CREATOR.createFromParcel(in);
+ }
+ mUri = in.readString();
+ mKey = in.readString();
+ }
+
+ /**
+ * Create a new person.
+ */
+ public Person() {
+ }
+
+ /**
+ * Give this person a name.
+ *
+ * @param name the name of this person
+ */
+ public Person setName(@Nullable CharSequence name) {
+ this.mName = name;
+ return this;
+ }
+
+ /**
+ * Add an icon for this person.
+ * <br />
+ * This is currently only used for {@link MessagingStyle} notifications and should not be
+ * provided otherwise, in order to save memory. The system will prefer this icon over any
+ * images that are resolved from the URI.
+ *
+ * @param icon the icon of the person
+ */
+ public Person setIcon(@Nullable Icon icon) {
+ this.mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Set a URI associated with this person.
+ *
+ * <P>
+ * Depending on user preferences, adding a URI to a Person may allow the notification to
+ * pass through interruption filters, if this notification is of
+ * category {@link #CATEGORY_CALL} or {@link #CATEGORY_MESSAGE}.
+ * The addition of people may also cause this notification to appear more prominently in
+ * the user interface.
+ * </P>
+ *
+ * <P>
+ * The person should be specified by the {@code String} representation of a
+ * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
+ * </P>
+ *
+ * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema
+ * URIs. The path part of these URIs must exist in the contacts database, in the
+ * appropriate column, or the reference will be discarded as invalid. Telephone schema
+ * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}.
+ * </P>
+ *
+ * @param uri a URI for the person
+ */
+ public Person setUri(@Nullable String uri) {
+ mUri = uri;
+ return this;
+ }
+
+ /**
+ * Add a key to this person in order to uniquely identify it.
+ * This is especially useful if the name doesn't uniquely identify this person or if the
+ * display name is a short handle of the actual name.
+ *
+ * <P>If no key is provided, the name serves as as the key for the purpose of
+ * identification.</P>
+ *
+ * @param key the key that uniquely identifies this person
+ */
+ public Person setKey(@Nullable String key) {
+ mKey = key;
+ return this;
+ }
+
+
+ /**
+ * @return the uri provided for this person or {@code null} if no Uri was provided
+ */
+ @Nullable
+ public String getUri() {
+ return mUri;
+ }
+
+ /**
+ * @return the name provided for this person or {@code null} if no name was provided
+ */
+ @Nullable
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * @return the icon provided for this person or {@code null} if no icon was provided
+ */
+ @Nullable
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * @return the key provided for this person or {@code null} if no key was provided
+ */
+ @Nullable
+ public String getKey() {
+ return mKey;
+ }
+
+ /**
+ * @return the URI associated with this person, or "name:mName" otherwise
+ * @hide
+ */
+ public String resolveToLegacyUri() {
+ if (mUri != null) {
+ return mUri;
+ }
+ if (mName != null) {
+ return "name:" + mName;
+ }
+ return "";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, @WriteFlags int flags) {
+ dest.writeCharSequence(mName);
+ if (mIcon != null) {
+ dest.writeInt(1);
+ mIcon.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(mUri);
+ dest.writeString(mKey);
+ }
+
+ public static final Creator<Person> CREATOR = new Creator<Person>() {
+ @Override
+ public Person createFromParcel(Parcel in) {
+ return new Person(in);
+ }
+
+ @Override
+ public Person[] newArray(int size) {
+ return new Person[size];
+ }
+ };
+ }
+
// When adding a new Style subclass here, don't forget to update
// Builder.getNotificationStyleClass.
@@ -8541,6 +9023,7 @@ public class Notification implements Parcelable
boolean ambient = false;
CharSequence title;
CharSequence text;
+ CharSequence headerTextSecondary;
boolean hideLargeIcon;
public boolean alwaysShowReply;
@@ -8549,6 +9032,7 @@ public class Notification implements Parcelable
ambient = false;
title = null;
text = null;
+ headerTextSecondary = null;
return this;
}
@@ -8567,6 +9051,11 @@ public class Notification implements Parcelable
return this;
}
+ final StandardTemplateParams headerTextSecondary(CharSequence text) {
+ this.headerTextSecondary = text;
+ return this;
+ }
+
final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
this.alwaysShowReply = alwaysShowReply;
return this;
diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java
index c06ad3f3..30f2697c 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -32,8 +32,6 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.Preconditions;
-
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
@@ -936,7 +934,9 @@ public final class NotificationChannel implements Parcelable {
}
/** @hide */
- public void toProto(ProtoOutputStream proto) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
proto.write(NotificationChannelProto.ID, mId);
proto.write(NotificationChannelProto.NAME, mName);
proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
@@ -959,10 +959,10 @@ public final class NotificationChannel implements Parcelable {
proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
proto.write(NotificationChannelProto.GROUP, mGroup);
if (mAudioAttributes != null) {
- long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES);
- mAudioAttributes.toProto(proto);
- proto.end(aToken);
+ mAudioAttributes.writeToProto(proto, NotificationChannelProto.AUDIO_ATTRIBUTES);
}
proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
+
+ proto.end(token);
}
}
diff --git a/android/app/NotificationChannelGroup.java b/android/app/NotificationChannelGroup.java
index 5cb7fb7a..16166f7c 100644
--- a/android/app/NotificationChannelGroup.java
+++ b/android/app/NotificationChannelGroup.java
@@ -298,13 +298,17 @@ public final class NotificationChannelGroup implements Parcelable {
}
/** @hide */
- public void toProto(ProtoOutputStream proto) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
proto.write(NotificationChannelGroupProto.ID, mId);
proto.write(NotificationChannelGroupProto.NAME, mName.toString());
proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
for (NotificationChannel channel : mChannels) {
- channel.toProto(proto);
+ channel.writeToProto(proto, NotificationChannelGroupProto.CHANNELS);
}
+
+ proto.end(token);
}
}
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index 659cf169..49c03ab9 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -93,6 +93,18 @@ public class NotificationManager {
private static boolean localLOGV = false;
/**
+ * Intent that is broadcast when an application is blocked or unblocked.
+ *
+ * This broadcast is only sent to the app whose block state has changed.
+ *
+ * Input: nothing
+ * Output: nothing
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_APP_BLOCK_STATE_CHANGED =
+ "android.app.action.APP_BLOCK_STATE_CHANGED";
+
+ /**
* Intent that is broadcast when a {@link NotificationChannel} is blocked
* (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
* (when {@link NotificationChannel#getImportance()} is anything other than
@@ -1133,7 +1145,7 @@ public class NotificationManager {
}
/** @hide */
- public void toProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long pToken = proto.start(fieldId);
bitwiseToProtoEnum(proto, PolicyProto.PRIORITY_CATEGORIES, priorityCategories);
diff --git a/android/app/PendingIntent.java b/android/app/PendingIntent.java
index 8b76cc7c..d6429ae9 100644
--- a/android/app/PendingIntent.java
+++ b/android/app/PendingIntent.java
@@ -867,19 +867,30 @@ public final class PendingIntent implements Parcelable {
@Nullable OnFinished onFinished, @Nullable Handler handler,
@Nullable String requiredPermission, @Nullable Bundle options)
throws CanceledException {
+ if (sendAndReturnResult(context, code, intent, onFinished, handler, requiredPermission,
+ options) < 0) {
+ throw new CanceledException();
+ }
+ }
+
+ /**
+ * Like {@link #send}, but returns the result
+ * @hide
+ */
+ public int sendAndReturnResult(Context context, int code, @Nullable Intent intent,
+ @Nullable OnFinished onFinished, @Nullable Handler handler,
+ @Nullable String requiredPermission, @Nullable Bundle options)
+ throws CanceledException {
try {
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver())
: null;
- int res = ActivityManager.getService().sendIntentSender(
+ return ActivityManager.getService().sendIntentSender(
mTarget, mWhitelistToken, code, intent, resolvedType,
onFinished != null
? new FinishedDispatcher(this, onFinished, handler)
: null,
requiredPermission, options);
- if (res < 0) {
- throw new CanceledException();
- }
} catch (RemoteException e) {
throw new CanceledException(e);
}
diff --git a/android/app/ProfilerInfo.java b/android/app/ProfilerInfo.java
index d5234278..0ed1b082 100644
--- a/android/app/ProfilerInfo.java
+++ b/android/app/ProfilerInfo.java
@@ -20,6 +20,7 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import java.io.IOException;
import java.util.Objects;
@@ -55,14 +56,24 @@ public class ProfilerInfo implements Parcelable {
*/
public final String agent;
+ /**
+ * Whether the {@link agent} should be attached early (before bind-application) or during
+ * bind-application. Agents attached prior to binding cannot be loaded from the app's APK
+ * directly and must be given as an absolute path (or available in the default LD_LIBRARY_PATH).
+ * Agents attached during bind-application will miss early setup (e.g., resource initialization
+ * and classloader generation), but are searched in the app's library search path.
+ */
+ public final boolean attachAgentDuringBind;
+
public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
- boolean streaming, String agent) {
+ boolean streaming, String agent, boolean attachAgentDuringBind) {
profileFile = filename;
profileFd = fd;
samplingInterval = interval;
autoStopProfiler = autoStop;
streamingOutput = streaming;
this.agent = agent;
+ this.attachAgentDuringBind = attachAgentDuringBind;
}
public ProfilerInfo(ProfilerInfo in) {
@@ -72,6 +83,7 @@ public class ProfilerInfo implements Parcelable {
autoStopProfiler = in.autoStopProfiler;
streamingOutput = in.streamingOutput;
agent = in.agent;
+ attachAgentDuringBind = in.attachAgentDuringBind;
}
/**
@@ -110,6 +122,21 @@ public class ProfilerInfo implements Parcelable {
out.writeInt(autoStopProfiler ? 1 : 0);
out.writeInt(streamingOutput ? 1 : 0);
out.writeString(agent);
+ out.writeBoolean(attachAgentDuringBind);
+ }
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(ProfilerInfoProto.PROFILE_FILE, profileFile);
+ if (profileFd != null) {
+ proto.write(ProfilerInfoProto.PROFILE_FD, profileFd.getFd());
+ }
+ proto.write(ProfilerInfoProto.SAMPLING_INTERVAL, samplingInterval);
+ proto.write(ProfilerInfoProto.AUTO_STOP_PROFILER, autoStopProfiler);
+ proto.write(ProfilerInfoProto.STREAMING_OUTPUT, streamingOutput);
+ proto.write(ProfilerInfoProto.AGENT, agent);
+ proto.end(token);
}
public static final Parcelable.Creator<ProfilerInfo> CREATOR =
@@ -132,6 +159,7 @@ public class ProfilerInfo implements Parcelable {
autoStopProfiler = in.readInt() != 0;
streamingOutput = in.readInt() != 0;
agent = in.readString();
+ attachAgentDuringBind = in.readBoolean();
}
@Override
diff --git a/android/app/RemoteInput.java b/android/app/RemoteInput.java
index 02a01242..b7100e6f 100644
--- a/android/app/RemoteInput.java
+++ b/android/app/RemoteInput.java
@@ -24,6 +24,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
+
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -73,6 +74,15 @@ 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. */
+ private static final String EXTRA_RESULTS_SOURCE = "android.remoteinput.resultsSource";
+
+ /** The user manually entered the data. */
+ public static final int SOURCE_FREE_FORM_INPUT = 0;
+
+ /** The user selected one of the choices from {@link #getChoices}. */
+ public static final int SOURCE_CHOICE = 1;
+
// Flags bitwise-ored to mFlags
private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1;
@@ -416,6 +426,48 @@ public final class RemoteInput implements Parcelable {
intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));
}
+ /**
+ * Set the source of the RemoteInput results. This method should only be called by remote
+ * input collection services (e.g.
+ * {@link android.service.notification.NotificationListenerService})
+ * when sending results to a pending intent.
+ *
+ * @see #SOURCE_FREE_FORM_INPUT
+ * @see #SOURCE_CHOICE
+ *
+ * @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) {
+ Intent clipDataIntent = getClipDataIntentFromIntent(intent);
+ if (clipDataIntent == null) {
+ clipDataIntent = new Intent(); // First time we've added a result.
+ }
+ clipDataIntent.putExtra(EXTRA_RESULTS_SOURCE, source);
+ intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent));
+ }
+
+ /**
+ * Get the source of the RemoteInput results.
+ *
+ * @see #SOURCE_FREE_FORM_INPUT
+ * @see #SOURCE_CHOICE
+ *
+ * @param intent The intent object that fired in response to an action or content intent
+ * which also had one or more remote input requested.
+ * @return The source of the results. If no source was set, {@link #SOURCE_FREE_FORM_INPUT} will
+ * be returned.
+ */
+ public static int getResultsSource(Intent intent) {
+ Intent clipDataIntent = getClipDataIntentFromIntent(intent);
+ if (clipDataIntent == null) {
+ return SOURCE_FREE_FORM_INPUT;
+ }
+ return clipDataIntent.getExtras().getInt(EXTRA_RESULTS_SOURCE, SOURCE_FREE_FORM_INPUT);
+ }
+
private static String getExtraResultsKeyForData(String mimeType) {
return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType;
}
diff --git a/android/app/SharedPreferencesImpl.java b/android/app/SharedPreferencesImpl.java
index 6dca4004..6ac15a5f 100644
--- a/android/app/SharedPreferencesImpl.java
+++ b/android/app/SharedPreferencesImpl.java
@@ -50,11 +50,6 @@ import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
final class SharedPreferencesImpl implements SharedPreferences {
private static final String TAG = "SharedPreferencesImpl";
@@ -74,12 +69,18 @@ final class SharedPreferencesImpl implements SharedPreferences {
private final Object mLock = new Object();
private final Object mWritingToDiskLock = new Object();
- private Future<Map<String, Object>> mMap;
+ @GuardedBy("mLock")
+ private Map<String, Object> mMap;
+ @GuardedBy("mLock")
+ private Throwable mThrowable;
@GuardedBy("mLock")
private int mDiskWritesInFlight = 0;
@GuardedBy("mLock")
+ private boolean mLoaded = false;
+
+ @GuardedBy("mLock")
private StructTimespec mStatTimestamp;
@GuardedBy("mLock")
@@ -106,18 +107,28 @@ final class SharedPreferencesImpl implements SharedPreferences {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
+ mLoaded = false;
mMap = null;
+ mThrowable = null;
startLoadFromDisk();
}
private void startLoadFromDisk() {
- FutureTask<Map<String, Object>> futureTask = new FutureTask<>(() -> loadFromDisk());
- mMap = futureTask;
- new Thread(futureTask, "SharedPreferencesImpl-load").start();
+ synchronized (mLock) {
+ mLoaded = false;
+ }
+ new Thread("SharedPreferencesImpl-load") {
+ public void run() {
+ loadFromDisk();
+ }
+ }.start();
}
- private Map<String, Object> loadFromDisk() {
+ private void loadFromDisk() {
synchronized (mLock) {
+ if (mLoaded) {
+ return;
+ }
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
@@ -131,13 +142,14 @@ final class SharedPreferencesImpl implements SharedPreferences {
Map<String, Object> map = null;
StructStat stat = null;
+ Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
- new FileInputStream(mFile), 16*1024);
+ new FileInputStream(mFile), 16 * 1024);
map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
@@ -146,18 +158,37 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
} catch (ErrnoException e) {
- /* ignore */
+ // An errno exception means the stat failed. Treat as empty/non-existing by
+ // ignoring.
+ } catch (Throwable t) {
+ thrown = t;
}
synchronized (mLock) {
- if (map != null) {
- mStatTimestamp = stat.st_mtim;
- mStatSize = stat.st_size;
- } else {
- map = new HashMap<>();
+ mLoaded = true;
+ mThrowable = thrown;
+
+ // It's important that we always signal waiters, even if we'll make
+ // them fail with an exception. The try-finally is pretty wide, but
+ // better safe than sorry.
+ try {
+ if (thrown == null) {
+ if (map != null) {
+ mMap = map;
+ mStatTimestamp = stat.st_mtim;
+ mStatSize = stat.st_size;
+ } else {
+ mMap = new HashMap<>();
+ }
+ }
+ // In case of a thrown exception, we retain the old map. That allows
+ // any open editors to commit and store updates.
+ } catch (Throwable t) {
+ mThrowable = t;
+ } finally {
+ mLock.notifyAll();
}
}
- return map;
}
static File makeBackupFile(File prefsFile) {
@@ -216,42 +247,40 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
- private @GuardedBy("mLock") Map<String, Object> getLoaded() {
- // For backwards compatibility, we need to ignore any interrupts. b/70122540.
- for (;;) {
- try {
- return mMap.get();
- } catch (ExecutionException e) {
- throw new IllegalStateException(e);
- } catch (InterruptedException e) {
- // Ignore and try again.
- }
- }
- }
- private @GuardedBy("mLock") Map<String, Object> getLoadedWithBlockGuard() {
- if (!mMap.isDone()) {
+ @GuardedBy("mLock")
+ private void awaitLoadedLocked() {
+ if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
- return getLoaded();
+ while (!mLoaded) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException unused) {
+ }
+ }
+ if (mThrowable != null) {
+ throw new IllegalStateException(mThrowable);
+ }
}
@Override
public Map<String, ?> getAll() {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- return new HashMap<String, Object>(map);
+ awaitLoadedLocked();
+ //noinspection unchecked
+ return new HashMap<String, Object>(mMap);
}
}
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- String v = (String) map.get(key);
+ awaitLoadedLocked();
+ String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
@@ -259,65 +288,66 @@ final class SharedPreferencesImpl implements SharedPreferences {
@Override
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- @SuppressWarnings("unchecked")
- Set<String> v = (Set<String>) map.get(key);
+ awaitLoadedLocked();
+ Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
}
@Override
public int getInt(String key, int defValue) {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- Integer v = (Integer) map.get(key);
+ awaitLoadedLocked();
+ Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
@Override
public long getLong(String key, long defValue) {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- Long v = (Long) map.get(key);
+ awaitLoadedLocked();
+ Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
@Override
public float getFloat(String key, float defValue) {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- Float v = (Float) map.get(key);
+ awaitLoadedLocked();
+ Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
}
}
@Override
public boolean getBoolean(String key, boolean defValue) {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- Boolean v = (Boolean) map.get(key);
+ awaitLoadedLocked();
+ Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}
@Override
public boolean contains(String key) {
- Map<String, Object> map = getLoadedWithBlockGuard();
synchronized (mLock) {
- return map.containsKey(key);
+ awaitLoadedLocked();
+ return mMap.containsKey(key);
}
}
@Override
public Editor edit() {
- // TODO: remove the need to call getLoaded() when
+ // TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
- getLoadedWithBlockGuard();
+ synchronized (mLock) {
+ awaitLoadedLocked();
+ }
return new EditorImpl();
}
@@ -471,43 +501,13 @@ final class SharedPreferencesImpl implements SharedPreferences {
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {
- // We can't modify our map as a currently
+ // We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
- mMap = new Future<Map<String, Object>>() {
- private Map<String, Object> mCopiedMap =
- new HashMap<String, Object>(getLoaded());
-
- @Override
- public boolean cancel(boolean mayInterruptIfRunning) {
- return false;
- }
-
- @Override
- public boolean isCancelled() {
- return false;
- }
-
- @Override
- public boolean isDone() {
- return true;
- }
-
- @Override
- public Map<String, Object> get()
- throws InterruptedException, ExecutionException {
- return mCopiedMap;
- }
-
- @Override
- public Map<String, Object> get(long timeout, TimeUnit unit)
- throws InterruptedException, ExecutionException, TimeoutException {
- return mCopiedMap;
- }
- };
+ mMap = new HashMap<String, Object>(mMap);
}
- mapToWriteToDisk = getLoaded();
+ mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
diff --git a/android/app/StatsManager.java b/android/app/StatsManager.java
new file mode 100644
index 00000000..963fc776
--- /dev/null
+++ b/android/app/StatsManager.java
@@ -0,0 +1,238 @@
+/*
+ * 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;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * API for statsd clients to send configurations and retrieve data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends.
+ IStatsManager mService;
+ private static final String TAG = "StatsManager";
+
+ /** 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. */
+ public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
+ "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
+ /**
+ * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
+ * information.
+ */
+ public static final String EXTRA_STATS_DIMENSIONS_VALUE =
+ "android.app.extra.STATS_DIMENSIONS_VALUE";
+
+ /**
+ * Constructor for StatsManagerClient.
+ *
+ * @hide
+ */
+ public StatsManager() {
+ }
+
+ /**
+ * Clients can send a configuration and simultaneously registers the name of a broadcast
+ * receiver that listens for when it should request data.
+ *
+ * @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) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(TAG, "Failed to find statsd when adding configuration");
+ return false;
+ }
+ return service.addConfiguration(configKey, config, pkg, cls);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when adding configuration");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Remove a configuration from logging.
+ *
+ * @param configKey Configuration key to remove.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean removeConfiguration(long configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(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");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * 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}.
+ *
+ * <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 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) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber");
+ return false;
+ }
+ if (pendingIntent != null) {
+ // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+ IBinder intentSender = pendingIntent.getTarget().asBinder();
+ return service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
+ } else {
+ return service.unsetBroadcastSubscriber(configKey, subscriberId);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Clients can request data with a binder call. This getter is destructive and also clears
+ * the retrieved metrics from statsd memory.
+ *
+ * @param configKey Configuration key to retrieve data from.
+ * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getData(long configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(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");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Clients can request metadata for statsd. Will contain stats across all configurations but not
+ * 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.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getMetadata() {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(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");
+ return null;
+ }
+ }
+ }
+
+ private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ synchronized (this) {
+ mService = null;
+ }
+ }
+ }
+
+ private IStatsManager getIStatsManagerLocked() throws RemoteException {
+ if (mService != null) {
+ return mService;
+ }
+ mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ if (mService != null) {
+ mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+ }
+ return mService;
+ }
+}
diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java
index 66cf9915..4310434c 100644
--- a/android/app/SystemServiceRegistry.java
+++ b/android/app/SystemServiceRegistry.java
@@ -38,12 +38,12 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
+import android.content.pm.CrossProfileApps;
+import android.content.pm.ICrossProfileApps;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
-import android.content.pm.crossprofile.CrossProfileApps;
-import android.content.pm.crossprofile.ICrossProfileApps;
import android.content.res.Resources;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -112,6 +112,7 @@ import android.os.IBinder;
import android.os.IHardwarePropertiesManager;
import android.os.IPowerManager;
import android.os.IRecoverySystem;
+import android.os.ISystemUpdateManager;
import android.os.IUserManager;
import android.os.IncidentManager;
import android.os.PowerManager;
@@ -119,6 +120,7 @@ import android.os.Process;
import android.os.RecoverySystem;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemUpdateManager;
import android.os.SystemVibrator;
import android.os.UserHandle;
import android.os.UserManager;
@@ -136,9 +138,9 @@ import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
-import android.util.StatsManager;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.WindowManager;
@@ -484,6 +486,17 @@ final class SystemServiceRegistry {
return new StorageStatsManager(ctx, service);
}});
+ registerService(Context.SYSTEM_UPDATE_SERVICE, SystemUpdateManager.class,
+ new CachedServiceFetcher<SystemUpdateManager>() {
+ @Override
+ public SystemUpdateManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.SYSTEM_UPDATE_SERVICE);
+ ISystemUpdateManager service = ISystemUpdateManager.Stub.asInterface(b);
+ return new SystemUpdateManager(service);
+ }});
+
registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
new CachedServiceFetcher<TelephonyManager>() {
@Override
@@ -494,7 +507,7 @@ final class SystemServiceRegistry {
registerService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
new CachedServiceFetcher<SubscriptionManager>() {
@Override
- public SubscriptionManager createService(ContextImpl ctx) {
+ public SubscriptionManager createService(ContextImpl ctx) throws ServiceNotFoundException {
return new SubscriptionManager(ctx.getOuterContext());
}});
@@ -519,6 +532,13 @@ final class SystemServiceRegistry {
return new EuiccManager(ctx.getOuterContext());
}});
+ registerService(Context.EUICC_CARD_SERVICE, EuiccCardManager.class,
+ new CachedServiceFetcher<EuiccCardManager>() {
+ @Override
+ public EuiccCardManager createService(ContextImpl ctx) {
+ return new EuiccCardManager(ctx.getOuterContext());
+ }});
+
registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
new CachedServiceFetcher<UiModeManager>() {
@Override
diff --git a/android/app/UiAutomation.java b/android/app/UiAutomation.java
index 8f016853..ba39740b 100644
--- a/android/app/UiAutomation.java
+++ b/android/app/UiAutomation.java
@@ -24,7 +24,6 @@ import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -47,10 +46,14 @@ import android.view.accessibility.AccessibilityInteractionClient;
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;
@@ -580,6 +583,8 @@ 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.
@@ -600,14 +605,14 @@ public final class UiAutomation {
if (filter.accept(event)) {
return event;
}
- event.recycle();
+ eventsReceived = CollectionUtils.add(eventsReceived, event);
}
// 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.");
+ + timeoutMillis + " ms, among " + eventsReceived);
}
synchronized (mLock) {
if (mEventQueue.isEmpty()) {
@@ -620,6 +625,10 @@ 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();
diff --git a/android/app/admin/ConnectEvent.java b/android/app/admin/ConnectEvent.java
index f06a9257..d511c57b 100644
--- a/android/app/admin/ConnectEvent.java
+++ b/android/app/admin/ConnectEvent.java
@@ -68,7 +68,7 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable {
@Override
public String toString() {
- return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp,
+ return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp,
mPackageName);
}
diff --git a/android/app/admin/DeviceAdminReceiver.java b/android/app/admin/DeviceAdminReceiver.java
index 2e697ac0..28e845a0 100644
--- a/android/app/admin/DeviceAdminReceiver.java
+++ b/android/app/admin/DeviceAdminReceiver.java
@@ -29,10 +29,14 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.PersistableBundle;
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;
@@ -335,7 +339,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
/**
* Broadcast action: notify the device owner that a user or profile has been removed.
* Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of
- * the new user.
+ * the user.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -343,6 +347,36 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
public static final String ACTION_USER_REMOVED = "android.app.action.USER_REMOVED";
/**
+ * Broadcast action: notify the device owner that a user or profile has been started.
+ * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of
+ * the user.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_USER_STARTED = "android.app.action.USER_STARTED";
+
+ /**
+ * Broadcast action: notify the device owner that a user or profile has been stopped.
+ * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of
+ * the user.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_USER_STOPPED = "android.app.action.USER_STOPPED";
+
+ /**
+ * Broadcast action: notify the device owner that a user or profile has been switched to.
+ * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of
+ * the user.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_USER_SWITCHED = "android.app.action.USER_SWITCHED";
+
+ /**
* A string containing the SHA-256 hash of the bugreport file.
*
* @see #ACTION_BUGREPORT_SHARE
@@ -438,6 +472,65 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
// TO DO: describe syntax.
public static final String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
+ /**
+ * Broadcast action: notify the newly transferred administrator that the transfer
+ * from the original administrator was successful.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TRANSFER_OWNERSHIP_COMPLETE =
+ "android.app.action.TRANSFER_OWNERSHIP_COMPLETE";
+
+ /**
+ * Broadcast action: notify the device owner that the ownership of one of its affiliated
+ * profiles is transferred.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE =
+ "android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE";
+
+ /**
+ * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
+ * allows a mobile device management application to pass data to the management application
+ * instance after owner transfer.
+ *
+ * <p>If the transfer is successful, the new owner receives the data in
+ * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}.
+ * The bundle is not changed during the ownership transfer.
+ *
+ * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
+ */
+ 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;
@@ -860,6 +953,76 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
}
/**
+ * Called when a user or profile is started.
+ *
+ * <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 startedUser The {@link UserHandle} of the user that has just been started.
+ */
+ public void onUserStarted(Context context, Intent intent, UserHandle startedUser) {
+ }
+
+ /**
+ * Called when a user or profile is stopped.
+ *
+ * <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 stoppedUser The {@link UserHandle} of the user that has just been stopped.
+ */
+ public void onUserStopped(Context context, Intent intent, UserHandle stoppedUser) {
+ }
+
+ /**
+ * Called when a user or profile is switched to.
+ *
+ * <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 switchedUser The {@link UserHandle} of the user that has just been switched to.
+ */
+ public void onUserSwitched(Context context, Intent intent, UserHandle switchedUser) {
+ }
+
+ /**
+ * Called on the newly assigned owner (either device owner or profile owner) when the ownership
+ * transfer has completed successfully.
+ *
+ * <p> The {@code bundle} parameter allows the original owner to pass data
+ * to the new one.
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param bundle the data to be passed to the new owner
+ */
+ public void onTransferOwnershipComplete(@NonNull Context context,
+ @Nullable PersistableBundle bundle) {
+ }
+
+ /**
+ * Called on the device owner when the ownership of one of its affiliated profiles is
+ * transferred.
+ *
+ * <p>This can be used when transferring both device and profile ownership when using
+ * work profile on a fully managed device. The process would look like this:
+ * <ol>
+ * <li>Transfer profile ownership</li>
+ * <li>The device owner gets notified with this callback</li>
+ * <li>Transfer device ownership</li>
+ * <li>Both profile and device ownerships have been transferred</li>
+ * </ol>
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param user the {@link UserHandle} of the affiliated user
+ * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
+ */
+ public void onTransferAffiliatedProfileOwnershipComplete(Context context, UserHandle user) {
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -921,6 +1084,19 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
onUserAdded(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
} else if (ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
+ } else if (ACTION_USER_STARTED.equals(action)) {
+ onUserStarted(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
+ } else if (ACTION_USER_STOPPED.equals(action)) {
+ onUserStopped(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
+ } else if (ACTION_USER_SWITCHED.equals(action)) {
+ onUserSwitched(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
+ } else if (ACTION_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
+ PersistableBundle bundle =
+ intent.getParcelableExtra(EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE);
+ onTransferOwnershipComplete(context, bundle);
+ } else if (ACTION_AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE.equals(action)) {
+ onTransferAffiliatedProfileOwnershipComplete(context,
+ intent.getParcelableExtra(Intent.EXTRA_USER));
}
}
}
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 7e80ac7b..8f76032b 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -18,7 +18,6 @@ package android.app.admin;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
-import android.annotation.Condemned;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -50,8 +49,6 @@ import android.graphics.Bitmap;
import android.net.ProxyInfo;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.Process;
@@ -71,6 +68,7 @@ import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.service.restrictions.RestrictionsReceiver;
import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
import android.util.ArraySet;
import android.util.Log;
@@ -1124,6 +1122,7 @@ public class DevicePolicyManager {
*
* This broadcast is sent only to the primary user.
* @see #ACTION_PROVISION_MANAGED_DEVICE
+ * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_DEVICE_OWNER_CHANGED
@@ -1159,9 +1158,17 @@ public class DevicePolicyManager {
public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
/**
+ * Constant to indicate the feature of mandatory backups. Used as argument to
+ * {@link #createAdminSupportIntent(String)}.
+ * @see #setMandatoryBackupTransport(ComponentName, ComponentName)
+ */
+ public static final String POLICY_MANDATORY_BACKUPS = "policy_mandatory_backups";
+
+ /**
* A String indicating a specific restricted feature. Can be a user restriction from the
* {@link UserManager}, e.g. {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the values
- * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+ * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or
+ * {@link #POLICY_MANDATORY_BACKUPS}.
* @see #createAdminSupportIntent(String)
* @hide
*/
@@ -1253,6 +1260,26 @@ public class DevicePolicyManager {
= "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
/**
+ * Broadcast action to notify ManagedProvisioning that
+ * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED =
+ "android.app.action.DATA_SHARING_RESTRICTION_CHANGED";
+
+ /**
+ * Broadcast action from ManagedProvisioning to notify that the latest change to
+ * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has been successfully
+ * applied (cross profile intent filters updated). Only usesd for CTS tests.
+ * @hide
+ */
+ @TestApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED =
+ "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
+
+ /**
* Permission policy to prompt user for new permission requests for runtime permissions.
* Already granted or denied permissions are not affected by this.
*/
@@ -1668,6 +1695,56 @@ public class DevicePolicyManager {
public static final String ACTION_DEVICE_ADMIN_SERVICE
= "android.app.action.DEVICE_ADMIN_SERVICE";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"ID_TYPE_"}, value = {
+ ID_TYPE_BASE_INFO,
+ ID_TYPE_SERIAL,
+ ID_TYPE_IMEI,
+ ID_TYPE_MEID
+ })
+ public @interface AttestationIdType {}
+
+ /**
+ * Specifies that the device should attest its manufacturer details. For use with
+ * {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_BASE_INFO = 1;
+
+ /**
+ * Specifies that the device should attest its serial number. For use with
+ * {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_SERIAL = 2;
+
+ /**
+ * Specifies that the device should attest its IMEI. For use with {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_IMEI = 4;
+
+ /**
+ * Specifies that the device should attest its MEID. For use with {@link #generateKeyPair}.
+ *
+ * @see #generateKeyPair
+ */
+ public static final int ID_TYPE_MEID = 8;
+
+ /**
+ * 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.
+ * @see DevicePolicyManager#transferOwnership(ComponentName, ComponentName, PersistableBundle)
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PROFILE_OWNER_CHANGED =
+ "android.app.action.PROFILE_OWNER_CHANGED";
+
/**
* Return true if the given administrator component is currently active (enabled) in the system.
*
@@ -4106,22 +4183,46 @@ public class DevicePolicyManager {
* @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}.
* @param keySpec Specification of the key to generate, see
* {@link java.security.KeyPairGenerator}.
+ * @param idAttestationFlags A bitmask of all the identifiers that should be included in the
+ * attestation record ({@code ID_TYPE_BASE_INFO}, {@code ID_TYPE_SERIAL},
+ * {@code ID_TYPE_IMEI} and {@code ID_TYPE_MEID}), or {@code 0} if no device
+ * identification is required in the attestation record.
+ * Device owner, profile owner and their delegated certificate installer can use
+ * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information
+ * including manufacturer, model, brand, device and product in the attestation record.
+ * Only device owner and their delegated certificate installer can use
+ * {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request
+ * unique device identifiers to be attested.
+ * <p>
+ * If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID}
+ * is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set.
+ * <p>
+ * If any flag is specified, then an attestation challenge must be included in the
+ * {@code keySpec}.
* @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
- * owner.
- * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, or if the
+ * owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
+ * {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner
+ * or the Certificate Installer delegate.
+ * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, if the
* algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec}
- * or {@code ECGenParameterSpec}.
+ * or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the
+ * {@code keySpec} does not contain an attestation challenge.
+ * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])
*/
public AttestedKeyPair generateKeyPair(@Nullable ComponentName admin,
- @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec) {
+ @NonNull String algorithm, @NonNull KeyGenParameterSpec keySpec,
+ @AttestationIdType int idAttestationFlags) {
throwIfParentInstance("generateKeyPair");
try {
final ParcelableKeyGenParameterSpec parcelableSpec =
new ParcelableKeyGenParameterSpec(keySpec);
KeymasterCertificateChain attestationChain = new KeymasterCertificateChain();
+
+ // Translate ID attestation flags to values used by AttestationUtils
final boolean success = mService.generateKeyPair(
- admin, mContext.getPackageName(), algorithm, parcelableSpec, attestationChain);
+ admin, mContext.getPackageName(), algorithm, parcelableSpec,
+ idAttestationFlags, attestationChain);
if (!success) {
Log.e(TAG, "Error generating key via DevicePolicyManagerService.");
return null;
@@ -5982,6 +6083,13 @@ public class DevicePolicyManager {
* Called by a profile owner of a managed profile to remove the cross-profile intent filters
* that go from the managed profile to the parent, or from the parent to the managed profile.
* Only removes those that have been set by the profile owner.
+ * <p>
+ * <em>Note</em>: A list of default cross profile intent filters are set up by the system when
+ * the profile is created, some of them ensure the proper functioning of the profile, while
+ * others enable sharing of data from the parent to the managed profile for user convenience.
+ * These default intent filters are not cleared when this API is called. If the default cross
+ * profile data sharing is not desired, they can be disabled with
+ * {@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.
@@ -6404,12 +6512,6 @@ public class DevicePolicyManager {
public static final int MAKE_USER_DEMO = 0x0004;
/**
- * Flag used by {@link #createAndManageUser} to specify that the newly created user should be
- * started in the background as part of the user creation.
- */
- public static final int START_USER_IN_BACKGROUND = 0x0008;
-
- /**
* Flag used by {@link #createAndManageUser} to specify that the newly created user should skip
* the disabling of system apps during provisioning.
*/
@@ -6422,7 +6524,6 @@ public class DevicePolicyManager {
SKIP_SETUP_WIZARD,
MAKE_USER_EPHEMERAL,
MAKE_USER_DEMO,
- START_USER_IN_BACKGROUND,
LEAVE_ALL_SYSTEM_APPS_ENABLED
})
@Retention(RetentionPolicy.SOURCE)
@@ -6451,7 +6552,8 @@ public class DevicePolicyManager {
* IllegalArgumentException is thrown.
* @param adminExtras Extras that will be passed to onEnable of the admin receiver on the new
* user.
- * @param flags {@link #SKIP_SETUP_WIZARD} is supported.
+ * @param flags {@link #SKIP_SETUP_WIZARD}, {@link #MAKE_USER_EPHEMERAL} and
+ * {@link #LEAVE_ALL_SYSTEM_APPS_ENABLED} are supported.
* @see UserHandle
* @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
* user could not be created.
@@ -6470,8 +6572,8 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner to remove a user and all associated data. The primary user can not
- * be removed.
+ * Called by a device owner to remove a user/profile and all associated data. The primary user
+ * can not be removed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to remove.
@@ -6488,14 +6590,14 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner to switch the specified user to the foreground.
- * <p> This cannot be used to switch to a managed profile.
+ * Called by a device owner to switch the specified secondary user to the foreground.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to switch to; null will switch to primary.
* @return {@code true} if the switch was successful, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner.
* @see Intent#ACTION_USER_FOREGROUND
+ * @see #getSecondaryUsers(ComponentName)
*/
public boolean switchUser(@NonNull ComponentName admin, @Nullable UserHandle userHandle) {
throwIfParentInstance("switchUser");
@@ -6507,13 +6609,32 @@ 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.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ * @see #getSecondaryUsers(ComponentName)
+ */
+ public boolean startUserInBackground(
+ @NonNull ComponentName admin, @NonNull UserHandle userHandle) {
+ throwIfParentInstance("startUserInBackground");
+ try {
+ return mService.startUserInBackground(admin, userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called by a device owner to stop the specified secondary user.
- * <p> This cannot be used to stop the primary user or a managed profile.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to be stopped.
* @return {@code true} if the user can be stopped, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner.
+ * @see #getSecondaryUsers(ComponentName)
*/
public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) {
throwIfParentInstance("stopUser");
@@ -6525,14 +6646,13 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile owner that is affiliated with the device to stop the calling user
- * and switch back to primary.
- * <p> This has no effect when called on a managed profile.
+ * Called by a profile owner of secondary user that is affiliated with the device to stop the
+ * 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.
* @throws SecurityException if {@code admin} is not a profile owner affiliated with the device.
- * @see #isAffiliatedUser
+ * @see #getSecondaryUsers(ComponentName)
*/
public boolean logoutUser(@NonNull ComponentName admin) {
throwIfParentInstance("logoutUser");
@@ -6544,17 +6664,18 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner to list all secondary users on the device, excluding managed
- * profiles.
+ * Called by a device owner to list all secondary users on the device. Managed profiles are not
+ * considered as secondary users.
* <p> Used for various user management APIs, including {@link #switchUser}, {@link #removeUser}
* and {@link #stopUser}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return list of other {@link UserHandle}s on the device.
* @throws SecurityException if {@code admin} is not a device owner.
- * @see #switchUser
- * @see #removeUser
- * @see #stopUser
+ * @see #removeUser(ComponentName, UserHandle)
+ * @see #switchUser(ComponentName, UserHandle)
+ * @see #startUserInBackground(ComponentName, UserHandle)
+ * @see #stopUser(ComponentName, UserHandle)
*/
public List<UserHandle> getSecondaryUsers(@NonNull ComponentName admin) {
throwIfParentInstance("getSecondaryUsers");
@@ -6694,7 +6815,8 @@ public class DevicePolicyManager {
* @param restriction Indicates for which feature the dialog should be displayed. Can be a
* user restriction from {@link UserManager}, e.g.
* {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the constants
- * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+ * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or
+ * {@link #POLICY_MANDATORY_BACKUPS}.
* @return Intent An intent to be used to start the dialog-activity if the restriction is
* set by an admin, or null if the restriction does not exist or no admin set it.
*/
@@ -6915,14 +7037,14 @@ public class DevicePolicyManager {
* task. From {@link android.os.Build.VERSION_CODES#M} removing packages from the lock task
* package list results in locked tasks belonging to those packages to be finished.
* <p>
- * This function can only be called by the device owner or by a profile owner of a user/profile
- * that is affiliated with the device. See {@link #isAffiliatedUser}. Any packages
- * set via this method will be cleared if the user becomes unaffiliated.
+ * 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 package set via this method will be cleared if the user becomes unaffiliated.
*
* @param packages The list of packages allowed to enter lock task mode
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @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
* @see Activity#startLockTask()
* @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String)
@@ -6944,8 +7066,8 @@ public class DevicePolicyManager {
/**
* Returns the list of packages allowed to start the lock task mode.
*
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @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
* @see #setLockTaskPackages
*/
@@ -6985,9 +7107,9 @@ public class DevicePolicyManager {
* 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 or by a profile owner of a user/profile
- * that is affiliated with the device. See {@link #isAffiliatedUser}. Any features
- * set via this method will be cleared if the user becomes unaffiliated.
+ * 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.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param flags Bitfield of feature flags:
@@ -6998,9 +7120,10 @@ public class DevicePolicyManager {
* {@link #LOCK_TASK_FEATURE_RECENTS},
* {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
* {@link #LOCK_TASK_FEATURE_KEYGUARD}
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @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");
@@ -7018,8 +7141,8 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
- * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
- * an affiliated user or profile.
+ * @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
* @see #setLockTaskFeatures
*/
@@ -7454,7 +7577,8 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner to disable the keyguard altogether.
+ * Called by a device owner or profile owner of secondary users that is affiliated with the
+ * device to disable the keyguard altogether.
* <p>
* Setting the keyguard to disabled has the same effect as choosing "None" as the screen lock
* type. However, this call has no effect if a password, pin or pattern is currently set. If a
@@ -7469,7 +7593,10 @@ public class DevicePolicyManager {
* @param disabled {@code true} disables the keyguard, {@code false} reenables it.
* @return {@code false} if attempting to disable the keyguard while a lock password was in
* place. {@code true} otherwise.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of
+ * secondary user that is affiliated with the device.
+ * @see #isAffiliatedUser
+ * @see #getSecondaryUsers
*/
public boolean setKeyguardDisabled(@NonNull ComponentName admin, boolean disabled) {
throwIfParentInstance("setKeyguardDisabled");
@@ -7481,9 +7608,9 @@ public class DevicePolicyManager {
}
/**
- * Called by device owner to disable the status bar. Disabling the status bar blocks
- * notifications, quick settings and other screen overlays that allow escaping from a single use
- * device.
+ * Called by device owner or profile owner of secondary users that is affiliated with the
+ * device to disable the status bar. Disabling the status bar blocks notifications, quick
+ * settings and other screen overlays that allow escaping from a single use device.
* <p>
* <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the
* status bar in LockTask mode can be configured with
@@ -7494,7 +7621,10 @@ public class DevicePolicyManager {
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param disabled {@code true} disables the status bar, {@code false} reenables it.
* @return {@code false} if attempting to disable the status bar failed. {@code true} otherwise.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of
+ * secondary user that is affiliated with the device.
+ * @see #isAffiliatedUser
+ * @see #getSecondaryUsers
*/
public boolean setStatusBarDisabled(@NonNull ComponentName admin, boolean disabled) {
throwIfParentInstance("setStatusBarDisabled");
@@ -8100,6 +8230,47 @@ public class DevicePolicyManager {
}
/**
+ * Called by a device or profile owner to restrict packages from accessing 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,
+ @NonNull List<String> packageNames) {
+ throwIfParentInstance("setMeteredDataDisabled");
+ if (mService != null) {
+ try {
+ return mService.setMeteredDataDisabled(admin, packageNames);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return packageNames;
+ }
+
+ /**
+ * Called by a device or profile owner to retrieve the list of packages which are restricted
+ * by the admin from accessing 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) {
+ throwIfParentInstance("getMeteredDataDisabled");
+ if (mService != null) {
+ try {
+ return mService.getMeteredDataDisabled(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return new ArrayList<>();
+ }
+
+ /**
* 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
@@ -8511,6 +8682,13 @@ public class DevicePolicyManager {
*
* <p> Backup service is off by default when device owner is present.
*
+ * <p> If backups are made mandatory by specifying a non-null mandatory backup transport using
+ * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is
+ * automatically enabled.
+ *
+ * <p> If the backup service is disabled using this method after the mandatory backup transport
+ * has been set, the mandatory backup transport is cleared.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled {@code true} to enable the backup service, {@code false} to disable it.
* @throws SecurityException if {@code admin} is not a device owner.
@@ -8542,6 +8720,43 @@ public class DevicePolicyManager {
}
/**
+ * Makes backups mandatory and enforces the usage of the specified backup transport.
+ *
+ * <p>When a {@code null} backup transport is specified, backups are made optional again.
+ * <p>Only device owner can call this method.
+ * <p>If backups were disabled and a non-null backup transport {@link ComponentName} is
+ * specified, backups will be enabled.
+ *
+ * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param backupTransportComponent The backup transport layer to be used for mandatory backups.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setMandatoryBackupTransport(
+ @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) {
+ try {
+ mService.setMandatoryBackupTransport(admin, backupTransportComponent);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the backup transport which has to be used for backups if backups are mandatory or
+ * {@code null} if backups are not mandatory.
+ *
+ * @return a {@link ComponentName} of the backup transport layer to be used if backups are
+ * mandatory or {@code null} if backups are not mandatory.
+ */
+ public ComponentName getMandatoryBackupTransport() {
+ try {
+ return mService.getMandatoryBackupTransport();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
* Called by a device owner to control the network logging feature.
*
* <p> Network logs contain DNS lookup and connect() library call events. The following library
@@ -8817,15 +9032,6 @@ public class DevicePolicyManager {
}
}
- /** {@hide} */
- @Condemned
- @Deprecated
- public boolean clearApplicationUserData(@NonNull ComponentName admin,
- @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener,
- @NonNull Handler handler) {
- return clearApplicationUserData(admin, packageName, listener, new HandlerExecutor(handler));
- }
-
/**
* Called by the device owner or profile owner to clear application user data of a given
* package. The behaviour of this is equivalent to the target application calling
@@ -8836,14 +9042,14 @@ public class DevicePolicyManager {
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageName The name of the package which will have its user data wiped.
- * @param listener A callback object that will inform the caller when the clearing is done.
* @param 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,
- @NonNull String packageName, @NonNull OnClearApplicationUserDataListener listener,
- @NonNull @CallbackExecutor Executor executor) {
+ @NonNull String packageName, @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnClearApplicationUserDataListener listener) {
throwIfParentInstance("clearAppData");
Preconditions.checkNotNull(executor);
try {
@@ -8926,41 +9132,312 @@ public class DevicePolicyManager {
}
}
- //TODO STOPSHIP Add link to onTransferComplete callback when implemented.
/**
- * Transfers the current administrator. All policies from the current administrator are
- * migrated to the new administrator. The whole operation is atomic - the transfer is either
- * complete or not done at all.
+ * Changes the current administrator to another one. All policies from the current
+ * administrator are migrated to the new administrator. The whole operation is atomic -
+ * the transfer is either complete or not done at all.
*
- * Depending on the current administrator (device owner, profile owner, corporate owned
- * profile owner), you have the following expected behaviour:
+ * <p>Depending on the current administrator (device owner, profile owner), you have the
+ * following expected behaviour:
* <ul>
* <li>A device owner can only be transferred to a new device owner</li>
* <li>A profile owner can only be transferred to a new profile owner</li>
- * <li>A corporate owned managed profile can have two cases:
- * <ul>
- * <li>If the device owner and profile owner are the same package,
- * both will be transferred.</li>
- * <li>If the device owner and profile owner are different packages,
- * and if this method is called from the profile owner, only the profile owner
- * is transferred. Similarly, if it is called from the device owner, only
- * the device owner is transferred.</li>
- * </ul>
- * </li>
* </ul>
*
- * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param target Which {@link DeviceAdminReceiver} we want the new administrator to be.
- * @param bundle Parameters - This bundle allows the current administrator to pass data to the
- * new administrator. The parameters will be received in the
- * onTransferComplete callback.
- * @hide
+ * <p>Use the {@code bundle} parameter to pass data to the new administrator. The data
+ * will be received in the
+ * {@link DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)}
+ * callback of the new administrator.
+ *
+ * <p>The transfer has failed if the original administrator is still the corresponding owner
+ * 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.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param target which {@link DeviceAdminReceiver} we want the new administrator to be
+ * @param bundle data to be sent to the new administrator
+ * @throws SecurityException if {@code admin} is not a device owner nor a profile owner
+ * @throws IllegalArgumentException if {@code admin} or {@code target} is {@code null}, they
+ * are components in the same package or {@code target} is not an active admin
+ */
+ public void transferOwnership(@NonNull ComponentName admin, @NonNull ComponentName target,
+ @Nullable PersistableBundle bundle) {
+ throwIfParentInstance("transferOwnership");
+ try {
+ mService.transferOwnership(admin, target, bundle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by a device owner to specify the user session start message. This may be displayed
+ * during a user switch.
+ * <p>
+ * The message should be limited to a short statement or it may be truncated.
+ * <p>
+ * If the message needs to be localized, it is the responsibility of the
+ * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
+ * and set a new version of this message accordingly.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @param startUserSessionMessage message for starting user session, or {@code null} to use
+ * system default message.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setStartUserSessionMessage(
+ @NonNull ComponentName admin, @Nullable CharSequence startUserSessionMessage) {
+ throwIfParentInstance("setStartUserSessionMessage");
+ try {
+ mService.setStartUserSessionMessage(admin, startUserSessionMessage);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by a device owner to specify the user session end message. This may be displayed
+ * during a user switch.
+ * <p>
+ * The message should be limited to a short statement or it may be truncated.
+ * <p>
+ * If the message needs to be localized, it is the responsibility of the
+ * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
+ * and set a new version of this message accordingly.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @param endUserSessionMessage message for ending user session, or {@code null} to use system
+ * default message.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setEndUserSessionMessage(
+ @NonNull ComponentName admin, @Nullable CharSequence endUserSessionMessage) {
+ throwIfParentInstance("setEndUserSessionMessage");
+ try {
+ mService.setEndUserSessionMessage(admin, endUserSessionMessage);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user session start message.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public CharSequence getStartUserSessionMessage(@NonNull ComponentName admin) {
+ throwIfParentInstance("getStartUserSessionMessage");
+ try {
+ return mService.getStartUserSessionMessage(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the user session end message.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public CharSequence getEndUserSessionMessage(@NonNull ComponentName admin) {
+ throwIfParentInstance("getEndUserSessionMessage");
+ try {
+ return mService.getEndUserSessionMessage(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @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
+ * the database.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public int addOverrideApn(@NonNull ComponentName admin, @NonNull ApnSetting apnSetting) {
+ throwIfParentInstance("addOverrideApn");
+ if (mService != null) {
+ try {
+ return mService.addOverrideApn(admin, apnSetting);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Called by device owner to update an override APN.
+ *
+ * @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
+ * @return {@code true} if the required override APN is successfully updated,
+ * {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public boolean updateOverrideApn(@NonNull ComponentName admin, int apnId,
+ @NonNull ApnSetting apnSetting) {
+ throwIfParentInstance("updateOverrideApn");
+ if (mService != null) {
+ try {
+ return mService.updateOverrideApn(admin, apnId, apnSetting);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device owner to remove an override APN.
+ *
+ * @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}
+ * otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public boolean removeOverrideApn(@NonNull ComponentName admin, int apnId) {
+ throwIfParentInstance("removeOverrideApn");
+ if (mService != null) {
+ try {
+ return mService.removeOverrideApn(admin, apnId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device owner to get all override APNs inserted by device owner.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @return A list of override APNs inserted by device owner.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public List<ApnSetting> getOverrideApns(@NonNull ComponentName admin) {
+ throwIfParentInstance("getOverrideApns");
+ if (mService != null) {
+ try {
+ return mService.getOverrideApns(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Called by device owner to set if override APNs should be enabled.
+ * <p> Override APNs are separated from other APNs on the device, and can only be inserted or
+ * modified by the device owner. When enabled, only override APNs are in use, any other APNs
+ * are ignored.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param enabled {@code true} if override APNs should be enabled, {@code false} otherwise
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setOverrideApnsEnabled(@NonNull ComponentName admin, boolean enabled) {
+ throwIfParentInstance("setOverrideApnEnabled");
+ if (mService != null) {
+ try {
+ mService.setOverrideApnsEnabled(admin, enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by device owner to check if override APNs are currently enabled.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @return {@code true} if override APNs are currently enabled, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ *
+ * @see #setOverrideApnsEnabled(ComponentName, boolean)
+ */
+ public boolean isOverrideApnEnabled(@NonNull ComponentName admin) {
+ throwIfParentInstance("isOverrideApnEnabled");
+ if (mService != null) {
+ try {
+ return mService.isOverrideApnEnabled(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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)}.
+ *
+ * <p>Returns <code>null</code> if no ownership transfer was started for the calling user.
+ *
+ * @see #transferOwnership
+ * @see DeviceAdminReceiver#onTransferOwnershipComplete(Context, PersistableBundle)
*/
- public void transferOwner(@NonNull ComponentName admin, @NonNull ComponentName target,
- PersistableBundle bundle) {
- throwIfParentInstance("transferOwner");
+ @Nullable
+ public PersistableBundle getTransferOwnershipBundle() {
+ throwIfParentInstance("getTransferOwnershipBundle");
try {
- mService.transferOwner(admin, target, bundle);
+ return mService.getTransferOwnershipBundle();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/android/app/admin/DevicePolicyManagerInternal.java b/android/app/admin/DevicePolicyManagerInternal.java
index b692ffd9..ebaf4648 100644
--- a/android/app/admin/DevicePolicyManagerInternal.java
+++ b/android/app/admin/DevicePolicyManagerInternal.java
@@ -123,4 +123,22 @@ public abstract class DevicePolicyManagerInternal {
* @param userId User ID of the profile.
*/
public abstract void reportSeparateProfileChallengeChanged(@UserIdInt int userId);
+
+ /**
+ * Check whether the user could have their password reset in an untrusted manor due to there
+ * being an admin which can call {@link #resetPassword} to reset the password without knowledge
+ * of the previous password.
+ *
+ * @param userId The user in question
+ */
+ public abstract boolean canUserHaveUntrustedCredentialReset(@UserIdInt int userId);
+
+ /**
+ * Return text of error message if printing is disabled.
+ * Called by Print Service when printing is disabled by PO or DO when printing is attempted.
+ *
+ * @param userId The user in question
+ * @return localized error message
+ */
+ public abstract CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId);
}
diff --git a/android/app/admin/DnsEvent.java b/android/app/admin/DnsEvent.java
index 4ddf13e0..a2d704b8 100644
--- a/android/app/admin/DnsEvent.java
+++ b/android/app/admin/DnsEvent.java
@@ -96,7 +96,7 @@ public final class DnsEvent extends NetworkEvent implements Parcelable {
@Override
public String toString() {
- return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname,
+ return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname,
(mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses),
mIpAddressesCount, mTimestamp, mPackageName);
}
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index 7b549cd5..87f22712 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -32,6 +32,8 @@ import android.view.WindowManagerGlobal;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -624,6 +626,7 @@ public class AssistStructure implements Parcelable {
int mMinEms = -1;
int mMaxEms = -1;
int mMaxLength = -1;
+ @Nullable String mTextIdEntry;
// POJO used to override some autofill-related values when the node is parcelized.
// Not written to parcel.
@@ -701,7 +704,7 @@ public class AssistStructure implements Parcelable {
final int flags = mFlags;
if ((flags&FLAGS_HAS_ID) != 0) {
mId = in.readInt();
- if (mId != 0) {
+ if (mId != View.NO_ID) {
mIdEntry = preader.readString();
if (mIdEntry != null) {
mIdType = preader.readString();
@@ -724,6 +727,7 @@ public class AssistStructure implements Parcelable {
mMinEms = in.readInt();
mMaxEms = in.readInt();
mMaxLength = in.readInt();
+ mTextIdEntry = preader.readString();
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
mX = in.readInt();
@@ -857,7 +861,7 @@ public class AssistStructure implements Parcelable {
out.writeInt(writtenFlags);
if ((flags&FLAGS_HAS_ID) != 0) {
out.writeInt(mId);
- if (mId != 0) {
+ if (mId != View.NO_ID) {
pwriter.writeString(mIdEntry);
if (mIdEntry != null) {
pwriter.writeString(mIdType);
@@ -890,6 +894,7 @@ public class AssistStructure implements Parcelable {
out.writeInt(mMinEms);
out.writeInt(mMaxEms);
out.writeInt(mMaxLength);
+ pwriter.writeString(mTextIdEntry);
}
if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
out.writeInt(mX);
@@ -1430,6 +1435,17 @@ public class AssistStructure implements Parcelable {
}
/**
+ * Gets the identifier used to set the text associated with this view.
+ *
+ * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+ * not for assist purposes.
+ */
+ @Nullable
+ public String getTextIdEntry() {
+ return mTextIdEntry;
+ }
+
+ /**
* Return additional hint text associated with the node; this is typically used with
* a node that takes user input, describing to the user what the input means.
*/
@@ -1684,6 +1700,11 @@ public class AssistStructure implements Parcelable {
}
@Override
+ public void setTextIdEntry(@NonNull String entryName) {
+ mNode.mTextIdEntry = Preconditions.checkNotNull(entryName);
+ }
+
+ @Override
public void setHint(CharSequence hint) {
getNodeText().mHint = hint != null ? hint.toString() : null;
}
@@ -2082,6 +2103,7 @@ public class AssistStructure implements Parcelable {
Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor())
+ ", bg: #" + Integer.toHexString(node.getTextBackgroundColor()));
Log.i(TAG, prefix + " Input type: " + node.getInputType());
+ Log.i(TAG, prefix + " Resource id: " + node.getTextIdEntry());
}
String webDomain = node.getWebDomain();
if (webDomain != null) {
diff --git a/android/app/backup/BackupManager.java b/android/app/backup/BackupManager.java
index 6512b98c..12f44831 100644
--- a/android/app/backup/BackupManager.java
+++ b/android/app/backup/BackupManager.java
@@ -27,6 +27,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -387,6 +388,29 @@ public class BackupManager {
}
/**
+ * Report whether the backup mechanism is currently active.
+ * When it is inactive, the device will not perform any backup operations, nor will it
+ * deliver data for restore, although clients can still safely call BackupManager methods.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BACKUP)
+ public boolean isBackupServiceActive(UserHandle user) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
+ "isBackupServiceActive");
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ return sService.isBackupServiceActive(user.getIdentifier());
+ } catch (RemoteException e) {
+ Log.e(TAG, "isBackupEnabled() couldn't connect");
+ }
+ }
+ return false;
+ }
+
+ /**
* Enable/disable data restore at application install time. When enabled, app
* installation will include an attempt to fetch the app's historical data from
* the archival restore dataset (if any). When disabled, no such attempt will
@@ -707,7 +731,6 @@ public class BackupManager {
* redirects them into main-thread actions. This serializes the backup
* progress callbacks nicely within the usual main-thread lifecycle pattern.
*/
- @SystemApi
private class BackupObserverWrapper extends IBackupObserver.Stub {
final Handler mHandler;
final BackupObserver mObserver;
diff --git a/android/app/backup/BackupManagerMonitor.java b/android/app/backup/BackupManagerMonitor.java
index ae4a98a4..a91aded1 100644
--- a/android/app/backup/BackupManagerMonitor.java
+++ b/android/app/backup/BackupManagerMonitor.java
@@ -172,6 +172,12 @@ public class BackupManagerMonitor {
public static final int LOG_EVENT_ID_NO_PACKAGES = 49;
public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50;
+ /**
+ * 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 da81d19c..266f58df 100644
--- a/android/app/backup/BackupTransport.java
+++ b/android/app/backup/BackupTransport.java
@@ -51,10 +51,40 @@ public class BackupTransport {
public static final int AGENT_UNKNOWN = -1004;
public static final int TRANSPORT_QUOTA_EXCEEDED = -1005;
+ /**
+ * Indicates that the transport cannot accept a diff backup for this package.
+ *
+ * <p>Backup manager should clear its state for this package and immediately retry a
+ * non-incremental backup. This might be used if the transport no longer has data for this
+ * package in its backing store.
+ *
+ * <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;
+
// Indicates that operation was initiated by user, not a scheduled one.
// Transport should ignore its own moratoriums for call with this flag set.
public static final int FLAG_USER_INITIATED = 1;
+ /**
+ * 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
+ */
+ public static final int FLAG_INCREMENTAL = 1 << 1;
+
+ /**
+ * 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
+ */
+ public static final int FLAG_NON_INCREMENTAL = 1 << 2;
+
IBackupTransport mBinderImpl = new TransportImpl();
public IBinder getBinder() {
@@ -231,18 +261,33 @@ public class BackupTransport {
* {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data
* is sent and recorded successfully.
*
+ * If the backup data is a diff against the previous backup then the flag {@link
+ * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup
+ * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will
+ * be set regardless of whether the backup is incremental or not.
+ *
+ * <p>If {@link BackupTransport#FLAG_INCREMENTAL} is set and the transport does not have data
+ * for this package in its storage backend then it cannot apply the incremental diff. Thus it
+ * should return {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} to indicate
+ * that backup manager should delete its state and retry the package as a non-incremental
+ * backup. Before P, or if this is a non-incremental backup, then this return code is equivalent
+ * to {@link BackupTransport#TRANSPORT_ERROR}.
+ *
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
* @param inFd Descriptor of file with data that resulted from invoking the application's
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
- * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
+ * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
+ * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
- * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
- * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
- * become lost due to inactivity purge or some other reason and needs re-initializing)
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), {@link
+ * BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED} (if the transport cannot accept
+ * an incremental backup for this package), or {@link
+ * BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has become lost due to
+ * inactivity purge or some other reason and needs re-initializing)
*/
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
return performBackup(packageInfo, inFd);
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index 7c40b4ea..cba9dcc3 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -253,6 +253,11 @@ public class JobInfo implements Parcelable {
/**
* @hide
*/
+ public static final int FLAG_IS_PREFETCH = 1 << 2;
+
+ /**
+ * @hide
+ */
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
/**
@@ -1364,6 +1369,28 @@ public class JobInfo implements Parcelable {
}
/**
+ * 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
+ * of interest to the current user.
+ * <p>
+ * The system may use this signal to relax the network constraints you
+ * originally requested, such as allowing a
+ * {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered
+ * 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.
+ */
+ public Builder setIsPrefetch(boolean isPrefetch) {
+ if (isPrefetch) {
+ mFlags |= FLAG_IS_PREFETCH;
+ } else {
+ mFlags &= (~FLAG_IS_PREFETCH);
+ }
+ return this;
+ }
+
+ /**
* Set whether or not to persist this job across device reboots.
*
* @param isPersisted True to indicate that the job will be written to
diff --git a/android/app/job/JobParameters.java b/android/app/job/JobParameters.java
index 5053dc6f..c71bf2e6 100644
--- a/android/app/job/JobParameters.java
+++ b/android/app/job/JobParameters.java
@@ -70,6 +70,7 @@ public class JobParameters implements Parcelable {
private final Network network;
private int stopReason; // Default value of stopReason is REASON_CANCELED
+ private String debugStopReason; // Human readable stop reason for debugging.
/** @hide */
public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
@@ -104,6 +105,14 @@ public class JobParameters implements Parcelable {
}
/**
+ * Reason onStopJob() was called on this job.
+ * @hide
+ */
+ public String getDebugStopReason() {
+ return debugStopReason;
+ }
+
+ /**
* @return The extras you passed in when constructing this job with
* {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
* never be null. If you did not set any extras this will be an empty bundle.
@@ -288,11 +297,13 @@ public class JobParameters implements Parcelable {
network = null;
}
stopReason = in.readInt();
+ debugStopReason = in.readString();
}
/** @hide */
- public void setStopReason(int reason) {
+ public void setStopReason(int reason, String debugStopReason) {
stopReason = reason;
+ this.debugStopReason = debugStopReason;
}
@Override
@@ -323,6 +334,7 @@ public class JobParameters implements Parcelable {
dest.writeInt(0);
}
dest.writeInt(stopReason);
+ dest.writeString(debugStopReason);
}
public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() {
diff --git a/android/app/servertransaction/ActivityLifecycleItem.java b/android/app/servertransaction/ActivityLifecycleItem.java
index 0fdc7c56..9a50a009 100644
--- a/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/android/app/servertransaction/ActivityLifecycleItem.java
@@ -17,7 +17,9 @@
package android.app.servertransaction;
import android.annotation.IntDef;
+import android.os.Parcel;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -26,6 +28,7 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
public abstract class ActivityLifecycleItem extends ClientTransactionItem {
+ private String mDescription;
@IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = {
UNDEFINED,
@@ -53,4 +56,39 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem {
/** A final lifecycle state that an activity should reach. */
@LifecycleState
public abstract int getTargetState();
+
+
+ protected ActivityLifecycleItem() {
+ }
+
+ protected ActivityLifecycleItem(Parcel in) {
+ mDescription = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mDescription);
+ }
+
+ /**
+ * Sets a description that can be retrieved later for debugging purposes.
+ * @param description Description to set.
+ * @return The {@link ActivityLifecycleItem}.
+ */
+ public ActivityLifecycleItem setDescription(String description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Retrieves description if set through {@link #setDescription(String)}.
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "target state:" + getTargetState());
+ pw.println(prefix + "description: " + mDescription);
+ }
}
diff --git a/android/app/servertransaction/ClientTransaction.java b/android/app/servertransaction/ClientTransaction.java
index 3c96f069..fc078798 100644
--- a/android/app/servertransaction/ClientTransaction.java
+++ b/android/app/servertransaction/ClientTransaction.java
@@ -24,6 +24,9 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -83,7 +86,8 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
}
/** Get the target state lifecycle request. */
- ActivityLifecycleItem getLifecycleStateRequest() {
+ @VisibleForTesting
+ public ActivityLifecycleItem getLifecycleStateRequest() {
return mLifecycleStateRequest;
}
@@ -234,4 +238,12 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
return result;
}
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "mActivityToken:" + mActivityToken.hashCode());
+ pw.println(prefix + "mLifecycleStateRequest:");
+ if (mLifecycleStateRequest != null) {
+ mLifecycleStateRequest.dump(pw, prefix + " ");
+ }
+ }
}
diff --git a/android/app/servertransaction/DestroyActivityItem.java b/android/app/servertransaction/DestroyActivityItem.java
index 83da5f33..cbcf6c75 100644
--- a/android/app/servertransaction/DestroyActivityItem.java
+++ b/android/app/servertransaction/DestroyActivityItem.java
@@ -76,12 +76,14 @@ public class DestroyActivityItem extends ActivityLifecycleItem {
/** Write to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
dest.writeBoolean(mFinished);
dest.writeInt(mConfigChanges);
}
/** Read from Parcel. */
private DestroyActivityItem(Parcel in) {
+ super(in);
mFinished = in.readBoolean();
mConfigChanges = in.readInt();
}
diff --git a/android/app/servertransaction/PauseActivityItem.java b/android/app/servertransaction/PauseActivityItem.java
index 880fef73..70a4755f 100644
--- a/android/app/servertransaction/PauseActivityItem.java
+++ b/android/app/servertransaction/PauseActivityItem.java
@@ -114,6 +114,7 @@ public class PauseActivityItem extends ActivityLifecycleItem {
/** Write to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
dest.writeBoolean(mFinished);
dest.writeBoolean(mUserLeaving);
dest.writeInt(mConfigChanges);
@@ -122,6 +123,7 @@ public class PauseActivityItem extends ActivityLifecycleItem {
/** Read from Parcel. */
private PauseActivityItem(Parcel in) {
+ super(in);
mFinished = in.readBoolean();
mUserLeaving = in.readBoolean();
mConfigChanges = in.readInt();
diff --git a/android/app/servertransaction/ResumeActivityItem.java b/android/app/servertransaction/ResumeActivityItem.java
index 9249c6e8..ed90f2cb 100644
--- a/android/app/servertransaction/ResumeActivityItem.java
+++ b/android/app/servertransaction/ResumeActivityItem.java
@@ -113,6 +113,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem {
/** Write to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
dest.writeInt(mProcState);
dest.writeBoolean(mUpdateProcState);
dest.writeBoolean(mIsForward);
@@ -120,6 +121,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem {
/** Read from Parcel. */
private ResumeActivityItem(Parcel in) {
+ super(in);
mProcState = in.readInt();
mUpdateProcState = in.readBoolean();
mIsForward = in.readBoolean();
diff --git a/android/app/servertransaction/StopActivityItem.java b/android/app/servertransaction/StopActivityItem.java
index 5c5c3041..b814d1ae 100644
--- a/android/app/servertransaction/StopActivityItem.java
+++ b/android/app/servertransaction/StopActivityItem.java
@@ -83,12 +83,14 @@ public class StopActivityItem extends ActivityLifecycleItem {
/** Write to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
dest.writeBoolean(mShowWindow);
dest.writeInt(mConfigChanges);
}
/** Read from Parcel. */
private StopActivityItem(Parcel in) {
+ super(in);
mShowWindow = in.readBoolean();
mConfigChanges = in.readInt();
}
diff --git a/android/app/servertransaction/TransactionExecutor.java b/android/app/servertransaction/TransactionExecutor.java
index 5b0ea6b1..78b393a8 100644
--- a/android/app/servertransaction/TransactionExecutor.java
+++ b/android/app/servertransaction/TransactionExecutor.java
@@ -33,6 +33,8 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.List;
/**
@@ -122,6 +124,21 @@ public class TransactionExecutor {
final IBinder token = transaction.getActivityToken();
final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
+ // TODO(b/71506345): Remove once root cause is found.
+ if (r == null) {
+ final StringWriter stringWriter = new StringWriter();
+ final PrintWriter pw = new PrintWriter(stringWriter);
+ final String prefix = " ";
+
+ pw.println("Lifecycle transaction does not have valid ActivityClientRecord.");
+ pw.println("Transaction:");
+ transaction.dump(pw, prefix);
+ pw.println("Executor:");
+ dump(pw, prefix);
+
+ Slog.wtf(TAG, stringWriter.toString());
+ }
+
// Cycle to the state right before the final requested state.
cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */);
@@ -245,4 +262,9 @@ public class TransactionExecutor {
private static void log(String message) {
if (DEBUG_RESOLVER) Slog.d(TAG, message);
}
+
+ private void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "mTransactionHandler:");
+ mTransactionHandler.dump(pw, prefix + " ");
+ }
}
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index 5c7f6741..5808f8b5 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -21,12 +21,10 @@ import android.annotation.Nullable;
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
-import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
-import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -67,6 +65,7 @@ public final class Slice implements Parcelable {
HINT_TOGGLE,
HINT_HORIZONTAL,
HINT_PARTIAL,
+ HINT_SEE_MORE
})
@Retention(RetentionPolicy.SOURCE)
public @interface SliceHint {}
@@ -151,7 +150,19 @@ public final class Slice implements Parcelable {
* 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.
+ */
+ public static final String HINT_CALLER_NEEDED = "caller_needed";
/**
* Key to retrieve an extra added to an intent when a control is changed.
*/
@@ -184,6 +195,10 @@ public final class Slice implements Parcelable {
* Subtype to tag an item representing priority.
*/
public static final String SUBTYPE_PRIORITY = "priority";
+ /**
+ * Subtype to tag an item to use as a content description.
+ */
+ public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
@@ -415,28 +430,6 @@ public final class Slice implements Parcelable {
* Add a color to the slice being constructed
* @param subType Optional template-specific type information
* @see {@link SliceItem#getSubType()}
- * @deprecated will be removed once supportlib updates
- */
- public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, SliceItem.FORMAT_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 will be removed once supportlib updates
- */
- public Builder addColor(int color, @Nullable String subType,
- @SliceHint List<String> hints) {
- return addColor(color, subType, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Add a color to the slice being constructed
- * @param subType Optional template-specific type information
- * @see {@link SliceItem#getSubType()}
*/
public Builder addInt(int value, @Nullable String subType, @SliceHint String... hints) {
mItems.add(new SliceItem(value, SliceItem.FORMAT_INT, subType, hints));
@@ -549,16 +542,11 @@ public final class Slice implements Parcelable {
}
/**
- * Turns a slice Uri into slice content.
- *
- * @param resolver ContentResolver to be used.
- * @param uri The URI to a slice provider
- * @param supportedSpecs List of supported specs.
- * @return The Slice provided by the app or null if none is given.
- * @see Slice
- */
- public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri,
- List<SliceSpec> supportedSpecs) {
+ * @deprecated TO BE REMOVED.
+ */
+ @Deprecated
+ public static @Nullable Slice bindSlice(ContentResolver resolver,
+ @NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider provider = resolver.acquireProvider(uri);
if (provider == null) {
@@ -586,60 +574,11 @@ public final class Slice implements Parcelable {
}
/**
- * Turns a slice intent into slice content. Expects an explicit intent. If there is no
- * {@link ContentProvider} associated with the given intent this will throw
- * {@link IllegalArgumentException}.
- *
- * @param context The context to use.
- * @param intent The intent associated with a slice.
- * @param supportedSpecs List of supported specs.
- * @return The Slice provided by the app or null if none is given.
- * @see Slice
- * @see SliceProvider#onMapIntentToUri(Intent)
- * @see Intent
+ * @deprecated TO BE REMOVED.
*/
+ @Deprecated
public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
- List<SliceSpec> supportedSpecs) {
- Preconditions.checkNotNull(intent, "intent");
- Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
- "Slice intent must be explicit " + intent);
- ContentResolver resolver = context.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 bindSlice(resolver, intentData, supportedSpecs);
- }
- // Otherwise ask the app
- List<ResolveInfo> providers =
- context.getPackageManager().queryIntentContentProviders(intent, 0);
- if (providers == null) {
- throw new IllegalArgumentException("Unable to resolve intent " + intent);
- }
- 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 {
- Bundle extras = new Bundle();
- extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
- extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
- new ArrayList<>(supportedSpecs));
- final Bundle res = provider.call(resolver.getPackageName(),
- SliceProvider.METHOD_MAP_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;
- } finally {
- resolver.releaseProvider(provider);
- }
+ @NonNull List<SliceSpec> supportedSpecs) {
+ return context.getSystemService(SliceManager.class).bindSlice(intent, supportedSpecs);
}
}
diff --git a/android/app/slice/SliceItem.java b/android/app/slice/SliceItem.java
index bcfd413f..9eb2bb89 100644
--- a/android/app/slice/SliceItem.java
+++ b/android/app/slice/SliceItem.java
@@ -98,11 +98,6 @@ public final class SliceItem implements Parcelable {
*/
public static final String FORMAT_INT = "int";
/**
- * A {@link SliceItem} that contains an int.
- * @deprecated to be removed
- */
- public static final String FORMAT_COLOR = "color";
- /**
* A {@link SliceItem} that contains a timestamp.
*/
public static final String FORMAT_TIMESTAMP = "timestamp";
@@ -231,13 +226,6 @@ public final class SliceItem implements Parcelable {
}
/**
- * @deprecated to be removed.
- */
- public int getColor() {
- return (Integer) mObj;
- }
-
- /**
* @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
*/
public Slice getSlice() {
diff --git a/android/app/slice/SliceManager.java b/android/app/slice/SliceManager.java
index 0c5f225d..2fa9d8e0 100644
--- a/android/app/slice/SliceManager.java
+++ b/android/app/slice/SliceManager.java
@@ -16,18 +16,31 @@
package android.app.slice;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Pair;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
@@ -39,12 +52,36 @@ import java.util.concurrent.Executor;
@SystemService(Context.SLICE_SERVICE)
public class SliceManager {
+ private static final String TAG = "SliceManager";
+
+ /**
+ * @hide
+ */
+ public static final String ACTION_REQUEST_SLICE_PERMISSION =
+ "android.intent.action.REQUEST_SLICE_PERMISSION";
+
private final ISliceManager mService;
private final Context mContext;
private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup =
new ArrayMap<>();
/**
+ * Permission denied.
+ * @hide
+ */
+ public static final int PERMISSION_DENIED = -1;
+ /**
+ * Permission granted.
+ * @hide
+ */
+ public static final int PERMISSION_GRANTED = 0;
+ /**
+ * Permission just granted by the user, and should be granted uri permission as well.
+ * @hide
+ */
+ public static final int PERMISSION_USER_GRANTED = 1;
+
+ /**
* @hide
*/
public SliceManager(Context context, Handler handler) throws ServiceNotFoundException {
@@ -54,21 +91,21 @@ public class SliceManager {
}
/**
- * 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)
+ * @deprecated TO BE REMOVED.
*/
+ @Deprecated
public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
@NonNull List<SliceSpec> specs) {
- registerSliceCallback(uri, callback, specs, Handler.getMain());
+ 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);
}
/**
@@ -84,19 +121,9 @@ public class SliceManager {
* @param specs The list of supported {@link SliceSpec}s of the callback.
* @see SliceProvider#onSlicePinned(Uri)
*/
- public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
- @NonNull List<SliceSpec> specs, Handler handler) {
- try {
- mService.addSliceListener(uri, mContext.getPackageName(),
- getListener(uri, callback, new ISliceListener.Stub() {
- @Override
- public void onSliceUpdated(Slice s) throws RemoteException {
- handler.post(() -> callback.onSliceUpdated(s));
- }
- }), specs.toArray(new SliceSpec[specs.size()]));
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ public void registerSliceCallback(@NonNull Uri uri, @NonNull List<SliceSpec> specs,
+ @NonNull SliceCallback callback) {
+ registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback);
}
/**
@@ -112,8 +139,8 @@ public class SliceManager {
* @param specs The list of supported {@link SliceSpec}s of the callback.
* @see SliceProvider#onSlicePinned(Uri)
*/
- public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback,
- @NonNull List<SliceSpec> specs, Executor executor) {
+ 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() {
@@ -224,6 +251,165 @@ public class SliceManager {
}
/**
+ * Obtains a list of slices that are descendants of the specified Uri.
+ * <p>
+ * Not all slice providers will implement this functionality, in which case,
+ * an empty collection will be returned.
+ *
+ * @param uri The uri to look for descendants under.
+ * @return All slices within the space.
+ * @see SliceProvider#onGetSliceDescendants(Uri)
+ */
+ public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) {
+ ContentResolver resolver = mContext.getContentResolver();
+ IContentProvider provider = resolver.acquireProvider(uri);
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+ final Bundle res = provider.call(resolver.getPackageName(),
+ 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();
+ }
+
+ /**
+ * Turns a slice Uri into slice content.
+ *
+ * @param uri The URI to a slice provider
+ * @param supportedSpecs List of supported specs.
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ */
+ 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 {
+ 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);
+ Bundle.setDefusable(res, true);
+ 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;
+ } finally {
+ resolver.releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Turns a slice intent into slice content. Expects an explicit intent. If there is no
+ * {@link android.content.ContentProvider} associated with the given intent this will throw
+ * {@link IllegalArgumentException}.
+ *
+ * @param intent The intent associated with a slice.
+ * @param supportedSpecs List of supported specs.
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ * @see SliceProvider#onMapIntentToUri(Intent)
+ * @see Intent
+ */
+ 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);
+ 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 bindSlice(intentData, supportedSpecs);
+ }
+ // Otherwise ask the app
+ List<ResolveInfo> providers =
+ mContext.getPackageManager().queryIntentContentProviders(intent, 0);
+ if (providers == null) {
+ throw new IllegalArgumentException("Unable to resolve intent " + intent);
+ }
+ 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 {
+ 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);
+ 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;
+ } finally {
+ resolver.releaseProvider(provider);
+ }
+ }
+
+ /**
+ * 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) {
+ try {
+ if (pkg == null) {
+ throw new SecurityException("No pkg specified");
+ }
+ int result = mService.checkSlicePermission(uri, pkg, pid, uid);
+ if (result == PERMISSION_DENIED) {
+ throw new SecurityException("User " + uid + " does not have slice permission for "
+ + uri + ".");
+ }
+ if (result == PERMISSION_USER_GRANTED) {
+ // We just had a user grant of this permission and need to grant this to the app
+ // permanently.
+ mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(),
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by SystemUI to grant a slice permission after a dialog is shown.
+ * @hide
+ */
+ public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) {
+ try {
+ mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Class that listens to changes in {@link Slice}s.
*/
public interface SliceCallback {
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 8483931c..00e8ccad 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -15,13 +15,19 @@
*/
package android.app.slice;
-import android.Manifest.permission;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
@@ -36,6 +42,9 @@ import android.os.StrictMode.ThreadPolicy;
import android.os.UserHandle;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -80,7 +89,7 @@ import java.util.concurrent.CountDownLatch;
*/
public abstract class SliceProvider extends ContentProvider {
/**
- * This is the Android platform's MIME type for a slice: URI
+ * This is the Android platform's MIME type for a URI
* containing a slice implemented through {@link SliceProvider}.
*/
public static final String SLICE_TYPE = "vnd.android.slice";
@@ -113,14 +122,53 @@ public abstract class SliceProvider extends ContentProvider {
/**
* @hide
*/
+ public static final String METHOD_GET_DESCENDANTS = "get_descendants";
+ /**
+ * @hide
+ */
public static final String EXTRA_INTENT = "slice_intent";
/**
* @hide
*/
public static final String EXTRA_SLICE = "slice";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_PKG = "pkg";
+ /**
+ * @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 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)}.
+ */
+ public final @Nullable String getBindingPackage() {
+ return mBindingPkg;
+ }
+
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ super.attachInfo(context, info);
+ mSliceManager = context.getSystemService(SliceManager.class);
+ }
+
/**
* Implemented to create a slice. Will be called on the main thread.
* <p>
@@ -139,14 +187,6 @@ public abstract class SliceProvider extends ContentProvider {
* @see {@link Slice#HINT_PARTIAL}
*/
public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
- return onBindSlice(sliceUri);
- }
-
- /**
- * @deprecated migrating to {@link #onBindSlice(Uri, List)}
- */
- @Deprecated
- public Slice onBindSlice(Uri sliceUri) {
return null;
}
@@ -183,6 +223,20 @@ public abstract class SliceProvider extends ContentProvider {
}
/**
+ * Obtains a list of slices that are descendants of the specified Uri.
+ * <p>
+ * Implementing this is optional for a SliceProvider, but does provide a good
+ * discovery mechanism for finding slice Uris.
+ *
+ * @param uri The uri to look for descendants under.
+ * @return All slices within the space.
+ * @see SliceManager#getSliceDescendants(Uri)
+ */
+ public @NonNull Collection<Uri> onGetSliceDescendants(@NonNull Uri uri) {
+ return Collections.emptyList();
+ }
+
+ /**
* This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
* In that case, this method can be called and is expected to return a non-null Uri representing
* a slice. Otherwise this will throw {@link UnsupportedOperationException}.
@@ -244,56 +298,74 @@ public abstract class SliceProvider extends ContentProvider {
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
- Uri uri = extras.getParcelable(EXTRA_BIND_URI);
- if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
- getContext().enforceUriPermission(uri, permission.BIND_SLICE,
- permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
- "Slice binding requires the permission BIND_SLICE");
- }
+ Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
- Slice s = handleBindSlice(uri, supportedSpecs);
+ 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);
Bundle b = new Bundle();
b.putParcelable(EXTRA_SLICE, s);
return b;
} else if (method.equals(METHOD_MAP_INTENT)) {
- getContext().enforceCallingPermission(permission.BIND_SLICE,
- "Slice binding requires the permission BIND_SLICE");
Intent intent = extras.getParcelable(EXTRA_INTENT);
if (intent == null) return null;
Uri uri = onMapIntentToUri(intent);
List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
Bundle b = new Bundle();
if (uri != null) {
- Slice s = handleBindSlice(uri, supportedSpecs);
+ Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage());
b.putParcelable(EXTRA_SLICE, s);
} else {
b.putParcelable(EXTRA_SLICE, null);
}
return b;
} else if (method.equals(METHOD_PIN)) {
- Uri uri = extras.getParcelable(EXTRA_BIND_URI);
- if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
- getContext().enforceUriPermission(uri, permission.BIND_SLICE,
- permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
- "Slice binding requires the permission BIND_SLICE");
+ Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system can pin/unpin slices");
}
handlePinSlice(uri);
} else if (method.equals(METHOD_UNPIN)) {
- Uri uri = extras.getParcelable(EXTRA_BIND_URI);
- if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
- getContext().enforceUriPermission(uri, permission.BIND_SLICE,
- permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
- "Slice binding requires the permission BIND_SLICE");
+ Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system can pin/unpin slices");
}
handleUnpinSlice(uri);
+ } else if (method.equals(METHOD_GET_DESCENDANTS)) {
+ Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
+ Bundle b = new Bundle();
+ b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS,
+ new ArrayList<>(handleGetDescendants(uri)));
+ return b;
}
return super.call(method, arg, extras);
}
+ private Collection<Uri> handleGetDescendants(Uri uri) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ 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);
+ }
+ }
+ }
+
private void handlePinSlice(Uri sliceUri) {
if (Looper.myLooper() == Looper.getMainLooper()) {
onSlicePinned(sliceUri);
@@ -328,14 +400,27 @@ public abstract class SliceProvider extends ContentProvider {
}
}
- private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
+ private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,
+ String callingPkg) {
+ // 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);
+ }
+ }
if (Looper.myLooper() == Looper.getMainLooper()) {
- return onBindSliceStrict(sliceUri, supportedSpecs);
+ return onBindSliceStrict(sliceUri, supportedSpecs, pkg);
} else {
CountDownLatch latch = new CountDownLatch(1);
Slice[] output = new Slice[1];
Handler.getMain().post(() -> {
- output[0] = onBindSliceStrict(sliceUri, supportedSpecs);
+ output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg);
latch.countDown();
});
try {
@@ -347,15 +432,66 @@ public abstract class SliceProvider extends ContentProvider {
}
}
- private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
+ /**
+ * @hide
+ */
+ public static 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();
+ }
+
+ /**
+ * @hide
+ */
+ public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
+ String callingPackage) {
+ Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
+ intent.setComponent(new ComponentName("com.android.systemui",
+ "com.android.systemui.SlicePermissionActivity"));
+ intent.putExtra(EXTRA_BIND_URI, sliceUri);
+ intent.putExtra(EXTRA_PKG, callingPackage);
+ intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
+ // Unique pending intent.
+ intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
+ .build());
+
+ return PendingIntent.getActivity(context, 0, intent, 0);
+ }
+
+ /**
+ * @hide
+ */
+ public static CharSequence getPermissionString(Context context, String callingPackage) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ return context.getString(
+ com.android.internal.R.string.slices_permission_request,
+ pm.getApplicationInfo(callingPackage, 0).loadLabel(pm),
+ context.getApplicationInfo().loadLabel(pm));
+ } catch (NameNotFoundException e) {
+ // This shouldn't be possible since the caller is verified.
+ throw new RuntimeException("Unknown calling app", e);
+ }
+ }
+
+ private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs,
+ String callingPackage) {
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);
}
}
diff --git a/android/app/timezone/RulesState.java b/android/app/timezone/RulesState.java
index 16309fab..e86d348a 100644
--- a/android/app/timezone/RulesState.java
+++ b/android/app/timezone/RulesState.java
@@ -126,9 +126,6 @@ public final class RulesState implements Parcelable {
mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */,
"stagedDistroRulesVersion", stagedDistroRulesVersion);
- if (operationInProgress && distroStatus != DISTRO_STATUS_UNKNOWN) {
- throw new IllegalArgumentException("distroInstalled != DISTRO_STATUS_UNKNOWN");
- }
this.mDistroStatus = validateDistroStatus(distroStatus);
this.mInstalledDistroRulesVersion = validateConditionalNull(
mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */,
diff --git a/android/app/trust/TrustManager.java b/android/app/trust/TrustManager.java
index 852cb8e0..8ab0b706 100644
--- a/android/app/trust/TrustManager.java
+++ b/android/app/trust/TrustManager.java
@@ -36,9 +36,11 @@ public class TrustManager {
private static final int MSG_TRUST_CHANGED = 1;
private static final int MSG_TRUST_MANAGED_CHANGED = 2;
+ private static final int MSG_TRUST_ERROR = 3;
private static final String TAG = "TrustManager";
private static final String DATA_FLAGS = "initiatedByUser";
+ private static final String DATA_MESSAGE = "message";
private final ITrustManager mService;
private final ArrayMap<TrustListener, ITrustListener> mTrustListeners;
@@ -148,6 +150,13 @@ public class TrustManager {
mHandler.obtainMessage(MSG_TRUST_MANAGED_CHANGED, (managed ? 1 : 0), userId,
trustListener).sendToTarget();
}
+
+ @Override
+ public void onTrustError(CharSequence message) {
+ Message m = mHandler.obtainMessage(MSG_TRUST_ERROR);
+ m.getData().putCharSequence(DATA_MESSAGE, message);
+ m.sendToTarget();
+ }
};
mService.registerTrustListener(iTrustListener);
mTrustListeners.put(trustListener, iTrustListener);
@@ -221,6 +230,10 @@ public class TrustManager {
break;
case MSG_TRUST_MANAGED_CHANGED:
((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2);
+ break;
+ case MSG_TRUST_ERROR:
+ final CharSequence message = msg.peekData().getCharSequence(DATA_MESSAGE);
+ ((TrustListener)msg.obj).onTrustError(message);
}
}
};
@@ -229,9 +242,9 @@ public class TrustManager {
/**
* Reports that the trust state has changed.
- * @param enabled if true, the system believes the environment to be trusted.
- * @param userId the user, for which the trust changed.
- * @param flags flags specified by the trust agent when granting trust. See
+ * @param enabled If true, the system believes the environment to be trusted.
+ * @param userId The user, for which the trust changed.
+ * @param flags Flags specified by the trust agent when granting trust. See
* {@link android.service.trust.TrustAgentService#grantTrust(CharSequence, long, int)
* TrustAgentService.grantTrust(CharSequence, long, int)}.
*/
@@ -239,9 +252,15 @@ public class TrustManager {
/**
* Reports that whether trust is managed has changed
- * @param enabled if true, at least one trust agent is managing trust.
- * @param userId the user, for which the state changed.
+ * @param enabled If true, at least one trust agent is managing trust.
+ * @param userId The user, for which the state changed.
*/
void onTrustManagedChanged(boolean enabled, int userId);
+
+ /**
+ * Reports that an error happened on a TrustAgentService.
+ * @param message A message that should be displayed on the UI.
+ */
+ void onTrustError(CharSequence message);
}
}
diff --git a/android/app/usage/NetworkStats.java b/android/app/usage/NetworkStats.java
index 2e44a630..da36157d 100644
--- a/android/app/usage/NetworkStats.java
+++ b/android/app/usage/NetworkStats.java
@@ -227,6 +227,30 @@ public final class NetworkStats implements AutoCloseable {
*/
public static final int ROAMING_YES = 0x2;
+ /** @hide */
+ @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = {
+ DEFAULT_NETWORK_ALL,
+ DEFAULT_NETWORK_NO,
+ DEFAULT_NETWORK_YES
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DefaultNetwork {}
+
+ /**
+ * Combined usage for this network regardless of whether it was the active default network.
+ */
+ public static final int DEFAULT_NETWORK_ALL = -1;
+
+ /**
+ * Usage that occurs while this network is not the active default network.
+ */
+ public static final int DEFAULT_NETWORK_NO = 0x1;
+
+ /**
+ * Usage that occurs while this network is the active default network.
+ */
+ public static final int DEFAULT_NETWORK_YES = 0x2;
+
/**
* Special TAG value for total data across all tags
*/
@@ -235,6 +259,7 @@ public final class NetworkStats implements AutoCloseable {
private int mUid;
private int mTag;
private int mState;
+ private int mDefaultNetwork;
private int mMetered;
private int mRoaming;
private long mBeginTimeStamp;
@@ -286,6 +311,15 @@ public final class NetworkStats implements AutoCloseable {
return 0;
}
+ private static @DefaultNetwork int convertDefaultNetwork(int defaultNetwork) {
+ switch (defaultNetwork) {
+ case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL;
+ case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO;
+ case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES;
+ }
+ return 0;
+ }
+
public Bucket() {
}
@@ -351,6 +385,21 @@ public final class NetworkStats implements AutoCloseable {
}
/**
+ * Default network state. One of the following values:<p/>
+ * <ul>
+ * <li>{@link #DEFAULT_NETWORK_ALL}</li>
+ * <li>{@link #DEFAULT_NETWORK_NO}</li>
+ * <li>{@link #DEFAULT_NETWORK_YES}</li>
+ * </ul>
+ * <p>Indicates whether the network usage occurred on the system default network for this
+ * type of traffic, or whether the application chose to send this traffic on a network that
+ * was not the one selected by the system.
+ */
+ public @DefaultNetwork int getDefaultNetwork() {
+ return mDefaultNetwork;
+ }
+
+ /**
* Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see
* {@link java.lang.System#currentTimeMillis}.
* @return Start of interval.
@@ -551,6 +600,8 @@ public final class NetworkStats implements AutoCloseable {
bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag);
bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
+ bucketOut.mDefaultNetwork = Bucket.convertDefaultNetwork(
+ mRecycledSummaryEntry.defaultNetwork);
bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered);
bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming);
bucketOut.mBeginTimeStamp = mStartTimeStamp;
@@ -600,6 +651,7 @@ public final class NetworkStats implements AutoCloseable {
bucketOut.mUid = Bucket.convertUid(getUid());
bucketOut.mTag = Bucket.convertTag(mTag);
bucketOut.mState = Bucket.STATE_ALL;
+ bucketOut.mDefaultNetwork = Bucket.DEFAULT_NETWORK_ALL;
bucketOut.mMetered = Bucket.METERED_ALL;
bucketOut.mRoaming = Bucket.ROAMING_ALL;
bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
diff --git a/android/app/usage/NetworkStatsManager.java b/android/app/usage/NetworkStatsManager.java
index 853b0033..5576e86e 100644
--- a/android/app/usage/NetworkStatsManager.java
+++ b/android/app/usage/NetworkStatsManager.java
@@ -60,10 +60,11 @@ import android.util.Log;
* {@link #queryDetailsForUid} <p />
* {@link #queryDetails} <p />
* These queries do not aggregate over time but do aggregate over state, metered and roaming.
- * Therefore there can be multiple buckets for a particular key but all Bucket's state is going to
- * be {@link NetworkStats.Bucket#STATE_ALL}, all Bucket's metered is going to be
- * {@link NetworkStats.Bucket#METERED_ALL}, and all Bucket's roaming is going to be
- * {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * Therefore there can be multiple buckets for a particular key. However, all Buckets will have
+ * {@code state} {@link NetworkStats.Bucket#STATE_ALL},
+ * {@code defaultNetwork} {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * {@code metered } {@link NetworkStats.Bucket#METERED_ALL},
+ * {@code roaming} {@link NetworkStats.Bucket#ROAMING_ALL}.
* <p />
* <b>NOTE:</b> Calling {@link #querySummaryForDevice} or accessing stats for apps other than the
* calling app requires the permission {@link android.Manifest.permission#PACKAGE_USAGE_STATS},
@@ -130,13 +131,26 @@ public class NetworkStatsManager {
}
}
+ /** @hide */
+ public Bucket querySummaryForDevice(NetworkTemplate template,
+ long startTime, long endTime) throws SecurityException, RemoteException {
+ Bucket bucket = null;
+ NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime);
+ bucket = stats.getDeviceSummaryForNetwork();
+
+ stats.close();
+ return bucket;
+ }
+
/**
* Query network usage statistics summaries. Result is summarised data usage for the whole
* device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and
* roaming. This means the bucket's start and end timestamp are going to be the same as the
* 'startTime' and 'endTime' parameters. State is going to be
* {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL},
- * tag {@link NetworkStats.Bucket#TAG_NONE}, metered {@link NetworkStats.Bucket#METERED_ALL},
+ * tag {@link NetworkStats.Bucket#TAG_NONE},
+ * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+ * metered {@link NetworkStats.Bucket#METERED_ALL},
* and roaming {@link NetworkStats.Bucket#ROAMING_ALL}.
*
* @param networkType As defined in {@link ConnectivityManager}, e.g.
@@ -160,12 +174,7 @@ public class NetworkStatsManager {
return null;
}
- Bucket bucket = null;
- NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime);
- bucket = stats.getDeviceSummaryForNetwork();
-
- stats.close();
- return bucket;
+ return querySummaryForDevice(template, startTime, endTime);
}
/**
@@ -209,10 +218,10 @@ public class NetworkStatsManager {
/**
* Query network usage statistics summaries. Result filtered to include only uids belonging to
* calling user. Result is aggregated over time, hence all buckets will have the same start and
- * end timestamps. Not aggregated over state, uid, metered, or roaming. This means buckets'
- * start and end timestamps are going to be the same as the 'startTime' and 'endTime'
- * parameters. State, uid, metered, and roaming are going to vary, and tag is going to be the
- * same.
+ * end timestamps. Not aggregated over state, uid, default network, metered, or roaming. This
+ * means buckets' start and end timestamps are going to be the same as the 'startTime' and
+ * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to
+ * be the same.
*
* @param networkType As defined in {@link ConnectivityManager}, e.g.
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
@@ -258,9 +267,10 @@ public class NetworkStatsManager {
* 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. metered is going to be
- * {@link NetworkStats.Bucket#METERED_ALL}, and roaming is going to be
- * {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * same as the 'uid' parameter and tag the same as 'tag' 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}.
* <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
* interpolate across partial buckets. Since bucket length is in the order of hours, this
* method cannot be used to measure data usage on a fine grained time scale.
@@ -301,9 +311,10 @@ public class NetworkStatsManager {
* metered, nor roaming. 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 will vary,
- * tag {@link NetworkStats.Bucket#TAG_NONE}, metered is going to be
- * {@link NetworkStats.Bucket#METERED_ALL}, and roaming is going to be
- * {@link NetworkStats.Bucket#ROAMING_ALL}.
+ * tag {@link NetworkStats.Bucket#TAG_NONE},
+ * default network 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}.
* <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
* interpolate across partial buckets. Since bucket length is in the order of hours, this
* method cannot be used to measure data usage on a fine grained time scale.
@@ -335,6 +346,37 @@ public class NetworkStatsManager {
return result;
}
+ /** @hide */
+ public void registerUsageCallback(NetworkTemplate template, int networkType,
+ long thresholdBytes, UsageCallback callback, @Nullable Handler handler) {
+ checkNotNull(callback, "UsageCallback cannot be null");
+
+ final Looper looper;
+ if (handler == null) {
+ looper = Looper.myLooper();
+ } else {
+ looper = handler.getLooper();
+ }
+
+ DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
+ template, thresholdBytes);
+ try {
+ CallbackHandler callbackHandler = new CallbackHandler(looper, networkType,
+ template.getSubscriberId(), callback);
+ callback.request = mService.registerUsageCallback(
+ mContext.getOpPackageName(), request, new Messenger(callbackHandler),
+ new Binder());
+ if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request);
+
+ if (callback.request == null) {
+ Log.e(TAG, "Request from callback is null; should not happen");
+ }
+ } catch (RemoteException e) {
+ if (DBG) Log.d(TAG, "Remote exception when registering callback");
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Registers to receive notifications about data usage on specified networks.
*
@@ -363,15 +405,7 @@ public class NetworkStatsManager {
*/
public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes,
UsageCallback callback, @Nullable Handler handler) {
- checkNotNull(callback, "UsageCallback cannot be null");
-
- final Looper looper;
- if (handler == null) {
- looper = Looper.myLooper();
- } else {
- looper = handler.getLooper();
- }
-
+ NetworkTemplate template = createTemplate(networkType, subscriberId);
if (DBG) {
Log.d(TAG, "registerUsageCallback called with: {"
+ " networkType=" + networkType
@@ -379,25 +413,7 @@ public class NetworkStatsManager {
+ " thresholdBytes=" + thresholdBytes
+ " }");
}
-
- NetworkTemplate template = createTemplate(networkType, subscriberId);
- DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
- template, thresholdBytes);
- try {
- CallbackHandler callbackHandler = new CallbackHandler(looper, networkType,
- subscriberId, callback);
- callback.request = mService.registerUsageCallback(
- mContext.getOpPackageName(), request, new Messenger(callbackHandler),
- new Binder());
- if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request);
-
- if (callback.request == null) {
- Log.e(TAG, "Request from callback is null; should not happen");
- }
- } catch (RemoteException e) {
- if (DBG) Log.d(TAG, "Remote exception when registering callback");
- throw e.rethrowFromSystemServer();
- }
+ registerUsageCallback(template, networkType, thresholdBytes, callback, handler);
}
/**
diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java
index f04e9074..edb992bd 100644
--- a/android/app/usage/UsageEvents.java
+++ b/android/app/usage/UsageEvents.java
@@ -106,6 +106,12 @@ public final class UsageEvents implements Parcelable {
*/
public static final int NOTIFICATION_SEEN = 10;
+ /**
+ * An event type denoting a change in App Standby Bucket.
+ * @hide
+ */
+ public static final int STANDBY_BUCKET_CHANGED = 11;
+
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
@@ -170,6 +176,13 @@ public final class UsageEvents implements Parcelable {
*/
public String[] mContentAnnotations;
+ /**
+ * The app standby bucket assigned.
+ * Only present for {@link #STANDBY_BUCKET_CHANGED} event types
+ * {@hide}
+ */
+ public int mBucket;
+
/** @hide */
@EventFlags
public int mFlags;
@@ -189,6 +202,7 @@ public final class UsageEvents implements Parcelable {
mContentType = orig.mContentType;
mContentAnnotations = orig.mContentAnnotations;
mFlags = orig.mFlags;
+ mBucket = orig.mBucket;
}
/**
@@ -399,6 +413,9 @@ public final class UsageEvents implements Parcelable {
p.writeString(event.mContentType);
p.writeStringArray(event.mContentAnnotations);
break;
+ case Event.STANDBY_BUCKET_CHANGED:
+ p.writeInt(event.mBucket);
+ break;
}
}
@@ -442,6 +459,9 @@ public final class UsageEvents implements Parcelable {
eventOut.mContentType = p.readString();
eventOut.mContentAnnotations = p.createStringArray();
break;
+ case Event.STANDBY_BUCKET_CHANGED:
+ eventOut.mBucket = p.readInt();
+ break;
}
}
diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java
index 4b4fe72f..bd978e3d 100644
--- a/android/app/usage/UsageStatsManagerInternal.java
+++ b/android/app/usage/UsageStatsManagerInternal.java
@@ -16,11 +16,13 @@
package android.app.usage;
+import android.annotation.UserIdInt;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
import java.util.List;
+import java.util.Set;
/**
* UsageStatsManager local system service interface.
@@ -37,7 +39,7 @@ public abstract class UsageStatsManagerInternal {
* @param eventType The event that occurred. Valid values can be found at
* {@link UsageEvents}
*/
- public abstract void reportEvent(ComponentName component, int userId, int eventType);
+ public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType);
/**
* Reports an event to the UsageStatsManager.
@@ -47,14 +49,14 @@ public abstract class UsageStatsManagerInternal {
* @param eventType The event that occurred. Valid values can be found at
* {@link UsageEvents}
*/
- public abstract void reportEvent(String packageName, int userId, int eventType);
+ public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType);
/**
* Reports a configuration change to the UsageStatsManager.
*
* @param config The new device configuration.
*/
- public abstract void reportConfigurationChange(Configuration config, int userId);
+ public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId);
/**
* Reports that an action equivalent to a ShortcutInfo is taken by the user.
@@ -65,7 +67,8 @@ public abstract class UsageStatsManagerInternal {
*
* @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
*/
- public abstract void reportShortcutUsage(String packageName, String shortcutId, int userId);
+ public abstract void reportShortcutUsage(String packageName, String shortcutId,
+ @UserIdInt int userId);
/**
* Reports that a content provider has been accessed by a foreground app.
@@ -73,7 +76,8 @@ public abstract class UsageStatsManagerInternal {
* @param pkgName The package name of the content provider
* @param userId The user in which the content provider was accessed.
*/
- public abstract void reportContentProviderUsage(String name, String pkgName, int userId);
+ public abstract void reportContentProviderUsage(String name, String pkgName,
+ @UserIdInt int userId);
/**
* Prepares the UsageStatsService for shutdown.
@@ -89,7 +93,7 @@ public abstract class UsageStatsManagerInternal {
* @param userId
* @return
*/
- public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
+ public abstract boolean isAppIdle(String packageName, int uidForAppId, @UserIdInt int userId);
/**
* Returns the app standby bucket that the app is currently in. This accessor does
@@ -101,15 +105,15 @@ public abstract class UsageStatsManagerInternal {
* @return the AppStandby bucket code the app currently resides in. If the app is
* unknown in the given user, STANDBY_BUCKET_NEVER is returned.
*/
- @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, int userId,
- long nowElapsed);
+ @StandbyBuckets public abstract int getAppStandbyBucket(String packageName,
+ @UserIdInt int userId, long nowElapsed);
/**
* Returns all of the uids for a given user where all packages associating with that uid
* are in the app idle state -- there are no associated apps that are not idle. This means
* all of the returned uids can be safely considered app idle.
*/
- public abstract int[] getIdleUidsForUser(int userId);
+ public abstract int[] getIdleUidsForUser(@UserIdInt int userId);
/**
* @return True if currently app idle parole mode is on. This means all idle apps are allow to
@@ -134,8 +138,8 @@ public abstract class UsageStatsManagerInternal {
public static abstract class AppIdleStateChangeListener {
/** Callback to inform listeners that the idle state has changed to a new bucket. */
- public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
- int bucket);
+ public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
+ boolean idle, int bucket);
/**
* Callback to inform listeners that the parole state has changed. This means apps are
@@ -144,10 +148,38 @@ public abstract class UsageStatsManagerInternal {
public abstract void onParoleStateChanged(boolean isParoleOn);
}
- /* Backup/Restore API */
- public abstract byte[] getBackupPayload(int user, String key);
+ /** Backup/Restore API */
+ public abstract byte[] getBackupPayload(@UserIdInt int userId, String key);
- public abstract void applyRestoredPayload(int user, String key, byte[] payload);
+ /**
+ * ?
+ * @param userId
+ * @param key
+ * @param payload
+ */
+ public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload);
+
+ /**
+ * Called by DevicePolicyManagerService to inform that a new admin has been added.
+ *
+ * @param packageName the package in which the admin component is part of.
+ * @param userId the userId in which the admin has been added.
+ */
+ public abstract void onActiveAdminAdded(String packageName, int userId);
+
+ /**
+ * Called by DevicePolicyManagerService to inform about the active admins in an user.
+ *
+ * @param adminApps the set of active admins in {@param userId} or null if there are none.
+ * @param userId the userId to which the admin apps belong.
+ */
+ public abstract void setActiveAdminApps(Set<String> adminApps, int userId);
+
+ /**
+ * Called by DevicePolicyManagerService during boot to inform that admin data is loaded and
+ * pushed to UsageStatsService.
+ */
+ public abstract void onAdminDataAvailable();
/**
* Return usage stats.
@@ -155,6 +187,29 @@ public abstract class UsageStatsManagerInternal {
* @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
* result.
*/
- public abstract List<UsageStats> queryUsageStatsForUser(
- int userId, int interval, long beginTime, long endTime, boolean obfuscateInstantApps);
+ public abstract List<UsageStats> queryUsageStatsForUser(@UserIdInt int userId, int interval,
+ long beginTime, long endTime, boolean obfuscateInstantApps);
+
+ /**
+ * Used to persist the last time a job was run for this app, in order to make decisions later
+ * whether a job should be deferred until later. The time passed in should be in elapsed
+ * realtime since boot.
+ * @param packageName the app that executed a job.
+ * @param userId the user associated with the job.
+ * @param elapsedRealtime the time when the job was executed, in elapsed realtime millis since
+ * boot.
+ */
+ public abstract void setLastJobRunTime(String packageName, @UserIdInt int userId,
+ long elapsedRealtime);
+
+ /**
+ * Returns the time in millis since a job was executed for this app, in elapsed realtime
+ * timebase. This value can be larger than the current elapsed realtime if the job was executed
+ * before the device was rebooted. The default value is {@link Long#MAX_VALUE}.
+ * @param packageName the app you're asking about.
+ * @param userId the user associated with the job.
+ * @return the time in millis since a job was last executed for the app, provided it was
+ * indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}.
+ */
+ public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId);
}