diff options
author | Justin Klaassen <justinklaassen@google.com> | 2018-04-15 00:41:15 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2018-04-15 00:41:15 -0400 |
commit | b8042fc9b036db0a6692ca853428fc6ab1e60892 (patch) | |
tree | 82669ea5d75238758e22d379a42baeada526219e /android/app | |
parent | 4d01eeaffaa720e4458a118baa137a11614f00f7 (diff) | |
download | android-28-androidx-preference-release.tar.gz |
Import Android SDK Platform P [4719250]HEADmastermainandroidx-work-releaseandroidx-webkit-releaseandroidx-viewpager2-releaseandroidx-versionedparcelable-releaseandroidx-vectordrawable-releaseandroidx-transition-releaseandroidx-sqlite-releaseandroidx-sharetarget-releaseandroidx-security-security-crypto-releaseandroidx-savedstate-releaseandroidx-room-releaseandroidx-recyclerview-releaseandroidx-recyclerview-recyclerview-selection-releaseandroidx-preference-releaseandroidx-paging-releaseandroidx-paging-legacy-releaseandroidx-navigation-releaseandroidx-mediarouter-releaseandroidx-media2-releaseandroidx-media2-media2-widget-releaseandroidx-media-releaseandroidx-master-releaseandroidx-localbroadcastmanager-releaseandroidx-loader-releaseandroidx-lifecycle-releaseandroidx-jetifier-releaseandroidx-g3-releaseandroidx-fragment-releaseandroidx-exifinterface-releaseandroidx-enterprise-releaseandroidx-core-releaseandroidx-core-core-role-releaseandroidx-coordinatorlayout-releaseandroidx-concurrent-releaseandroidx-compose-releaseandroidx-collection-releaseandroidx-camerax-releaseandroidx-browser-releaseandroidx-biometric-releaseandroidx-benchmark-releaseandroidx-autofill-releaseandroidx-arch-core-releaseandroidx-appcompat-releaseandroidx-annotation-releaseandroidx-annotation-annotation-experimental-releaseandroidx-activity-releaseandroid-arch-work-releaseandroid-arch-navigation-release
/google/data/ro/projects/android/fetch_artifact \
--bid 4719250 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4719250.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: I9ec0a12c9251b8449dba0d86b0cfdbcca16b0a7c
Diffstat (limited to 'android/app')
24 files changed, 1011 insertions, 669 deletions
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java index 09dcbf21..ecd99a7b 100644 --- a/android/app/ActivityOptions.java +++ b/android/app/ActivityOptions.java @@ -1139,7 +1139,8 @@ public class ActivityOptions { * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns * {@code false} for the package of the target activity, a {@link SecurityException} will be - * thrown during {@link Context#startActivity(Intent, Bundle)}. + * thrown during {@link Context#startActivity(Intent, Bundle)}. This method doesn't affect + * activities that are already running — relaunch the activity to run in lock task mode. * * Defaults to {@code false} if not set. * diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java index 50a43989..82c3383d 100644 --- a/android/app/ActivityThread.java +++ b/android/app/ActivityThread.java @@ -5873,7 +5873,7 @@ public final class ActivityThread extends ClientTransactionHandler { } finally { // If the app targets < O-MR1, or doesn't change the thread policy // during startup, clobber the policy to maintain behavior of b/36951662 - if (data.appInfo.targetSdkVersion <= Build.VERSION_CODES.O + if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1 || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) { StrictMode.setThreadPolicy(savedPolicy); } diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java index a68136b5..1084b425 100644 --- a/android/app/ApplicationPackageManager.java +++ b/android/app/ApplicationPackageManager.java @@ -2155,40 +2155,28 @@ public class ApplicationPackageManager extends PackageManager { public String[] setPackagesSuspended(String[] packageNames, boolean suspended, PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage) { - // TODO (b/75332201): Pass in the dialogMessage and use it in the interceptor dialog try { return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras, - launcherExtras, mContext.getOpPackageName(), mContext.getUserId()); + launcherExtras, dialogMessage, mContext.getOpPackageName(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @Override - public PersistableBundle getSuspendedPackageAppExtras(String packageName) { + public Bundle getSuspendedPackageAppExtras() { + final PersistableBundle extras; try { - return mPM.getSuspendedPackageAppExtras(packageName, mContext.getUserId()); + extras = mPM.getSuspendedPackageAppExtras(mContext.getOpPackageName(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - } - - @Override - public Bundle getSuspendedPackageAppExtras() { - final PersistableBundle extras = getSuspendedPackageAppExtras(mContext.getOpPackageName()); return extras != null ? new Bundle(extras.deepCopy()) : null; } @Override - public void setSuspendedPackageAppExtras(String packageName, PersistableBundle appExtras) { - try { - mPM.setSuspendedPackageAppExtras(packageName, appExtras, mContext.getUserId()); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - - @Override public boolean isPackageSuspendedForUser(String packageName, int userId) { try { return mPM.isPackageSuspendedForUser(packageName, userId); @@ -2199,8 +2187,12 @@ public class ApplicationPackageManager extends PackageManager { /** @hide */ @Override - public boolean isPackageSuspended(String packageName) { - return isPackageSuspendedForUser(packageName, mContext.getUserId()); + public boolean isPackageSuspended(String packageName) throws NameNotFoundException { + try { + return isPackageSuspendedForUser(packageName, mContext.getUserId()); + } catch (IllegalArgumentException ie) { + throw new NameNotFoundException(packageName); + } } @Override diff --git a/android/app/BroadcastOptions.java b/android/app/BroadcastOptions.java index b6cff385..69c3632b 100644 --- a/android/app/BroadcastOptions.java +++ b/android/app/BroadcastOptions.java @@ -32,6 +32,7 @@ public class BroadcastOptions { private long mTemporaryAppWhitelistDuration; private int mMinManifestReceiverApiLevel = 0; private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; + private boolean mDontSendToRestrictedApps = false; /** * How long to temporarily put an app on the power whitelist when executing this broadcast @@ -52,6 +53,12 @@ public class BroadcastOptions { static final String KEY_MAX_MANIFEST_RECEIVER_API_LEVEL = "android:broadcast.maxManifestReceiverApiLevel"; + /** + * Corresponds to {@link #setMaxManifestReceiverApiLevel}. + */ + static final String KEY_DONT_SEND_TO_RESTRICTED_APPS = + "android:broadcast.dontSendToRestrictedApps"; + public static BroadcastOptions makeBasic() { BroadcastOptions opts = new BroadcastOptions(); return opts; @@ -66,6 +73,7 @@ public class BroadcastOptions { mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, Build.VERSION_CODES.CUR_DEVELOPMENT); + mDontSendToRestrictedApps = opts.getBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, false); } /** @@ -123,6 +131,23 @@ public class BroadcastOptions { } /** + * Sets whether pending intent can be sent for an application with background restrictions + * @param dontSendToRestrictedApps if true, pending intent will not be sent for an application + * with background restrictions. Default value is {@code false} + */ + public void setDontSendToRestrictedApps(boolean dontSendToRestrictedApps) { + mDontSendToRestrictedApps = dontSendToRestrictedApps; + } + + /** + * @hide + * @return #setDontSendToRestrictedApps + */ + public boolean isDontSendToRestrictedApps() { + return mDontSendToRestrictedApps; + } + + /** * Returns the created options as a Bundle, which can be passed to * {@link android.content.Context#sendBroadcast(android.content.Intent) * Context.sendBroadcast(Intent)} and related methods. @@ -141,6 +166,9 @@ public class BroadcastOptions { if (mMaxManifestReceiverApiLevel != Build.VERSION_CODES.CUR_DEVELOPMENT) { b.putInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, mMaxManifestReceiverApiLevel); } + if (mDontSendToRestrictedApps) { + b.putBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, true); + } return b.isEmpty() ? null : b; } } diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java index 71b88fa4..9a491bc3 100644 --- a/android/app/ContextImpl.java +++ b/android/app/ContextImpl.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -88,11 +89,12 @@ import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicInteger; class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { @@ -212,13 +214,24 @@ class ContextImpl extends Context { static final int STATE_UNINITIALIZED = 0; static final int STATE_INITIALIZING = 1; static final int STATE_READY = 2; + static final int STATE_NOT_FOUND = 3; + + /** @hide */ + @IntDef(prefix = { "STATE_" }, value = { + STATE_UNINITIALIZED, + STATE_INITIALIZING, + STATE_READY, + STATE_NOT_FOUND, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ServiceInitializationState {} /** * Initialization state for each service. Any of {@link #STATE_UNINITIALIZED}, * {@link #STATE_INITIALIZING} or {@link #STATE_READY}, */ - final AtomicInteger[] mServiceInitializationStateArray = - SystemServiceRegistry.createServiceInitializationStateArray(); + @ServiceInitializationState + final int[] mServiceInitializationStateArray = new int[mServiceCache.length]; static ContextImpl getImpl(Context context) { Context nextContext; diff --git a/android/app/Notification.java b/android/app/Notification.java index 4326ee3e..2b4f4206 100644 --- a/android/app/Notification.java +++ b/android/app/Notification.java @@ -3902,7 +3902,7 @@ public class Notification implements Parcelable * @deprecated use {@link #addPerson(Person)} */ public Builder addPerson(String uri) { - addPerson(new Person().setUri(uri)); + addPerson(new Person.Builder().setUri(uri).build()); return this; } @@ -4588,10 +4588,18 @@ public class Notification implements Parcelable bindHeaderChronometerAndTime(contentView); bindProfileBadge(contentView); } + bindActivePermissions(contentView); bindExpandButton(contentView); mN.mUsesStandardHeader = true; } + private void bindActivePermissions(RemoteViews contentView) { + int color = isColorized() ? getPrimaryTextColor() : getSecondaryTextColor(); + contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP); + contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP); + contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP); + } + private void bindExpandButton(RemoteViews contentView) { int color = isColorized() ? getPrimaryTextColor() : getSecondaryTextColor(); contentView.setDrawableTint(R.id.expand_button, false, color, @@ -6384,7 +6392,7 @@ public class Notification implements Parcelable * @deprecated use {@code MessagingStyle(Person)} */ public MessagingStyle(@NonNull CharSequence userDisplayName) { - this(new Person().setName(userDisplayName)); + this(new Person.Builder().setName(userDisplayName).build()); } /** @@ -6431,6 +6439,7 @@ public class Notification implements Parcelable /** * @return the user to be displayed for any replies sent by the user */ + @NonNull public Person getUser() { return mUser; } @@ -6489,7 +6498,7 @@ public class Notification implements Parcelable */ public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { return addMessage(text, timestamp, - sender == null ? null : new Person().setName(sender)); + sender == null ? null : new Person.Builder().setName(sender).build()); } /** @@ -6505,7 +6514,8 @@ public class Notification implements Parcelable * * @return this object for method chaining */ - public MessagingStyle addMessage(CharSequence text, long timestamp, Person sender) { + public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, + @Nullable Person sender) { return addMessage(new Message(text, timestamp, sender)); } @@ -6661,7 +6671,7 @@ public class Notification implements Parcelable mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON); if (mUser == null) { CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); - mUser = new Person().setName(displayName); + mUser = new Person.Builder().setName(displayName).build(); } mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); @@ -6678,7 +6688,8 @@ public class Notification implements Parcelable public RemoteViews makeContentView(boolean increasedHeight) { mBuilder.mOriginalActions = mBuilder.mActions; mBuilder.mActions = new ArrayList<>(); - RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); + RemoteViews remoteViews = makeMessagingView(true /* displayImagesAtEnd */, + true /* showReplyIcon */); mBuilder.mActions = mBuilder.mOriginalActions; mBuilder.mOriginalActions = null; return remoteViews; @@ -6765,11 +6776,19 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - return makeMessagingView(false /* isCollapsed */); + return makeMessagingView(false /* displayImagesAtEnd */, false /* showReplyIcon */); } + /** + * Create a messaging layout. + * + * @param displayImagesAtEnd should images be displayed at the end of the content instead + * of inline. + * @param showReplyIcon Should the reply affordance be shown at the end of the notification + * @return the created remoteView. + */ @NonNull - private RemoteViews makeMessagingView(boolean isCollapsed) { + private RemoteViews makeMessagingView(boolean displayImagesAtEnd, boolean showReplyIcon) { CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; @@ -6780,24 +6799,24 @@ public class Notification implements Parcelable nameReplacement = conversationTitle; conversationTitle = null; } - boolean hideLargeIcon = !isCollapsed || isOneToOne; + boolean hideLargeIcon = !showReplyIcon || isOneToOne; RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null) .hideLargeIcon(hideLargeIcon) .headerTextSecondary(conversationTitle) - .alwaysShowReply(isCollapsed)); + .alwaysShowReply(showReplyIcon)); addExtras(mBuilder.mN.extras); // also update the end margin if there is an image int endMargin = R.dimen.notification_content_margin_end; - if (isCollapsed) { + if (showReplyIcon) { endMargin = R.dimen.notification_content_plus_picture_margin_end; } contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", mBuilder.resolveContrastColor()); - contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", - isCollapsed); + contentView.setBoolean(R.id.status_bar_latest_event_content, "setDisplayImagesAtEnd", + displayImagesAtEnd); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", @@ -6864,7 +6883,8 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */); + RemoteViews remoteViews = makeMessagingView(true /* displayImagesAtEnd */, + false /* showReplyIcon */); remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); return remoteViews; } @@ -6906,7 +6926,8 @@ public class Notification implements Parcelable * @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)); + this(text, timestamp, sender == null ? null + : new Person.Builder().setName(sender).build()); } /** @@ -6917,13 +6938,14 @@ public class Notification implements Parcelable * 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. + * The person provided should contain an Icon, set with + * {@link Person.Builder#setIcon(Icon)} and also have a name provided + * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same + * name, consider providing a key with {@link Person.Builder#setKey(String)} in order + * to differentiate between the different users. * </p> */ - public Message(CharSequence text, long timestamp, @Nullable Person sender){ + public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { mText = text; mTimestamp = timestamp; mSender = sender; @@ -7082,7 +7104,7 @@ public class Notification implements Parcelable // the native api instead CharSequence senderName = bundle.getCharSequence(KEY_SENDER); if (senderName != null) { - senderPerson = new Person().setName(senderName); + senderPerson = new Person.Builder().setName(senderName).build(); } } Message message = new Message(bundle.getCharSequence(KEY_TEXT), @@ -7777,217 +7799,6 @@ 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; - private boolean mBot; - private boolean mImportant; - - protected Person(Parcel in) { - mName = in.readCharSequence(); - if (in.readInt() != 0) { - mIcon = Icon.CREATOR.createFromParcel(in); - } - mUri = in.readString(); - mKey = in.readString(); - mImportant = in.readBoolean(); - mBot = in.readBoolean(); - } - - /** - * 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; - } - - /** - * Sets whether this is an important person. Use this method to denote users who frequently - * interact with the user of this device, when it is not possible to refer to the user - * by {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. - * - * @param isImportant {@code true} if this is an important person, {@code false} otherwise. - */ - public Person setImportant(boolean isImportant) { - mImportant = isImportant; - return this; - } - - /** - * Sets whether this person is a machine rather than a human. - * - * @param isBot {@code true} if this person is a machine, {@code false} otherwise. - */ - public Person setBot(boolean isBot) { - mBot = isBot; - return this; - } - - /** - * @return the uri provided for this person or {@code null} if no Uri was provided - */ - @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 whether this Person is a machine. - */ - public boolean isBot() { - return mBot; - } - - /** - * @return whether this Person is important. - */ - public boolean isImportant() { - return mImportant; - } - - /** - * @return the URI associated with this person, or "name:mName" otherwise - * @hide - */ - 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); - dest.writeBoolean(mImportant); - dest.writeBoolean(mBot); - } - - 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. diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java index 4a7cf623..9e47ced4 100644 --- a/android/app/NotificationChannel.java +++ b/android/app/NotificationChannel.java @@ -328,7 +328,8 @@ public final class NotificationChannel implements Parcelable { * Group information is only used for presentation, not for behavior. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}, unless the + * channel is not currently part of a group. * * @param groupId the id of a group created by * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}. @@ -341,6 +342,9 @@ public final class NotificationChannel implements Parcelable { * Sets whether notifications posted to this channel can appear as application icon badges * in a Launcher. * + * Only modifiable before the channel is submitted to + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. + * * @param showBadge true if badges should be allowed to be shown. */ public void setShowBadge(boolean showBadge) { @@ -353,7 +357,7 @@ public final class NotificationChannel implements Parcelable { * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void setSound(Uri sound, AudioAttributes audioAttributes) { this.mSound = sound; @@ -365,7 +369,7 @@ public final class NotificationChannel implements Parcelable { * on devices that support that feature. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void enableLights(boolean lights) { this.mLights = lights; @@ -376,7 +380,7 @@ public final class NotificationChannel implements Parcelable { * {@link #enableLights(boolean) enabled} on this channel and the device supports that feature. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void setLightColor(int argb) { this.mLightColor = argb; @@ -387,7 +391,7 @@ public final class NotificationChannel implements Parcelable { * be set with {@link #setVibrationPattern(long[])}. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void enableVibration(boolean vibration) { this.mVibrationEnabled = vibration; @@ -399,7 +403,7 @@ public final class NotificationChannel implements Parcelable { * vibration} as well. Otherwise, vibration will be disabled. * * Only modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ public void setVibrationPattern(long[] vibrationPattern) { this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0; @@ -407,9 +411,10 @@ public final class NotificationChannel implements Parcelable { } /** - * Sets the level of interruption of this notification channel. Only - * modifiable before the channel is submitted to - * {@link NotificationManager#notify(String, int, Notification)}. + * Sets the level of interruption of this notification channel. + * + * Only modifiable before the channel is submitted to + * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. * * @param importance the amount the user should be interrupted by * notifications from this channel. diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java index 46d1264f..757fc643 100644 --- a/android/app/NotificationManager.java +++ b/android/app/NotificationManager.java @@ -1145,6 +1145,21 @@ public class NotificationManager { SUPPRESSED_EFFECT_NOTIFICATION_LIST }; + private static final int[] SCREEN_OFF_SUPPRESSED_EFFECTS = { + SUPPRESSED_EFFECT_SCREEN_OFF, + SUPPRESSED_EFFECT_FULL_SCREEN_INTENT, + SUPPRESSED_EFFECT_LIGHTS, + SUPPRESSED_EFFECT_AMBIENT, + }; + + private static final int[] SCREEN_ON_SUPPRESSED_EFFECTS = { + SUPPRESSED_EFFECT_SCREEN_ON, + SUPPRESSED_EFFECT_PEEK, + SUPPRESSED_EFFECT_STATUS_BAR, + SUPPRESSED_EFFECT_BADGE, + SUPPRESSED_EFFECT_NOTIFICATION_LIST + }; + /** * Visual effects to suppress for a notification that is filtered by Do Not Disturb mode. * Bitmask of SUPPRESSED_EFFECT_* constants. @@ -1297,6 +1312,58 @@ public class NotificationManager { return true; } + /** + * @hide + */ + public static boolean areAnyScreenOffEffectsSuppressed(int effects) { + for (int i = 0; i < SCREEN_OFF_SUPPRESSED_EFFECTS.length; i++) { + final int effect = SCREEN_OFF_SUPPRESSED_EFFECTS[i]; + if ((effects & effect) != 0) { + return true; + } + } + return false; + } + + /** + * @hide + */ + public static boolean areAnyScreenOnEffectsSuppressed(int effects) { + for (int i = 0; i < SCREEN_ON_SUPPRESSED_EFFECTS.length; i++) { + final int effect = SCREEN_ON_SUPPRESSED_EFFECTS[i]; + if ((effects & effect) != 0) { + return true; + } + } + return false; + } + + /** + * @hide + */ + public static int toggleScreenOffEffectsSuppressed(int currentEffects, boolean suppress) { + return toggleEffects(currentEffects, SCREEN_OFF_SUPPRESSED_EFFECTS, suppress); + } + + /** + * @hide + */ + public static int toggleScreenOnEffectsSuppressed(int currentEffects, boolean suppress) { + return toggleEffects(currentEffects, SCREEN_ON_SUPPRESSED_EFFECTS, suppress); + } + + private static int toggleEffects(int currentEffects, int[] effects, boolean suppress) { + for (int i = 0; i < effects.length; i++) { + final int effect = effects[i]; + if (suppress) { + currentEffects |= effect; + } else { + currentEffects &= ~effect; + } + } + return currentEffects; + } + public static String suppressedEffectsToString(int effects) { if (effects <= 0) return ""; final StringBuilder sb = new StringBuilder(); diff --git a/android/app/Person.java b/android/app/Person.java new file mode 100644 index 00000000..3884a8d4 --- /dev/null +++ b/android/app/Person.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Provides an immutable reference to an entity that appears repeatedly on different surfaces of the + * platform. For example, this could represent the sender of a message. + */ +public final class Person implements Parcelable { + + @Nullable private CharSequence mName; + @Nullable private Icon mIcon; + @Nullable private String mUri; + @Nullable private String mKey; + private boolean mIsBot; + private boolean mIsImportant; + + private Person(Parcel in) { + mName = in.readCharSequence(); + if (in.readInt() != 0) { + mIcon = Icon.CREATOR.createFromParcel(in); + } + mUri = in.readString(); + mKey = in.readString(); + mIsImportant = in.readBoolean(); + mIsBot = in.readBoolean(); + } + + private Person(Builder builder) { + mName = builder.mName; + mIcon = builder.mIcon; + mUri = builder.mUri; + mKey = builder.mKey; + mIsBot = builder.mIsBot; + mIsImportant = builder.mIsImportant; + } + + /** Creates and returns a new {@link Builder} initialized with this Person's data. */ + public Builder toBuilder() { + return new Builder(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 whether this Person is a machine. + */ + public boolean isBot() { + return mIsBot; + } + + /** + * @return whether this Person is important. + */ + public boolean isImportant() { + return mIsImportant; + } + + /** + * @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); + dest.writeBoolean(mIsImportant); + dest.writeBoolean(mIsBot); + } + + /** Builder for the immutable {@link Person} class. */ + public static class Builder { + @Nullable private CharSequence mName; + @Nullable private Icon mIcon; + @Nullable private String mUri; + @Nullable private String mKey; + private boolean mIsBot; + private boolean mIsImportant; + + /** Creates a new, empty {@link Builder}. */ + public Builder() { + } + + private Builder(Person person) { + mName = person.mName; + mIcon = person.mIcon; + mUri = person.mUri; + mKey = person.mKey; + mIsBot = person.mIsBot; + mIsImportant = person.mIsImportant; + } + + /** + * Give this person a name. + * + * @param name the name of this person. + */ + @NonNull + public Person.Builder setName(@Nullable CharSequence name) { + this.mName = name; + return this; + } + + /** + * Add an icon for this person. + * <br /> + * The system will prefer this icon over any images that are resolved from the URI. + * + * @param icon the icon of the person. + */ + @NonNull + public Person.Builder setIcon(@Nullable Icon icon) { + this.mIcon = icon; + return this; + } + + /** + * Set a URI associated with this person. + * + * <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. + */ + @NonNull + public Person.Builder 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 the key for the purpose of + * identification.</P> + * + * @param key the key that uniquely identifies this person. + */ + @NonNull + public Person.Builder setKey(@Nullable String key) { + mKey = key; + return this; + } + + /** + * Sets whether this is an important person. Use this method to denote users who frequently + * interact with the user of this device when {@link #setUri(String)} isn't provided with + * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, and instead with + * the {@code mailto:} or {@code tel:} schemas. + * + * @param isImportant {@code true} if this is an important person, {@code false} otherwise. + */ + @NonNull + public Person.Builder setImportant(boolean isImportant) { + mIsImportant = isImportant; + return this; + } + + /** + * Sets whether this person is a machine rather than a human. + * + * @param isBot {@code true} if this person is a machine, {@code false} otherwise. + */ + @NonNull + public Person.Builder setBot(boolean isBot) { + mIsBot = isBot; + return this; + } + + /** Creates and returns the {@link Person} this builder represents. */ + @NonNull + public Person build() { + return new Person(this); + } + } + + 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]; + } + }; +} diff --git a/android/app/RemoteAction.java b/android/app/RemoteAction.java index 47741c02..c1746655 100644 --- a/android/app/RemoteAction.java +++ b/android/app/RemoteAction.java @@ -122,6 +122,7 @@ public final class RemoteAction implements Parcelable { public RemoteAction clone() { RemoteAction action = new RemoteAction(mIcon, mTitle, mContentDescription, mActionIntent); action.setEnabled(mEnabled); + action.setShouldShowIcon(mShouldShowIcon); return action; } diff --git a/android/app/StatsManager.java b/android/app/StatsManager.java index 4a6fa8c2..8783d945 100644 --- a/android/app/StatsManager.java +++ b/android/app/StatsManager.java @@ -23,6 +23,7 @@ import android.os.IBinder; import android.os.IStatsManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.AndroidException; import android.util.Slog; /** @@ -82,55 +83,74 @@ public final class StatsManager { } /** - * Clients can send a configuration and simultaneously registers the name of a broadcast - * receiver that listens for when it should request data. + * Adds the given configuration and associates it with the given configKey. If a config with the + * given configKey already exists for the caller's uid, it is replaced with the new one. * * @param configKey An arbitrary integer that allows clients to track the configuration. - * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all + * @param config Wire-encoded StatsdConfig proto that specifies metrics (and all * dependencies eg, conditions and matchers). - * @return true if successful + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service + * @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto */ @RequiresPermission(Manifest.permission.DUMP) - public boolean addConfiguration(long configKey, byte[] config) { + public void addConfig(long configKey, byte[] config) throws StatsUnavailableException { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); - if (service == null) { - Slog.e(TAG, "Failed to find statsd when adding configuration"); - return false; - } - return service.addConfiguration(configKey, config); + service.addConfiguration(configKey, config); // can throw IllegalArgumentException } catch (RemoteException e) { Slog.e(TAG, "Failed to connect to statsd when adding configuration"); - return false; + throw new StatsUnavailableException("could not connect", e); } } } /** + * TODO: Temporary for backwards compatibility. Remove. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean addConfiguration(long configKey, byte[] config) { + try { + addConfig(configKey, config); + return true; + } catch (StatsUnavailableException | IllegalArgumentException e) { + return false; + } + } + + /** * Remove a configuration from logging. * * @param configKey Configuration key to remove. - * @return true if successful + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service */ @RequiresPermission(Manifest.permission.DUMP) - public boolean removeConfiguration(long configKey) { + public void removeConfig(long configKey) throws StatsUnavailableException { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); - if (service == null) { - Slog.e(TAG, "Failed to find statsd when removing configuration"); - return false; - } - return service.removeConfiguration(configKey); + service.removeConfiguration(configKey); } catch (RemoteException e) { Slog.e(TAG, "Failed to connect to statsd when removing configuration"); - return false; + throw new StatsUnavailableException("could not connect", e); } } } /** + * TODO: Temporary for backwards compatibility. Remove. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(long configKey) { + try { + removeConfig(configKey); + return true; + } catch (StatsUnavailableException e) { + return false; + } + } + + /** * Set the PendingIntent to be used when broadcasting subscriber information to the given * subscriberId within the given config. * <p> @@ -150,126 +170,168 @@ public final class StatsManager { * {@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()). + * time statsd starts. The config must have been added first (via {@link #addConfig}). * - * @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 + * @param configKey The integer naming the config to which this subscriber is attached. + * @param subscriberId ID of the subscriber, as used in the config. + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service */ @RequiresPermission(Manifest.permission.DUMP) - public boolean setBroadcastSubscriber( - long configKey, long subscriberId, PendingIntent pendingIntent) { + public void setBroadcastSubscriber( + PendingIntent pendingIntent, long configKey, long subscriberId) + throws StatsUnavailableException { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); - if (service == null) { - Slog.e(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); + service.setBroadcastSubscriber(configKey, subscriberId, intentSender); } else { - return service.unsetBroadcastSubscriber(configKey, subscriberId); + service.unsetBroadcastSubscriber(configKey, subscriberId); } } catch (RemoteException e) { Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e); - return false; + throw new StatsUnavailableException("could not connect", e); } } } /** + * TODO: Temporary for backwards compatibility. Remove. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean setBroadcastSubscriber( + long configKey, long subscriberId, PendingIntent pendingIntent) { + try { + setBroadcastSubscriber(pendingIntent, configKey, subscriberId); + return true; + } catch (StatsUnavailableException e) { + return false; + } + } + + /** * Registers the operation that is called to retrieve the metrics data. This must be called - * each time statsd starts. The config must have been added first (via addConfiguration(), - * although addConfiguration could have been called on a previous boot). This operation allows + * each time statsd starts. The config must have been added first (via {@link #addConfig}, + * although addConfig could have been called on a previous boot). This operation allows * statsd to send metrics data whenever statsd determines that the metrics in memory are - * approaching the memory limits. The fetch operation should call {@link #getData} to fetch the - * data, which also deletes the retrieved metrics from statsd's memory. + * approaching the memory limits. The fetch operation should call {@link #getReports} to fetch + * the data, which also deletes the retrieved metrics from statsd's memory. * - * @param configKey The integer naming the config to which this operation is attached. * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber * associated with the given subscriberId. May be null, in which case * it removes any associated pending intent with this configKey. - * @return true if successful + * @param configKey The integer naming the config to which this operation is attached. + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service */ @RequiresPermission(Manifest.permission.DUMP) - public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) { + public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey) + throws StatsUnavailableException { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); - if (service == null) { - Slog.e(TAG, "Failed to find statsd when registering data listener."); - return false; - } if (pendingIntent == null) { - return service.removeDataFetchOperation(configKey); + service.removeDataFetchOperation(configKey); } else { // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. IBinder intentSender = pendingIntent.getTarget().asBinder(); - return service.setDataFetchOperation(configKey, intentSender); + service.setDataFetchOperation(configKey, intentSender); } } catch (RemoteException e) { Slog.e(TAG, "Failed to connect to statsd when registering data listener."); - return false; + throw new StatsUnavailableException("could not connect", e); } } } /** - * Clients can request data with a binder call. This getter is destructive and also clears - * the retrieved metrics from statsd memory. + * TODO: Temporary for backwards compatibility. Remove. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) { + try { + setFetchReportsOperation(pendingIntent, configKey); + return true; + } catch (StatsUnavailableException e) { + return false; + } + } + + /** + * Request the data collected for the given configKey. + * This getter is destructive - it also clears the retrieved metrics from statsd's memory. * * @param configKey Configuration key to retrieve data from. - * @return Serialized ConfigMetricsReportList proto. Returns null on failure (eg, if statsd - * crashed). + * @return Serialized ConfigMetricsReportList proto. + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service */ @RequiresPermission(Manifest.permission.DUMP) - public @Nullable byte[] getData(long configKey) { + public byte[] getReports(long configKey) throws StatsUnavailableException { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); - if (service == null) { - Slog.e(TAG, "Failed to find statsd when getting data"); - return null; - } return service.getData(configKey); } catch (RemoteException e) { Slog.e(TAG, "Failed to connect to statsd when getting data"); - return null; + throw new StatsUnavailableException("could not connect", e); } } } /** + * TODO: Temporary for backwards compatibility. Remove. + */ + @RequiresPermission(Manifest.permission.DUMP) + public @Nullable byte[] getData(long configKey) { + try { + return getReports(configKey); + } catch (StatsUnavailableException e) { + 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)}. + * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}. * This getter is not destructive and will not reset any metrics/counters. * - * @return Serialized StatsdStatsReport proto. Returns null on failure (eg, if statsd crashed). + * @return Serialized StatsdStatsReport proto. + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service */ @RequiresPermission(Manifest.permission.DUMP) - public @Nullable byte[] getMetadata() { + public byte[] getStatsMetadata() throws StatsUnavailableException { synchronized (this) { try { IStatsManager service = getIStatsManagerLocked(); - if (service == null) { - Slog.e(TAG, "Failed to find statsd when getting metadata"); - return null; - } return service.getMetadata(); } catch (RemoteException e) { Slog.e(TAG, "Failed to connect to statsd when getting metadata"); - return null; + throw new StatsUnavailableException("could not connect", e); } } } + /** + * Clients can request metadata for statsd. Will contain stats across all configurations but not + * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}. + * This getter is not destructive and will not reset any metrics/counters. + * + * @return Serialized StatsdStatsReport proto. Returns null on failure (eg, if statsd crashed). + */ + @RequiresPermission(Manifest.permission.DUMP) + public @Nullable byte[] getMetadata() { + try { + return getStatsMetadata(); + } catch (StatsUnavailableException e) { + return null; + } + } + private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { @@ -279,14 +341,33 @@ public final class StatsManager { } } - private IStatsManager getIStatsManagerLocked() throws RemoteException { + private IStatsManager getIStatsManagerLocked() throws StatsUnavailableException { if (mService != null) { return mService; } mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); - if (mService != null) { + if (mService == null) { + throw new StatsUnavailableException("could not be found"); + } + try { mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); + } catch (RemoteException e) { + throw new StatsUnavailableException("could not connect when linkToDeath", e); } return mService; } + + /** + * Exception thrown when communication with the stats service fails (eg if it is not available). + * This might be thrown early during boot before the stats service has started or if it crashed. + */ + public static class StatsUnavailableException extends AndroidException { + public StatsUnavailableException(String reason) { + super("Failed to connect to statsd: " + reason); + } + + public StatsUnavailableException(String reason, Throwable e) { + super("Failed to connect to statsd: " + reason, e); + } + } } diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java index 1776eace..246d4a37 100644 --- a/android/app/SystemServiceRegistry.java +++ b/android/app/SystemServiceRegistry.java @@ -18,6 +18,7 @@ package android.app; import android.accounts.AccountManager; import android.accounts.IAccountManager; +import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.job.IJobScheduler; @@ -104,10 +105,12 @@ import android.nfc.NfcManager; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Build; +import android.os.DeviceIdleManager; import android.os.DropBoxManager; import android.os.HardwarePropertiesManager; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; +import android.os.IDeviceIdleController; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; @@ -160,7 +163,6 @@ import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; import java.util.HashMap; -import java.util.concurrent.atomic.AtomicInteger; /** * Manages all of the system services that can be returned by {@link Context#getSystemService}. @@ -279,12 +281,12 @@ final class SystemServiceRegistry { }}); registerService(Context.IPSEC_SERVICE, IpSecManager.class, - new StaticServiceFetcher<IpSecManager>() { + new CachedServiceFetcher<IpSecManager>() { @Override - public IpSecManager createService() { + public IpSecManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder b = ServiceManager.getService(Context.IPSEC_SERVICE); IIpSecService service = IIpSecService.Stub.asInterface(b); - return new IpSecManager(service); + return new IpSecManager(ctx, service); }}); registerService(Context.COUNTRY_DETECTOR, CountryDetector.class, @@ -984,6 +986,17 @@ final class SystemServiceRegistry { ctx.mMainThread.getHandler()); } }); + + registerService(Context.DEVICE_IDLE_CONTROLLER, DeviceIdleManager.class, + new CachedServiceFetcher<DeviceIdleManager>() { + @Override + public DeviceIdleManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IDeviceIdleController service = IDeviceIdleController.Stub.asInterface( + ServiceManager.getServiceOrThrow( + Context.DEVICE_IDLE_CONTROLLER)); + return new DeviceIdleManager(ctx.getOuterContext(), service); + }}); } /** @@ -993,10 +1006,6 @@ final class SystemServiceRegistry { return new Object[sServiceCacheSize]; } - public static AtomicInteger[] createServiceInitializationStateArray() { - return new AtomicInteger[sServiceCacheSize]; - } - /** * Gets a system service from a given context. */ @@ -1037,7 +1046,10 @@ final class SystemServiceRegistry { static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> { private final int mCacheIndex; - public CachedServiceFetcher() { + CachedServiceFetcher() { + // Note this class must be instantiated only by the static initializer of the + // outer class (SystemServiceRegistry), which already does the synchronization, + // so bare access to sServiceCacheSize is okay here. mCacheIndex = sServiceCacheSize++; } @@ -1045,95 +1057,73 @@ final class SystemServiceRegistry { @SuppressWarnings("unchecked") public final T getService(ContextImpl ctx) { final Object[] cache = ctx.mServiceCache; + final int[] gates = ctx.mServiceInitializationStateArray; + + for (;;) { + boolean doInitialize = false; + synchronized (cache) { + // Return it if we already have a cached instance. + T service = (T) cache[mCacheIndex]; + if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) { + return service; + } - // Fast path. If it's already cached, just return it. - Object service = cache[mCacheIndex]; - if (service != null) { - return (T) service; - } + // If we get here, there's no cached instance. - // Slow path. - final AtomicInteger[] gates = ctx.mServiceInitializationStateArray; - final AtomicInteger gate; + // Grr... if gate is STATE_READY, then this means we initialized the service + // once but someone cleared it. + // We start over from STATE_UNINITIALIZED. + if (gates[mCacheIndex] == ContextImpl.STATE_READY) { + gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED; + } - synchronized (cache) { - // See if it's cached or not again, with the lock held this time. - service = cache[mCacheIndex]; - if (service != null) { - return (T) service; - } + // It's possible for multiple threads to get here at the same time, so + // use the "gate" to make sure only the first thread will call createService(). - // Not initialized yet. Create an atomic boolean to control which thread should - // instantiate the service. - if (gates[mCacheIndex] != null) { - gate = gates[mCacheIndex]; - } else { - gate = new AtomicInteger(ContextImpl.STATE_UNINITIALIZED); - gates[mCacheIndex] = gate; + // At this point, the gate must be either UNINITIALIZED or INITIALIZING. + if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) { + doInitialize = true; + gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING; + } } - } - // Not cached yet. - // - // Note multiple threads can reach here for the same service on the same context - // concurrently. - // - // Now we're going to instantiate the service, but do so without the cache held; - // otherwise it could deadlock. (b/71882178) - // - // However we still don't want to instantiate the same service multiple times, so - // use the atomic integer to ensure only one thread will call createService(). - - if (gate.compareAndSet( - ContextImpl.STATE_UNINITIALIZED, ContextImpl.STATE_INITIALIZING)) { - try { - // This thread is the first one to get here. Instantiate the service - // *without* the cache lock held. + if (doInitialize) { + // Only the first thread gets here. + + T service = null; + @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND; try { + // This thread is the first one to get here. Instantiate the service + // *without* the cache lock held. service = createService(ctx); + newState = ContextImpl.STATE_READY; + + } catch (ServiceNotFoundException e) { + onServiceNotFound(e); + } finally { synchronized (cache) { cache[mCacheIndex] = service; + gates[mCacheIndex] = newState; + cache.notifyAll(); } - } catch (ServiceNotFoundException e) { - onServiceNotFound(e); - } - } finally { - // Tell the all other threads that the cache is ready now. - // (But it's still be null in case of ServiceNotFoundException.) - synchronized (gate) { - gate.set(ContextImpl.STATE_READY); - gate.notifyAll(); } + return service; } - return (T) service; - } - // Other threads will wait on the gate lock. - synchronized (gate) { - boolean interrupted = false; - - // Note: We check whether "state == STATE_READY", not - // "cache[mCacheIndex] != null", because "cache[mCacheIndex] == null" - // is still a valid outcome in the ServiceNotFoundException case. - while (gate.get() != ContextImpl.STATE_READY) { - try { - gate.wait(); - } catch (InterruptedException e) { - Log.w(TAG, "getService() interrupted"); - interrupted = true; + // The other threads will wait for the first thread to call notifyAll(), + // and go back to the top and retry. + synchronized (cache) { + while (gates[mCacheIndex] < ContextImpl.STATE_READY) { + try { + cache.wait(); + } catch (InterruptedException e) { + Log.w(TAG, "getService() interrupted"); + Thread.currentThread().interrupt(); + return null; + } } } - if (interrupted) { - Thread.currentThread().interrupt(); - } - } - // Now the first thread has initialized it. - // It may still be null if ServiceNotFoundException was thrown, but that shouldn't - // happen, so we'll just return null here in that case. - synchronized (cache) { - service = cache[mCacheIndex]; } - return (T) service; } public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException; diff --git a/android/app/UiAutomation.java b/android/app/UiAutomation.java index bd4933a2..c03340e8 100644 --- a/android/app/UiAutomation.java +++ b/android/app/UiAutomation.java @@ -580,6 +580,8 @@ public final class UiAutomation { // Execute the command *without* the lock being held. command.run(); + List<AccessibilityEvent> receivedEvents = new ArrayList<>(); + // Acquire the lock and wait for the event. try { // Wait for the event. @@ -600,14 +602,14 @@ public final class UiAutomation { if (filter.accept(event)) { return event; } - event.recycle(); + receivedEvents.add(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: " + receivedEvents); } synchronized (mLock) { if (mEventQueue.isEmpty()) { @@ -620,6 +622,11 @@ public final class UiAutomation { } } } finally { + int size = receivedEvents.size(); + for (int i = 0; i < size; i++) { + receivedEvents.get(i).recycle(); + } + synchronized (mLock) { mWaitingForEventDelivery = false; mEventQueue.clear(); diff --git a/android/app/WallpaperManager.java b/android/app/WallpaperManager.java index 465340f6..6c2fb2df 100644 --- a/android/app/WallpaperManager.java +++ b/android/app/WallpaperManager.java @@ -401,7 +401,8 @@ public class WallpaperManager { } } synchronized (this) { - if (mCachedWallpaper != null && mCachedWallpaperUserId == userId) { + if (mCachedWallpaper != null && mCachedWallpaperUserId == userId + && !mCachedWallpaper.isRecycled()) { return mCachedWallpaper; } mCachedWallpaper = null; @@ -412,7 +413,7 @@ public class WallpaperManager { } catch (OutOfMemoryError e) { Log.w(TAG, "Out of memory loading the current wallpaper: " + e); } catch (SecurityException e) { - if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.O) { + if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { Log.w(TAG, "No permission to access wallpaper, suppressing" + " exception to avoid crashing legacy app."); } else { @@ -976,7 +977,7 @@ public class WallpaperManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (SecurityException e) { - if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.O) { + if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { Log.w(TAG, "No permission to access wallpaper, suppressing" + " exception to avoid crashing legacy app."); return null; diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java index b64aae52..c491dccb 100644 --- a/android/app/admin/DevicePolicyManager.java +++ b/android/app/admin/DevicePolicyManager.java @@ -1169,10 +1169,18 @@ public class DevicePolicyManager { * Constant to indicate the feature of mandatory backups. Used as argument to * {@link #createAdminSupportIntent(String)}. * @see #setMandatoryBackupTransport(ComponentName, ComponentName) + * @hide */ public static final String POLICY_MANDATORY_BACKUPS = "policy_mandatory_backups"; /** + * Constant to indicate the feature of suspending app. Use it as the value of + * {@link #EXTRA_RESTRICTION}. + * @hide + */ + public static final String POLICY_SUSPEND_PACKAGES = "policy_suspend_packages"; + + /** * 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}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or @@ -4211,6 +4219,15 @@ public class DevicePolicyManager { return null; } + /** + * Returns {@code true} if the device supports attestation of device identifiers in addition + * to key attestation. + * @return {@code true} if Device ID attestation is supported. + */ + public boolean isDeviceIdAttestationSupported() { + PackageManager pm = mContext.getPackageManager(); + return pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION); + } /** * Called by a device or profile owner, or delegated certificate installer, to associate @@ -6182,6 +6199,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public @Nullable List<String> getPermittedAccessibilityServices(int userId) { throwIfParentInstance("getPermittedAccessibilityServices"); if (mService != null) { @@ -6826,8 +6844,7 @@ 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}, {@link #POLICY_DISABLE_SCREEN_CAPTURE} or - * {@link #POLICY_MANDATORY_BACKUPS}. + * {@link #POLICY_DISABLE_CAMERA}, {@link #POLICY_DISABLE_SCREEN_CAPTURE}. * @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. */ @@ -8774,13 +8791,6 @@ 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. @@ -8818,6 +8828,8 @@ public class DevicePolicyManager { * <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. + * <p> If the backup service is disabled after the mandatory backup transport has been set, the + * mandatory backup transport is cleared. * * <p>NOTE: The method shouldn't be called on the main thread. * @@ -8825,6 +8837,7 @@ public class DevicePolicyManager { * @param backupTransportComponent The backup transport layer to be used for mandatory backups. * @return {@code true} if the backup transport was successfully set; {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner. + * @hide */ @WorkerThread public boolean setMandatoryBackupTransport( @@ -8844,6 +8857,7 @@ public class DevicePolicyManager { * * @return a {@link ComponentName} of the backup transport layer to be used if backups are * mandatory or {@code null} if backups are not mandatory. + * @hide */ public ComponentName getMandatoryBackupTransport() { throwIfParentInstance("getMandatoryBackupTransport"); diff --git a/android/app/admin/FreezeInterval.java b/android/app/admin/FreezePeriod.java index de5e21ac..657f0177 100644 --- a/android/app/admin/FreezeInterval.java +++ b/android/app/admin/FreezePeriod.java @@ -20,49 +20,88 @@ import android.util.Log; import android.util.Pair; import java.time.LocalDate; +import java.time.MonthDay; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; /** - * An interval representing one freeze period which repeats annually. We use the number of days - * since the start of (non-leap) year to define the start and end dates of an interval, both - * inclusive. If the end date is smaller than the start date, the interval is considered wrapped - * around the year-end. As far as an interval is concerned, February 29th should be treated as - * if it were February 28th: so an interval starting or ending on February 28th are not - * distinguishable from an interval on February 29th. When calulating interval length or - * distance between two dates, February 29th is also disregarded. + * A class that represents one freeze period which repeats <em>annually</em>. A freeze period has + * two {@link java.time#MonthDay} values that define the start and end dates of the period, both + * inclusive. If the end date is earlier than the start date, the period is considered wrapped + * around the year-end. As far as freeze period is concerned, leap year is disregarded and February + * 29th should be treated as if it were February 28th: so a freeze starting or ending on February + * 28th is identical to a freeze starting or ending on February 29th. When calulating the length of + * a freeze or the distance bewteen two freee periods, February 29th is also ignored. * * @see SystemUpdatePolicy#setFreezePeriods - * @hide */ -public class FreezeInterval { - private static final String TAG = "FreezeInterval"; +public class FreezePeriod { + private static final String TAG = "FreezePeriod"; private static final int DUMMY_YEAR = 2001; static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year - final int mStartDay; // [1,365] - final int mEndDay; // [1,365] + private final MonthDay mStart; + private final MonthDay mEnd; - FreezeInterval(int startDay, int endDay) { - if (startDay < 1 || startDay > 365 || endDay < 1 || endDay > 365) { - throw new RuntimeException("Bad dates for Interval: " + startDay + "," + endDay); - } + /* + * Start and end dates represented by number of days since the beginning of the year. + * They are internal representations of mStart and mEnd with normalized Leap year days + * (Feb 29 == Feb 28 == 59th day of year). All internal calclations are based on + * these two values so that leap year days are disregarded. + */ + private final int mStartDay; // [1, 365] + private final int mEndDay; // [1, 365] + + /** + * Creates a freeze period by its start and end dates. If the end date is earlier than the start + * date, the freeze period is considered wrapping year-end. + */ + public FreezePeriod(MonthDay start, MonthDay end) { + mStart = start; + mStartDay = mStart.atYear(DUMMY_YEAR).getDayOfYear(); + mEnd = end; + mEndDay = mEnd.atYear(DUMMY_YEAR).getDayOfYear(); + } + + /** + * Returns the start date (inclusive) of this freeze period. + */ + public MonthDay getStart() { + return mStart; + } + + /** + * Returns the end date (inclusive) of this freeze period. + */ + public MonthDay getEnd() { + return mEnd; + } + + /** + * @hide + */ + private FreezePeriod(int startDay, int endDay) { mStartDay = startDay; + mStart = dayOfYearToMonthDay(startDay); mEndDay = endDay; + mEnd = dayOfYearToMonthDay(endDay); } + /** @hide */ int getLength() { return getEffectiveEndDay() - mStartDay + 1; } + /** @hide */ boolean isWrapped() { return mEndDay < mStartDay; } /** * Returns the effective end day, taking wrapping around year-end into consideration + * @hide */ int getEffectiveEndDay() { if (!isWrapped()) { @@ -72,6 +111,7 @@ public class FreezeInterval { } } + /** @hide */ boolean contains(LocalDate localDate) { final int daysOfYear = dayOfYearDisregardLeapYear(localDate); if (!isWrapped()) { @@ -84,6 +124,7 @@ public class FreezeInterval { } } + /** @hide */ boolean after(LocalDate localDate) { return mStartDay > dayOfYearDisregardLeapYear(localDate); } @@ -95,6 +136,7 @@ public class FreezeInterval { * include now, the returned dates represents the next future interval. * The result will always have the same month and dayOfMonth value as the non-instantiated * interval itself. + * @hide */ Pair<LocalDate, LocalDate> toCurrentOrFutureRealDates(LocalDate now) { final int nowDays = dayOfYearDisregardLeapYear(now); @@ -138,14 +180,24 @@ public class FreezeInterval { + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter); } - // Treat the supplied date as in a non-leap year and return its day of year. - static int dayOfYearDisregardLeapYear(LocalDate date) { + /** @hide */ + private static MonthDay dayOfYearToMonthDay(int dayOfYear) { + LocalDate date = LocalDate.ofYearDay(DUMMY_YEAR, dayOfYear); + return MonthDay.of(date.getMonth(), date.getDayOfMonth()); + } + + /** + * Treat the supplied date as in a non-leap year and return its day of year. + * @hide + */ + private static int dayOfYearDisregardLeapYear(LocalDate date) { return date.withYear(DUMMY_YEAR).getDayOfYear(); } /** * Compute the number of days between first (inclusive) and second (exclusive), * treating all years in between as non-leap. + * @hide */ public static int distanceWithoutLeapYear(LocalDate first, LocalDate second) { return dayOfYearDisregardLeapYear(first) - dayOfYearDisregardLeapYear(second) @@ -165,16 +217,16 @@ public class FreezeInterval { * 3. At most one wrapped Interval remains, and it will be at the end of the list * @hide */ - protected static List<FreezeInterval> canonicalizeIntervals(List<FreezeInterval> intervals) { + static List<FreezePeriod> canonicalizePeriods(List<FreezePeriod> intervals) { boolean[] taken = new boolean[DAYS_IN_YEAR]; // First convert the intervals into flat array - for (FreezeInterval interval : intervals) { + for (FreezePeriod interval : intervals) { for (int i = interval.mStartDay; i <= interval.getEffectiveEndDay(); i++) { taken[(i - 1) % DAYS_IN_YEAR] = true; } } // Then reconstruct intervals from the array - List<FreezeInterval> result = new ArrayList<>(); + List<FreezePeriod> result = new ArrayList<>(); int i = 0; while (i < DAYS_IN_YEAR) { if (!taken[i]) { @@ -183,14 +235,14 @@ public class FreezeInterval { } final int intervalStart = i + 1; while (i < DAYS_IN_YEAR && taken[i]) i++; - result.add(new FreezeInterval(intervalStart, i)); + result.add(new FreezePeriod(intervalStart, i)); } // Check if the last entry can be merged to the first entry to become one single // wrapped interval final int lastIndex = result.size() - 1; if (lastIndex > 0 && result.get(lastIndex).mEndDay == DAYS_IN_YEAR && result.get(0).mStartDay == 1) { - FreezeInterval wrappedInterval = new FreezeInterval(result.get(lastIndex).mStartDay, + FreezePeriod wrappedInterval = new FreezePeriod(result.get(lastIndex).mStartDay, result.get(0).mEndDay); result.set(lastIndex, wrappedInterval); result.remove(0); @@ -207,18 +259,18 @@ public class FreezeInterval { * * @hide */ - protected static void validatePeriods(List<FreezeInterval> periods) { - List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); + static void validatePeriods(List<FreezePeriod> periods) { + List<FreezePeriod> allPeriods = FreezePeriod.canonicalizePeriods(periods); if (allPeriods.size() != periods.size()) { throw SystemUpdatePolicy.ValidationFailedException.duplicateOrOverlapPeriods(); } for (int i = 0; i < allPeriods.size(); i++) { - FreezeInterval current = allPeriods.get(i); + FreezePeriod current = allPeriods.get(i); if (current.getLength() > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooLong("Freeze " + "period " + current + " is too long: " + current.getLength() + " days"); } - FreezeInterval previous = i > 0 ? allPeriods.get(i - 1) + FreezePeriod previous = i > 0 ? allPeriods.get(i - 1) : allPeriods.get(allPeriods.size() - 1); if (previous != current) { final int separation; @@ -247,7 +299,7 @@ public class FreezeInterval { * * @hide */ - protected static void validateAgainstPreviousFreezePeriod(List<FreezeInterval> periods, + static void validateAgainstPreviousFreezePeriod(List<FreezePeriod> periods, LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) { if (periods.size() == 0 || prevPeriodStart == null || prevPeriodEnd == null) { return; @@ -258,14 +310,14 @@ public class FreezeInterval { // Clock was adjusted backwards. We can continue execution though, the separation // and length validation below still works under this condition. } - List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods); + List<FreezePeriod> allPeriods = FreezePeriod.canonicalizePeriods(periods); // Given current time now, find the freeze period that's either current, or the one // that's immediately afterwards. For the later case, it might be after the year-end, // but this can only happen if there is only one freeze period. - FreezeInterval curOrNextFreezePeriod = allPeriods.get(0); - for (FreezeInterval interval : allPeriods) { + FreezePeriod curOrNextFreezePeriod = allPeriods.get(0); + for (FreezePeriod interval : allPeriods) { if (interval.contains(now) - || interval.mStartDay > FreezeInterval.dayOfYearDisregardLeapYear(now)) { + || interval.mStartDay > FreezePeriod.dayOfYearDisregardLeapYear(now)) { curOrNextFreezePeriod = interval; break; } @@ -282,7 +334,7 @@ public class FreezeInterval { // Now validate [prevPeriodStart, prevPeriodEnd] against curOrNextFreezeDates final String periodsDescription = "Prev: " + prevPeriodStart + "," + prevPeriodEnd + "; cur: " + curOrNextFreezeDates.first + "," + curOrNextFreezeDates.second; - long separation = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.first, + long separation = FreezePeriod.distanceWithoutLeapYear(curOrNextFreezeDates.first, prevPeriodEnd) - 1; if (separation > 0) { // Two intervals do not overlap, check separation @@ -292,7 +344,7 @@ public class FreezeInterval { } } else { // Two intervals overlap, check combined length - long length = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.second, + long length = FreezePeriod.distanceWithoutLeapYear(curOrNextFreezeDates.second, prevPeriodStart) + 1; if (length > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) { throw ValidationFailedException.combinedPeriodTooLong("Combined freeze period " diff --git a/android/app/admin/SystemUpdatePolicy.java b/android/app/admin/SystemUpdatePolicy.java index 47b3a81d..20eef6cc 100644 --- a/android/app/admin/SystemUpdatePolicy.java +++ b/android/app/admin/SystemUpdatePolicy.java @@ -38,9 +38,11 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.MonthDay; import java.time.ZoneId; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -51,7 +53,7 @@ import java.util.stream.Collectors; * @see DevicePolicyManager#setSystemUpdatePolicy * @see DevicePolicyManager#getSystemUpdatePolicy */ -public class SystemUpdatePolicy implements Parcelable { +public final class SystemUpdatePolicy implements Parcelable { private static final String TAG = "SystemUpdatePolicy"; /** @hide */ @@ -163,6 +165,7 @@ public class SystemUpdatePolicy implements Parcelable { ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE, ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG, ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE, + ERROR_UNKNOWN, }) @Retention(RetentionPolicy.SOURCE) @interface ValidationFailureType {} @@ -171,33 +174,38 @@ public class SystemUpdatePolicy implements Parcelable { public static final int ERROR_NONE = 0; /** + * Validation failed with unknown error. + */ + public static final int ERROR_UNKNOWN = 1; + + /** * The freeze periods contains duplicates, periods that overlap with each * other or periods whose start and end joins. */ - public static final int ERROR_DUPLICATE_OR_OVERLAP = 1; + public static final int ERROR_DUPLICATE_OR_OVERLAP = 2; /** * There exists at least one freeze period whose length exceeds 90 days. */ - public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2; + public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 3; /** * There exists some freeze period which starts within 60 days of the preceding period's * end time. */ - public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3; + public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 4; /** * The device has been in a freeze period and when combining with the new freeze period * to be set, it will result in the total freeze period being longer than 90 days. */ - public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4; + public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 5; /** * The device has been in a freeze period and some new freeze period to be set is less * than 60 days from the end of the last freeze period the device went through. */ - public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5; + public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 6; @ValidationFailureType private final int mErrorCode; @@ -272,7 +280,7 @@ public class SystemUpdatePolicy implements Parcelable { private int mMaintenanceWindowStart; private int mMaintenanceWindowEnd; - private final ArrayList<FreezeInterval> mFreezePeriods; + private final ArrayList<FreezePeriod> mFreezePeriods; private SystemUpdatePolicy() { mPolicyType = TYPE_UNKNOWN; @@ -444,12 +452,10 @@ public class SystemUpdatePolicy implements Parcelable { * requirement set above * @return this instance */ - public SystemUpdatePolicy setFreezePeriods(List<Pair<Integer, Integer>> freezePeriods) { - List<FreezeInterval> newPeriods = freezePeriods.stream().map( - p -> new FreezeInterval(p.first, p.second)).collect(Collectors.toList()); - FreezeInterval.validatePeriods(newPeriods); + public SystemUpdatePolicy setFreezePeriods(List<FreezePeriod> freezePeriods) { + FreezePeriod.validatePeriods(freezePeriods); mFreezePeriods.clear(); - mFreezePeriods.addAll(newPeriods); + mFreezePeriods.addAll(freezePeriods); return this; } @@ -458,12 +464,8 @@ public class SystemUpdatePolicy implements Parcelable { * * @return the list of freeze periods, or an empty list if none was set. */ - public List<Pair<Integer, Integer>> getFreezePeriods() { - List<Pair<Integer, Integer>> result = new ArrayList<>(mFreezePeriods.size()); - for (FreezeInterval interval : mFreezePeriods) { - result.add(new Pair<>(interval.mStartDay, interval.mEndDay)); - } - return result; + public List<FreezePeriod> getFreezePeriods() { + return Collections.unmodifiableList(mFreezePeriods); } /** @@ -472,7 +474,7 @@ public class SystemUpdatePolicy implements Parcelable { * @hide */ public Pair<LocalDate, LocalDate> getCurrentFreezePeriod(LocalDate now) { - for (FreezeInterval interval : mFreezePeriods) { + for (FreezePeriod interval : mFreezePeriods) { if (interval.contains(now)) { return interval.toCurrentOrFutureRealDates(now); } @@ -485,10 +487,10 @@ public class SystemUpdatePolicy implements Parcelable { * is not within a freeze period. */ private long timeUntilNextFreezePeriod(long now) { - List<FreezeInterval> sortedPeriods = FreezeInterval.canonicalizeIntervals(mFreezePeriods); + List<FreezePeriod> sortedPeriods = FreezePeriod.canonicalizePeriods(mFreezePeriods); LocalDate nowDate = millisToDate(now); LocalDate nextFreezeStart = null; - for (FreezeInterval interval : sortedPeriods) { + for (FreezePeriod interval : sortedPeriods) { if (interval.after(nowDate)) { nextFreezeStart = interval.toCurrentOrFutureRealDates(nowDate).first; break; @@ -506,13 +508,13 @@ public class SystemUpdatePolicy implements Parcelable { /** @hide */ public void validateFreezePeriods() { - FreezeInterval.validatePeriods(mFreezePeriods); + FreezePeriod.validatePeriods(mFreezePeriods); } /** @hide */ public void validateAgainstPreviousFreezePeriod(LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) { - FreezeInterval.validateAgainstPreviousFreezePeriod(mFreezePeriods, prevPeriodStart, + FreezePeriod.validateAgainstPreviousFreezePeriod(mFreezePeriods, prevPeriodStart, prevPeriodEnd, now); } @@ -521,10 +523,10 @@ public class SystemUpdatePolicy implements Parcelable { * updates and how long this action is valid for, given the current system update policy. Its * action could be one of the following * <ul> - * <li> {@code TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and without - * user intervention as soon as they become available. - * <li> {@code TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days - * <li> {@code TYPE_PAUSE} system updates should be postponed indefinitely until further notice + * <li> {@link #TYPE_INSTALL_AUTOMATIC} system updates should be installed immedately and + * without user intervention as soon as they become available. + * <li> {@link #TYPE_POSTPONE} system updates should be postponed for a maximum of 30 days + * <li> {@link #TYPE_PAUSE} system updates should be postponed indefinitely until further notice * </ul> * * The effective time measures how long this installation option is valid for from the queried @@ -535,18 +537,38 @@ public class SystemUpdatePolicy implements Parcelable { */ @SystemApi public static class InstallationOption { + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_INSTALL_AUTOMATIC, + TYPE_PAUSE, + TYPE_POSTPONE + }) + @Retention(RetentionPolicy.SOURCE) + @interface InstallationOptionType {} + + @InstallationOptionType private final int mType; private long mEffectiveTime; - InstallationOption(int type, long effectiveTime) { + InstallationOption(@InstallationOptionType int type, long effectiveTime) { this.mType = type; this.mEffectiveTime = effectiveTime; } - public int getType() { + /** + * Returns the type of the current installation option, could be one of + * {@link #TYPE_INSTALL_AUTOMATIC}, {@link #TYPE_POSTPONE} and {@link #TYPE_PAUSE}. + * @return type of installation option. + */ + public @InstallationOptionType int getType() { return mType; } + /** + * Returns how long the current installation option in effective for, starting from the time + * of query. + * @return the effective time in milliseconds. + */ public long getEffectiveTime() { return mEffectiveTime; } @@ -667,9 +689,11 @@ public class SystemUpdatePolicy implements Parcelable { int freezeCount = mFreezePeriods.size(); dest.writeInt(freezeCount); for (int i = 0; i < freezeCount; i++) { - FreezeInterval interval = mFreezePeriods.get(i); - dest.writeInt(interval.mStartDay); - dest.writeInt(interval.mEndDay); + FreezePeriod interval = mFreezePeriods.get(i); + dest.writeInt(interval.getStart().getMonthValue()); + dest.writeInt(interval.getStart().getDayOfMonth()); + dest.writeInt(interval.getEnd().getMonthValue()); + dest.writeInt(interval.getEnd().getDayOfMonth()); } } @@ -686,8 +710,9 @@ public class SystemUpdatePolicy implements Parcelable { int freezeCount = source.readInt(); policy.mFreezePeriods.ensureCapacity(freezeCount); for (int i = 0; i < freezeCount; i++) { - policy.mFreezePeriods.add( - new FreezeInterval(source.readInt(), source.readInt())); + MonthDay start = MonthDay.of(source.readInt(), source.readInt()); + MonthDay end = MonthDay.of(source.readInt(), source.readInt()); + policy.mFreezePeriods.add(new FreezePeriod(start, end)); } return policy; } @@ -730,9 +755,9 @@ public class SystemUpdatePolicy implements Parcelable { if (!parser.getName().equals(KEY_FREEZE_TAG)) { continue; } - policy.mFreezePeriods.add(new FreezeInterval( - Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_START)), - Integer.parseInt(parser.getAttributeValue(null, KEY_FREEZE_END)))); + policy.mFreezePeriods.add(new FreezePeriod( + MonthDay.parse(parser.getAttributeValue(null, KEY_FREEZE_START)), + MonthDay.parse(parser.getAttributeValue(null, KEY_FREEZE_END)))); } return policy; } @@ -751,10 +776,10 @@ public class SystemUpdatePolicy implements Parcelable { out.attribute(null, KEY_INSTALL_WINDOW_START, Integer.toString(mMaintenanceWindowStart)); out.attribute(null, KEY_INSTALL_WINDOW_END, Integer.toString(mMaintenanceWindowEnd)); for (int i = 0; i < mFreezePeriods.size(); i++) { - FreezeInterval interval = mFreezePeriods.get(i); + FreezePeriod interval = mFreezePeriods.get(i); out.startTag(null, KEY_FREEZE_TAG); - out.attribute(null, KEY_FREEZE_START, Integer.toString(interval.mStartDay)); - out.attribute(null, KEY_FREEZE_END, Integer.toString(interval.mEndDay)); + out.attribute(null, KEY_FREEZE_START, interval.getStart().toString()); + out.attribute(null, KEY_FREEZE_END, interval.getEnd().toString()); out.endTag(null, KEY_FREEZE_TAG); } } diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java index bf3398ad..4336f184 100644 --- a/android/app/slice/Slice.java +++ b/android/app/slice/Slice.java @@ -21,19 +21,13 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; -import android.content.ContentResolver; -import android.content.Context; -import android.content.IContentProvider; -import android.content.Intent; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -575,45 +569,4 @@ public final class Slice implements Parcelable { } return sb.toString(); } - - /** - * @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) { - 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(resolver.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); - } - } - - /** - * @deprecated TO BE REMOVED. - */ - @Deprecated - public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent, - @NonNull List<SliceSpec> supportedSpecs) { - return context.getSystemService(SliceManager.class).bindSlice(intent, supportedSpecs); - } } diff --git a/android/app/slice/SliceManager.java b/android/app/slice/SliceManager.java index 0285e9f9..ad49437f 100644 --- a/android/app/slice/SliceManager.java +++ b/android/app/slice/SliceManager.java @@ -16,6 +16,8 @@ package android.app.slice; +import static android.content.pm.PackageManager.PERMISSION_DENIED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -38,6 +40,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.UserHandle; +import android.util.ArraySet; import android.util.Log; import com.android.internal.util.Preconditions; @@ -47,6 +50,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; /** * Class to handle interactions with {@link Slice}s. @@ -101,22 +105,6 @@ public class SliceManager { private final IBinder mToken = new Binder(); /** - * 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 { @@ -140,7 +128,7 @@ public class SliceManager { * @see Intent#ACTION_ASSIST * @see Intent#CATEGORY_HOME */ - public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) { + public void pinSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> specs) { try { mService.pinSlice(mContext.getPackageName(), uri, specs.toArray(new SliceSpec[specs.size()]), mToken); @@ -150,6 +138,14 @@ public class SliceManager { } /** + * @deprecated TO BE REMOVED + */ + @Deprecated + public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) { + pinSlice(uri, new ArraySet<>(specs)); + } + + /** * Remove a pin for a slice. * <p> * If the slice has no other pins/callbacks then the slice will be unpinned. @@ -189,9 +185,10 @@ public class SliceManager { * into account all clients and returns only specs supported by all. * @see SliceSpec */ - public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) { + public @NonNull Set<SliceSpec> getPinnedSpecs(Uri uri) { try { - return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName())); + return new ArraySet<>(Arrays.asList(mService.getPinnedSpecs(uri, + mContext.getPackageName()))); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -240,7 +237,7 @@ public class SliceManager { * @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) { + public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> supportedSpecs) { Preconditions.checkNotNull(uri, "uri"); ContentResolver resolver = mContext.getContentResolver(); try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { @@ -265,6 +262,14 @@ public class SliceManager { } /** + * @deprecated TO BE REMOVED + */ + @Deprecated + public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) { + return bindSlice(uri, new ArraySet<>(supportedSpecs)); + } + + /** * Turns a slice intent into a slice uri. Expects an explicit intent. * <p> * This goes through a several stage resolution process to determine if any slice @@ -272,12 +277,12 @@ public class SliceManager { * <ol> * <li> If the intent contains data that {@link ContentResolver#getType} is * {@link SliceProvider#SLICE_TYPE} then the data will be returned.</li> - * <li>If the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then - * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result - * will be returned.</li> - * <li>Lastly, if the intent explicitly points at an activity, and that activity has + * <li>If the intent explicitly points at an activity, and that activity has * meta-data for key {@link #SLICE_METADATA_KEY}, then the Uri specified there will be * returned.</li> + * <li>Lastly, if the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then + * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result + * will be returned.</li> * <li>If no slice is found, then {@code null} is returned.</li> * </ol> * @param intent The intent associated with a slice. @@ -287,37 +292,12 @@ public class SliceManager { * @see Intent */ public @Nullable Uri mapIntentToUri(@NonNull Intent intent) { - Preconditions.checkNotNull(intent, "intent"); - Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null - || intent.getData() != null, - "Slice intent must be explicit %s", intent); ContentResolver resolver = mContext.getContentResolver(); - - // Check if the intent has data for the slice uri on it and use that - final Uri intentData = intent.getData(); - if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { - return intentData; - } + final Uri staticUri = resolveStatic(intent, resolver); + if (staticUri != null) return staticUri; // Otherwise ask the app - Intent queryIntent = new Intent(intent); - if (!queryIntent.hasCategory(CATEGORY_SLICE)) { - queryIntent.addCategory(CATEGORY_SLICE); - } - List<ResolveInfo> providers = - mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0); - if (providers == null || providers.isEmpty()) { - // There are no providers, see if this activity has a direct link. - ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent, - PackageManager.GET_META_DATA); - if (resolve != null && resolve.activityInfo != null - && resolve.activityInfo.metaData != null - && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { - return Uri.parse( - resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY)); - } - return null; - } - String authority = providers.get(0).providerInfo.authority; + String authority = getAuthority(intent); + if (authority == null) return null; Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).build(); try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { @@ -338,10 +318,43 @@ public class SliceManager { } } + private String getAuthority(Intent intent) { + Intent queryIntent = new Intent(intent); + if (!queryIntent.hasCategory(CATEGORY_SLICE)) { + queryIntent.addCategory(CATEGORY_SLICE); + } + List<ResolveInfo> providers = + mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0); + return providers != null && !providers.isEmpty() ? providers.get(0).providerInfo.authority + : null; + } + + private Uri resolveStatic(@NonNull Intent intent, ContentResolver resolver) { + Preconditions.checkNotNull(intent, "intent"); + Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null + || intent.getData() != null, + "Slice intent must be explicit %s", intent); + + // Check if the intent has data for the slice uri on it and use that + final Uri intentData = intent.getData(); + if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { + return intentData; + } + // There are no providers, see if this activity has a direct link. + ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent, + PackageManager.GET_META_DATA); + if (resolve != null && resolve.activityInfo != null + && resolve.activityInfo.metaData != null + && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { + return Uri.parse( + resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY)); + } + return null; + } + /** - * 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}. + * Turns a slice intent into slice content. Is a shortcut to perform the action + * of both {@link #mapIntentToUri(Intent)} and {@link #bindSlice(Uri, List)} at once. * * @param intent The intent associated with a slice. * @param supportedSpecs List of supported specs. @@ -351,34 +364,17 @@ public class SliceManager { * @see Intent */ public @Nullable Slice bindSlice(@NonNull Intent intent, - @NonNull List<SliceSpec> supportedSpecs) { + @NonNull Set<SliceSpec> supportedSpecs) { Preconditions.checkNotNull(intent, "intent"); Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null || intent.getData() != null, "Slice intent must be explicit %s", intent); ContentResolver resolver = mContext.getContentResolver(); - - // Check if the intent has data for the slice uri on it and use that - final Uri intentData = intent.getData(); - if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { - return bindSlice(intentData, supportedSpecs); - } + final Uri staticUri = resolveStatic(intent, resolver); + if (staticUri != null) return bindSlice(staticUri, supportedSpecs); // Otherwise ask the app - List<ResolveInfo> providers = - mContext.getPackageManager().queryIntentContentProviders(intent, 0); - if (providers == null || providers.isEmpty()) { - // There are no providers, see if this activity has a direct link. - ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent, - PackageManager.GET_META_DATA); - if (resolve != null && resolve.activityInfo != null - && resolve.activityInfo.metaData != null - && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { - return bindSlice(Uri.parse(resolve.activityInfo.metaData - .getString(SLICE_METADATA_KEY)), supportedSpecs); - } - return null; - } - String authority = providers.get(0).providerInfo.authority; + String authority = getAuthority(intent); + if (authority == null) return null; Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).build(); try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { @@ -387,8 +383,6 @@ public class SliceManager { } Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); - extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, - new ArrayList<>(supportedSpecs)); final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras); if (res == null) { return null; @@ -402,6 +396,16 @@ public class SliceManager { } /** + * @deprecated TO BE REMOVED. + */ + @Deprecated + @Nullable + public Slice bindSlice(@NonNull Intent intent, + @NonNull List<SliceSpec> supportedSpecs) { + return bindSlice(intent, new ArraySet<>(supportedSpecs)); + } + + /** * Determine whether a particular process and user ID has been granted * permission to access a specific slice URI. * @@ -417,9 +421,11 @@ public class SliceManager { * @see #grantSlicePermission(String, Uri) */ public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) { - // TODO: Switch off Uri permissions. - return mContext.checkUriPermission(uri, pid, uid, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + try { + return mService.checkSlicePermission(uri, null, pid, uid, null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -431,11 +437,11 @@ public class SliceManager { * @see #revokeSlicePermission */ public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { - // TODO: Switch off Uri permissions. - mContext.grantUriPermission(toPackage, uri, - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + try { + mService.grantSlicePermission(mContext.getPackageName(), toPackage, uri); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -453,11 +459,11 @@ public class SliceManager { * @see #grantSlicePermission */ public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { - // TODO: Switch off Uri permissions. - mContext.revokeUriPermission(toPackage, uri, - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + try { + mService.revokeSlicePermission(mContext.getPackageName(), toPackage, uri); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -478,16 +484,6 @@ public class SliceManager { 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); - // Notify a change has happened because we just granted a permission. - mContext.getContentResolver().notifyChange(uri, null); - } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java index fe5742d6..d369272d 100644 --- a/android/app/slice/SliceProvider.java +++ b/android/app/slice/SliceProvider.java @@ -44,6 +44,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; /** * A SliceProvider allows an app to provide content to be displayed in system spaces. This content @@ -197,6 +198,14 @@ public abstract class SliceProvider extends ContentProvider { * @see {@link Slice}. * @see {@link Slice#HINT_PARTIAL} */ + public Slice onBindSlice(Uri sliceUri, Set<SliceSpec> supportedSpecs) { + return onBindSlice(sliceUri, new ArrayList<>(supportedSpecs)); + } + + /** + * @deprecated TO BE REMOVED + */ + @Deprecated public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) { return null; } diff --git a/android/app/usage/NetworkStats.java b/android/app/usage/NetworkStats.java index 7252f028..216a4a09 100644 --- a/android/app/usage/NetworkStats.java +++ b/android/app/usage/NetworkStats.java @@ -237,20 +237,26 @@ public final class NetworkStats implements AutoCloseable { DEFAULT_NETWORK_YES }) @Retention(RetentionPolicy.SOURCE) - public @interface DefaultNetwork {} + public @interface DefaultNetworkStatus {} /** - * Combined usage for this network regardless of whether it was the active default network. + * Combined usage for this network regardless of default network status. */ public static final int DEFAULT_NETWORK_ALL = -1; /** - * Usage that occurs while this network is not the active default network. + * Usage that occurs while this network is not a default network. + * + * <p>This implies that the app responsible for this usage requested that it occur on a + * specific network different from the one(s) the system would have selected for it. */ public static final int DEFAULT_NETWORK_NO = 0x1; /** - * Usage that occurs while this network is the active default network. + * Usage that occurs while this network is a default network. + * + * <p>This implies that the app either did not select a specific network for this usage, + * or it selected a network that the system could have selected for app traffic. */ public static final int DEFAULT_NETWORK_YES = 0x2; @@ -262,7 +268,7 @@ public final class NetworkStats implements AutoCloseable { private int mUid; private int mTag; private int mState; - private int mDefaultNetwork; + private int mDefaultNetworkStatus; private int mMetered; private int mRoaming; private long mBeginTimeStamp; @@ -323,8 +329,9 @@ public final class NetworkStats implements AutoCloseable { return 0; } - private static @DefaultNetwork int convertDefaultNetwork(int defaultNetwork) { - switch (defaultNetwork) { + private static @DefaultNetworkStatus int convertDefaultNetworkStatus( + int defaultNetworkStatus) { + switch (defaultNetworkStatus) { 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; @@ -397,18 +404,15 @@ public final class NetworkStats implements AutoCloseable { } /** - * Default network state. One of the following values:<p/> + * Default network status. 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; + public @DefaultNetworkStatus int getDefaultNetworkStatus() { + return mDefaultNetworkStatus; } /** @@ -605,7 +609,7 @@ 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( + bucketOut.mDefaultNetworkStatus = Bucket.convertDefaultNetworkStatus( mRecycledSummaryEntry.defaultNetwork); bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered); bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming); @@ -657,7 +661,7 @@ public final class NetworkStats implements AutoCloseable { bucketOut.mUid = Bucket.convertUid(getUid()); bucketOut.mTag = Bucket.convertTag(mTag); bucketOut.mState = mState; - bucketOut.mDefaultNetwork = Bucket.DEFAULT_NETWORK_ALL; + bucketOut.mDefaultNetworkStatus = 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 b2fe9586..0b21196f 100644 --- a/android/app/usage/NetworkStatsManager.java +++ b/android/app/usage/NetworkStatsManager.java @@ -35,6 +35,7 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.util.DataUnit; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -95,6 +96,15 @@ public class NetworkStatsManager { /** @hide */ public static final int CALLBACK_RELEASED = 1; + /** + * Minimum data usage threshold for registering usage callbacks. + * + * Requests registered with a threshold lower than this will only be triggered once this minimum + * is reached. + * @hide + */ + public static final long MIN_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(2); + private final Context mContext; private final INetworkStatsService mService; @@ -305,6 +315,8 @@ public class NetworkStatsManager { * {@link java.lang.System#currentTimeMillis}. * @param uid UID of app * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for no tags. + * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate + * traffic from all states. * @return Statistics object or null if an error happened during statistics collection. * @throws SecurityException if permissions are insufficient to read network statistics. */ diff --git a/android/app/usage/TimeSparseArray.java b/android/app/usage/TimeSparseArray.java index 9ef88e41..4ec0e9e4 100644 --- a/android/app/usage/TimeSparseArray.java +++ b/android/app/usage/TimeSparseArray.java @@ -88,7 +88,7 @@ public class TimeSparseArray<E> extends LongSparseArray<E> { key++; keyIndex++; } - if (key >= origKey + 10) { + if (key >= origKey + 100) { Slog.w(TAG, "Value " + value + " supposed to be inserted at " + origKey + " displaced to " + key); } diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java index 84f57a30..503ca6c3 100644 --- a/android/app/usage/UsageEvents.java +++ b/android/app/usage/UsageEvents.java @@ -111,7 +111,7 @@ public final class UsageEvents implements Parcelable { /** * An event type denoting a change in App Standby Bucket. The new bucket can be - * retrieved by calling {@link #getStandbyBucket()}. + * retrieved by calling {@link #getAppStandbyBucket()}. * * @see UsageStatsManager#getAppStandbyBucket() */ @@ -326,13 +326,23 @@ public final class UsageEvents implements Parcelable { * Returns the standby bucket of the app, if the event is of type * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. * @return the standby bucket associated with the event. - * + * @hide */ public int getStandbyBucket() { return (mBucketAndReason & 0xFFFF0000) >>> 16; } /** + * Returns the standby bucket of the app, if the event is of type + * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. + * @return the standby bucket associated with the event. + * + */ + public int getAppStandbyBucket() { + return (mBucketAndReason & 0xFFFF0000) >>> 16; + } + + /** * Returns the reason for the bucketing, if the event is of type * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include * the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there |