summaryrefslogtreecommitdiff
path: root/android/app
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2018-04-15 00:41:15 -0400
committerJustin Klaassen <justinklaassen@google.com>2018-04-15 00:41:15 -0400
commitb8042fc9b036db0a6692ca853428fc6ab1e60892 (patch)
tree82669ea5d75238758e22d379a42baeada526219e /android/app
parent4d01eeaffaa720e4458a118baa137a11614f00f7 (diff)
downloadandroid-28-androidx-preference-release.tar.gz
/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')
-rw-r--r--android/app/ActivityOptions.java3
-rw-r--r--android/app/ActivityThread.java2
-rw-r--r--android/app/ApplicationPackageManager.java32
-rw-r--r--android/app/BroadcastOptions.java28
-rw-r--r--android/app/ContextImpl.java19
-rw-r--r--android/app/Notification.java275
-rw-r--r--android/app/NotificationChannel.java23
-rw-r--r--android/app/NotificationManager.java67
-rw-r--r--android/app/Person.java270
-rw-r--r--android/app/RemoteAction.java1
-rw-r--r--android/app/StatsManager.java213
-rw-r--r--android/app/SystemServiceRegistry.java152
-rw-r--r--android/app/UiAutomation.java11
-rw-r--r--android/app/WallpaperManager.java7
-rw-r--r--android/app/admin/DevicePolicyManager.java32
-rw-r--r--android/app/admin/FreezePeriod.java (renamed from android/app/admin/FreezeInterval.java)120
-rw-r--r--android/app/admin/SystemUpdatePolicy.java105
-rw-r--r--android/app/slice/Slice.java47
-rw-r--r--android/app/slice/SliceManager.java202
-rw-r--r--android/app/slice/SliceProvider.java9
-rw-r--r--android/app/usage/NetworkStats.java34
-rw-r--r--android/app/usage/NetworkStatsManager.java12
-rw-r--r--android/app/usage/TimeSparseArray.java2
-rw-r--r--android/app/usage/UsageEvents.java14
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