Merge "[DO NOT MERGE] [Companion] Call notifyDataSetChanged on main thread" into pi-dev
diff --git a/Android.mk b/Android.mk
index d7d9c90..88394d6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -196,7 +196,7 @@
-since $(SRC_API_DIR)/25.txt 25 \
-since $(SRC_API_DIR)/26.txt 26 \
-since $(SRC_API_DIR)/27.txt 27 \
- -since ./frameworks/base/api/current.txt P \
+ -since $(SRC_API_DIR)/28.txt 28 \
-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 \
-overview $(LOCAL_PATH)/core/java/overview.html \
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 11b5d9c..2dcd03a 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -2282,6 +2282,7 @@
Landroid/view/Choreographer;->scheduleVsyncLocked()V
Landroid/view/Choreographer;->USE_VSYNC:Z
Landroid/view/ContextThemeWrapper;->getThemeResId()I
+Landroid/view/ContextThemeWrapper;->mInflater:Landroid/view/LayoutInflater;
Landroid/view/ContextThemeWrapper;->mResources:Landroid/content/res/Resources;
Landroid/view/ContextThemeWrapper;->mTheme:Landroid/content/res/Resources$Theme;
Landroid/view/ContextThemeWrapper;->mThemeResource:I
@@ -3580,6 +3581,7 @@
Ljava/util/PriorityQueue;->size:I
Ljava/util/Random;->seedUniquifier()J
Ljava/util/regex/Matcher;->appendPos:I
+Ljava/util/UUID;->leastSigBits:J
Ljava/util/UUID;->mostSigBits:J
Ljava/util/Vector;->elementData(I)Ljava/lang/Object;
Ljava/util/zip/Deflater;->buf:[B
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 0f2a11a..cd12710 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -87,6 +87,7 @@
boolean onlyHasDefaultChannel(String pkg, int uid);
ParceledListSlice getRecentNotifyingAppsForUser(int userId);
int getBlockedAppCount(int userId);
+ boolean areChannelsBypassingDnd();
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index d42fb4c..c7618fe 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1210,11 +1210,16 @@
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
- String pkg = intent.getComponent().getPackageName();
+ String pkg = intent != null && intent.getComponent() != null
+ ? intent.getComponent().getPackageName() : null;
return getFactory(pkg).instantiateActivity(cl, className, intent);
}
private AppComponentFactory getFactory(String pkg) {
+ if (pkg == null) {
+ Log.e(TAG, "No pkg specified, disabling AppComponentFactory");
+ return AppComponentFactory.DEFAULT;
+ }
if (mThread == null) {
Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation,"
+ " disabling AppComponentFactory", new Throwable());
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 22da924..4f88a03 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -91,6 +91,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
/**
* A class that represents how a persistent notification is to be presented to
@@ -2306,6 +2307,45 @@
}
/**
+ * Note all {@link Uri} that are referenced internally, with the expectation
+ * that Uri permission grants will need to be issued to ensure the recipient
+ * of this object is able to render its contents.
+ *
+ * @hide
+ */
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ visitor.accept(sound);
+
+ if (tickerView != null) tickerView.visitUris(visitor);
+ if (contentView != null) contentView.visitUris(visitor);
+ if (bigContentView != null) bigContentView.visitUris(visitor);
+ if (headsUpContentView != null) headsUpContentView.visitUris(visitor);
+
+ if (extras != null) {
+ visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI));
+ visitor.accept(extras.getParcelable(EXTRA_BACKGROUND_IMAGE_URI));
+ }
+
+ if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) {
+ final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+ if (!ArrayUtils.isEmpty(messages)) {
+ for (MessagingStyle.Message message : MessagingStyle.Message
+ .getMessagesFromBundleArray(messages)) {
+ visitor.accept(message.getDataUri());
+ }
+ }
+
+ final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
+ if (!ArrayUtils.isEmpty(historic)) {
+ for (MessagingStyle.Message message : MessagingStyle.Message
+ .getMessagesFromBundleArray(historic)) {
+ visitor.accept(message.getDataUri());
+ }
+ }
+ }
+ }
+
+ /**
* Removes heavyweight parts of the Notification object for archival or for sending to
* listeners when the full contents are not necessary.
* @hide
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 757fc64..93be932 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1167,6 +1167,23 @@
public final int suppressedVisualEffects;
/**
+ * @hide
+ */
+ public static final int STATE_CHANNELS_BYPASSING_DND = 1 << 0;
+
+ /**
+ * @hide
+ */
+ public static final int STATE_UNSET = -1;
+
+ /**
+ * Notification state information that is necessary to determine Do Not Disturb behavior.
+ * Bitmask of STATE_* constants.
+ * @hide
+ */
+ public final int state;
+
+ /**
* Constructs a policy for Do Not Disturb priority mode behavior.
*
* <p>
@@ -1181,7 +1198,7 @@
*/
public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders) {
this(priorityCategories, priorityCallSenders, priorityMessageSenders,
- SUPPRESSED_EFFECTS_UNSET);
+ SUPPRESSED_EFFECTS_UNSET, STATE_UNSET);
}
/**
@@ -1219,11 +1236,23 @@
this.priorityCallSenders = priorityCallSenders;
this.priorityMessageSenders = priorityMessageSenders;
this.suppressedVisualEffects = suppressedVisualEffects;
+ this.state = STATE_UNSET;
+ }
+
+ /** @hide */
+ public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders,
+ int suppressedVisualEffects, int state) {
+ this.priorityCategories = priorityCategories;
+ this.priorityCallSenders = priorityCallSenders;
+ this.priorityMessageSenders = priorityMessageSenders;
+ this.suppressedVisualEffects = suppressedVisualEffects;
+ this.state = state;
}
/** @hide */
public Policy(Parcel source) {
- this(source.readInt(), source.readInt(), source.readInt(), source.readInt());
+ this(source.readInt(), source.readInt(), source.readInt(), source.readInt(),
+ source.readInt());
}
@Override
@@ -1232,6 +1261,7 @@
dest.writeInt(priorityCallSenders);
dest.writeInt(priorityMessageSenders);
dest.writeInt(suppressedVisualEffects);
+ dest.writeInt(state);
}
@Override
@@ -1264,6 +1294,8 @@
+ ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders)
+ ",suppressedVisualEffects="
+ suppressedEffectsToString(suppressedVisualEffects)
+ + ",areChannelsBypassingDnd=" + (((state & STATE_CHANNELS_BYPASSING_DND) != 0)
+ ? "true" : "false")
+ "]";
}
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index ead6c25..fc58533 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -396,4 +396,14 @@
mPackage = pkg;
mClass = in.readString();
}
+
+ /**
+ * Interface for classes associated with a component name.
+ * @hide
+ */
+ @FunctionalInterface
+ public interface WithComponentName {
+ /** Return the associated component name. */
+ ComponentName getComponentName();
+ }
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f608fcb..206ed71 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2289,9 +2289,8 @@
/**
* Activity Action: Started to show more details about why an application was suspended.
*
- * <p>Whenever the system detects an activity launch for a suspended app, it shows a dialog to
- * the user to inform them of the state and present them an affordance to start this activity
- * action to show more details about the reason for suspension.
+ * <p>Whenever the system detects an activity launch for a suspended app, this action can
+ * be used to show more details about the reason for suspension.
*
* <p>Apps holding {@link android.Manifest.permission#SUSPEND_APPS} must declare an activity
* handling this intent and protect it with
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index ded11cfd..2d521e9 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -28,10 +28,15 @@
public class SyncStatusInfo implements Parcelable {
private static final String TAG = "Sync";
- static final int VERSION = 5;
+ static final int VERSION = 6;
private static final int MAX_EVENT_COUNT = 10;
+ /**
+ * Number of sync sources. KEEP THIS AND SyncStorageEngine.SOURCES IN SYNC.
+ */
+ private static final int SOURCE_COUNT = 6;
+
public final int authorityId;
/**
@@ -120,7 +125,10 @@
public long initialFailureTime;
public boolean pending;
public boolean initialize;
-
+
+ public final long[] perSourceLastSuccessTimes = new long[SOURCE_COUNT];
+ public final long[] perSourceLastFailureTimes = new long[SOURCE_COUNT];
+
// Warning: It is up to the external caller to ensure there are
// no race conditions when accessing this list
private ArrayList<Long> periodicSyncTimes;
@@ -191,6 +199,10 @@
todayStats.writeToParcel(parcel);
yesterdayStats.writeToParcel(parcel);
+
+ // Version 6.
+ parcel.writeLongArray(perSourceLastSuccessTimes);
+ parcel.writeLongArray(perSourceLastFailureTimes);
}
public SyncStatusInfo(Parcel parcel) {
@@ -260,6 +272,10 @@
todayStats.readFromParcel(parcel);
yesterdayStats.readFromParcel(parcel);
}
+ if (version >= 6) {
+ parcel.readLongArray(perSourceLastSuccessTimes);
+ parcel.readLongArray(perSourceLastFailureTimes);
+ }
}
public SyncStatusInfo(SyncStatusInfo other) {
@@ -284,6 +300,13 @@
}
mLastEventTimes.addAll(other.mLastEventTimes);
mLastEvents.addAll(other.mLastEvents);
+
+ copy(perSourceLastSuccessTimes, other.perSourceLastSuccessTimes);
+ copy(perSourceLastFailureTimes, other.perSourceLastFailureTimes);
+ }
+
+ private static void copy(long[] to, long[] from) {
+ System.arraycopy(from, 0, to, 0, to.length);
}
public void setPeriodicSyncTime(int index, long when) {
@@ -332,6 +355,34 @@
return mLastEvents.get(i);
}
+ /** Call this when a sync has succeeded. */
+ public void setLastSuccess(int source, long lastSyncTime) {
+ lastSuccessTime = lastSyncTime;
+ lastSuccessSource = source;
+ lastFailureTime = 0;
+ lastFailureSource = -1;
+ lastFailureMesg = null;
+ initialFailureTime = 0;
+
+ if (0 <= source && source < perSourceLastSuccessTimes.length) {
+ perSourceLastSuccessTimes[source] = lastSyncTime;
+ }
+ }
+
+ /** Call this when a sync has failed. */
+ public void setLastFailure(int source, long lastSyncTime, String failureMessage) {
+ lastFailureTime = lastSyncTime;
+ lastFailureSource = source;
+ lastFailureMesg = failureMessage;
+ if (initialFailureTime == 0) {
+ initialFailureTime = lastSyncTime;
+ }
+
+ if (0 <= source && source < perSourceLastFailureTimes.length) {
+ perSourceLastFailureTimes[source] = lastSyncTime;
+ }
+ }
+
public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
public SyncStatusInfo createFromParcel(Parcel in) {
return new SyncStatusInfo(in);
@@ -356,7 +407,7 @@
}
/**
- * If the last reset was not not today, move today's stats to yesterday's and clear today's.
+ * If the last reset was not today, move today's stats to yesterday's and clear today's.
*/
public void maybeResetTodayStats(boolean clockValid, boolean force) {
final long now = System.currentTimeMillis();
@@ -391,4 +442,4 @@
return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
&& c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 8223363..8717601 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -229,7 +229,7 @@
* <p>A suspending app with the permission {@code android.permission.SUSPEND_APPS} can
* optionally provide a {@link Bundle} of extra information that it deems helpful for the
* launcher to handle the suspended state of these packages. The contents of this
- * {@link Bundle} supposed to be a contract between the suspending app and the launcher.
+ * {@link Bundle} are supposed to be a contract between the suspending app and the launcher.
*
* @param packageNames The names of the packages that have just been suspended.
* @param user the user for which the given packages were suspended.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index db93e17..1d497c2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -68,6 +68,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Locale;
/**
* Class for retrieving various kinds of information related to the application
@@ -5527,15 +5528,23 @@
*
* <p>It doesn't remove the data or the actual package file. The application's notifications
* will be hidden, any of its started activities will be stopped and it will not be able to
- * show toasts or dialogs or ring the device. When the user tries to launch a suspended app, a
- * system dialog with the given {@code dialogMessage} will be shown instead.</p>
+ * show toasts or system alert windows or ring the device.
+ *
+ * <p>When the user tries to launch a suspended app, a system dialog with the given
+ * {@code dialogMessage} will be shown instead. Since the message is supplied to the system as
+ * a {@link String}, the caller needs to take care of localization as needed.
+ * The dialog message can optionally contain a placeholder for the name of the suspended app.
+ * The system uses {@link String#format(Locale, String, Object...) String.format} to insert the
+ * app name into the message, so an example format string could be {@code "The app %1$s is
+ * currently suspended"}. This makes it easier for callers to provide a single message which
+ * works for all the packages being suspended in a single call.
*
* <p>The package must already be installed. If the package is uninstalled while suspended
* the package will no longer be suspended. </p>
*
* <p>Optionally, the suspending app can provide extra information in the form of
* {@link PersistableBundle} objects to be shared with the apps being suspended and the
- * launcher to support customization that they might need to handle the suspended state. </p>
+ * launcher to support customization that they might need to handle the suspended state.
*
* <p>The caller must hold {@link Manifest.permission#SUSPEND_APPS} or
* {@link Manifest.permission#MANAGE_USERS} to use this api.</p>
@@ -5552,8 +5561,8 @@
* @param dialogMessage The message to be displayed to the user, when they try to launch a
* suspended app.
*
- * @return an array of package names for which the suspended status is not set as requested in
- * this method.
+ * @return an array of package names for which the suspended status could not be set as
+ * requested in this method.
*
* @hide
*/
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1d232bf..0b4b921 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -4447,8 +4447,7 @@
pw.println(sb.toString());
}
- final long dischargeScreenOnCount =
- dischargeCount - dischargeScreenOffCount - dischargeScreenDozeCount;
+ final long dischargeScreenOnCount = dischargeCount - dischargeScreenOffCount;
if (dischargeScreenOnCount >= 0) {
sb.setLength(0);
sb.append(prefix);
diff --git a/core/java/android/os/ISchedulingPolicyService.aidl b/core/java/android/os/ISchedulingPolicyService.aidl
index efcf59a..78d299a 100644
--- a/core/java/android/os/ISchedulingPolicyService.aidl
+++ b/core/java/android/os/ISchedulingPolicyService.aidl
@@ -31,4 +31,13 @@
*/
int requestPriority(int pid, int tid, int prio, boolean isForApp);
+ /**
+ * Move media.codec process between SP_FOREGROUND and SP_TOP_APP.
+ * When 'enable' is 'true', server will attempt to move media.codec process
+ * from SP_FOREGROUND into SP_TOP_APP cpuset. A valid 'client' must be
+ * provided for the server to receive death notifications. When 'enable'
+ * is 'false', server will attempt to move media.codec process back to
+ * the original cpuset, and 'client' is ignored in this case.
+ */
+ int requestCpusetBoost(boolean enable, IBinder client);
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 21c1263..1d4d4ce 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -416,6 +416,13 @@
**/
public static final int THREAD_GROUP_RT_APP = 6;
+ /**
+ * Thread group for bound foreground services that should
+ * have additional CPU restrictions during screen off
+ * @hide
+ **/
+ public static final int THREAD_GROUP_RESTRICTED = 7;
+
public static final int SIGNAL_QUIT = 3;
public static final int SIGNAL_KILL = 9;
public static final int SIGNAL_USR1 = 10;
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 673da50..6994033 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -475,11 +475,14 @@
* @param exemptions List of hidden API exemption prefixes. Any matching members are treated as
* whitelisted/public APIs (i.e. allowed, no logging of usage).
*/
- public void setApiBlacklistExemptions(List<String> exemptions) {
+ public boolean setApiBlacklistExemptions(List<String> exemptions) {
synchronized (mLock) {
mApiBlacklistExemptions = exemptions;
- maybeSetApiBlacklistExemptions(primaryZygoteState, true);
- maybeSetApiBlacklistExemptions(secondaryZygoteState, true);
+ boolean ok = maybeSetApiBlacklistExemptions(primaryZygoteState, true);
+ if (ok) {
+ ok = maybeSetApiBlacklistExemptions(secondaryZygoteState, true);
+ }
+ return ok;
}
}
@@ -499,12 +502,13 @@
}
@GuardedBy("mLock")
- private void maybeSetApiBlacklistExemptions(ZygoteState state, boolean sendIfEmpty) {
+ private boolean maybeSetApiBlacklistExemptions(ZygoteState state, boolean sendIfEmpty) {
if (state == null || state.isClosed()) {
- return;
+ Slog.e(LOG_TAG, "Can't set API blacklist exemptions: no zygote connection");
+ return false;
}
if (!sendIfEmpty && mApiBlacklistExemptions.isEmpty()) {
- return;
+ return true;
}
try {
state.writer.write(Integer.toString(mApiBlacklistExemptions.size() + 1));
@@ -520,8 +524,11 @@
if (status != 0) {
Slog.e(LOG_TAG, "Failed to set API blacklist exemptions; status " + status);
}
+ return true;
} catch (IOException ioe) {
Slog.e(LOG_TAG, "Failed to set API blacklist exemptions", ioe);
+ mApiBlacklistExemptions = Collections.emptyList();
+ return false;
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index e3f4ad1..309fa4a 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -95,6 +95,7 @@
private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
private static final boolean DEFAULT_ALLOW_SCREEN_OFF = false;
private static final boolean DEFAULT_ALLOW_SCREEN_ON = false;
+ private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false;
private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS =
Policy.getAllSuppressedVisualEffects();
@@ -118,6 +119,8 @@
private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
private static final String DISALLOW_TAG = "disallow";
private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
+ private static final String STATE_TAG = "state";
+ private static final String STATE_ATT_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd";
private static final String CONDITION_ATT_ID = "id";
private static final String CONDITION_ATT_SUMMARY = "summary";
@@ -154,6 +157,7 @@
public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS;
public boolean allowWhenScreenOff = DEFAULT_ALLOW_SCREEN_OFF;
public boolean allowWhenScreenOn = DEFAULT_ALLOW_SCREEN_ON;
+ public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND;
public int version;
public ZenRule manualRule;
@@ -187,6 +191,7 @@
allowMedia = source.readInt() == 1;
allowSystem = source.readInt() == 1;
suppressedVisualEffects = source.readInt();
+ areChannelsBypassingDnd = source.readInt() == 1;
}
@Override
@@ -220,6 +225,7 @@
dest.writeInt(allowMedia ? 1 : 0);
dest.writeInt(allowSystem ? 1 : 0);
dest.writeInt(suppressedVisualEffects);
+ dest.writeInt(areChannelsBypassingDnd ? 1 : 0);
}
@Override
@@ -239,6 +245,7 @@
.append(",allowWhenScreenOff=").append(allowWhenScreenOff)
.append(",allowWhenScreenOn=").append(allowWhenScreenOn)
.append(",suppressedVisualEffects=").append(suppressedVisualEffects)
+ .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd)
.append(",automaticRules=").append(automaticRules)
.append(",manualRule=").append(manualRule)
.append(']').toString();
@@ -303,6 +310,11 @@
ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
}
ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
+
+ if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+ d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
+ to.areChannelsBypassingDnd);
+ }
return d;
}
@@ -397,7 +409,8 @@
&& other.user == user
&& Objects.equals(other.automaticRules, automaticRules)
&& Objects.equals(other.manualRule, manualRule)
- && other.suppressedVisualEffects == suppressedVisualEffects;
+ && other.suppressedVisualEffects == suppressedVisualEffects
+ && other.areChannelsBypassingDnd == areChannelsBypassingDnd;
}
@Override
@@ -406,7 +419,7 @@
allowRepeatCallers, allowMessages,
allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule,
- suppressedVisualEffects);
+ suppressedVisualEffects, areChannelsBypassingDnd);
}
private static String toDayList(int[] days) {
@@ -511,6 +524,9 @@
automaticRule.id = id;
rt.automaticRules.put(id, automaticRule);
}
+ } else if (STATE_TAG.equals(tag)) {
+ rt.areChannelsBypassingDnd = safeBoolean(parser,
+ STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
}
}
}
@@ -561,6 +577,12 @@
writeRuleXml(automaticRule, out);
out.endTag(null, AUTOMATIC_TAG);
}
+
+ out.startTag(null, STATE_TAG);
+ out.attribute(null, STATE_ATT_CHANNELS_BYPASSING_DND,
+ Boolean.toString(areChannelsBypassingDnd));
+ out.endTag(null, STATE_TAG);
+
out.endTag(null, ZEN_TAG);
}
@@ -743,7 +765,8 @@
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
- suppressedVisualEffects);
+ suppressedVisualEffects, areChannelsBypassingDnd
+ ? Policy.STATE_CHANNELS_BYPASSING_DND : 0);
}
/**
@@ -795,6 +818,9 @@
if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
suppressedVisualEffects = policy.suppressedVisualEffects;
}
+ if (policy.state != Policy.STATE_UNSET) {
+ areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
+ }
}
public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
@@ -1465,15 +1491,15 @@
& NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0;
boolean allowRepeatCallers = (policy.priorityCategories
& NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0;
+ boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
return !allowReminders && !allowCalls && !allowMessages && !allowEvents
- && !allowRepeatCallers;
+ && !allowRepeatCallers && !areChannelsBypassingDnd;
}
/**
* Determines if DND is currently overriding the ringer
*/
public static boolean isZenOverridingRinger(int zen, ZenModeConfig zenConfig) {
- // TODO (beverlyt): check if apps can bypass dnd b/77729075
return zen == Global.ZEN_MODE_NO_INTERRUPTIONS
|| zen == Global.ZEN_MODE_ALARMS
|| (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
@@ -1485,7 +1511,8 @@
*/
public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(ZenModeConfig config) {
return !config.allowReminders && !config.allowCalls && !config.allowMessages
- && !config.allowEvents && !config.allowRepeatCallers;
+ && !config.allowEvents && !config.allowRepeatCallers
+ && !config.areChannelsBypassingDnd;
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 71b6084..6b16d42 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -20700,7 +20700,7 @@
if (canTakeFocus()) {
// We have a robust focus, so parents should no longer be wanting focus.
clearParentsWantFocus();
- } else if (!getViewRootImpl().isInLayout()) {
+ } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
// This is a weird case. Most-likely the user, rather than ViewRootImpl, called
// layout. In this case, there's no guarantee that parent layouts will be evaluated
// and thus the safest action is to clear focus here.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a34bd09..b6bd14e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,6 +83,7 @@
import java.util.Objects;
import java.util.Stack;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* A class that describes a view hierarchy that can be displayed in
@@ -444,6 +445,10 @@
return true;
}
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // Nothing to visit by default
+ }
+
int viewId;
}
@@ -517,6 +522,27 @@
setBitmapCache(mBitmapCache);
}
+ /**
+ * Note all {@link Uri} that are referenced internally, with the expectation
+ * that Uri permission grants will need to be issued to ensure the recipient
+ * of this object is able to render its contents.
+ *
+ * @hide
+ */
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ if (mActions != null) {
+ for (int i = 0; i < mActions.size(); i++) {
+ mActions.get(i).visitUris(visitor);
+ }
+ }
+ }
+
+ private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
+ if (icon != null && icon.getType() == Icon.TYPE_URI) {
+ visitor.accept(icon.getUri());
+ }
+ }
+
private static class RemoteViewsContextWrapper extends ContextWrapper {
private final Context mContextForResources;
@@ -1485,6 +1511,20 @@
public boolean prefersAsyncApply() {
return this.type == URI || this.type == ICON;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ switch (this.type) {
+ case URI:
+ final Uri uri = (Uri) this.value;
+ visitor.accept(uri);
+ break;
+ case ICON:
+ final Icon icon = (Icon) this.value;
+ visitIconUri(icon, visitor);
+ break;
+ }
+ }
}
/**
@@ -1849,6 +1889,16 @@
return TEXT_VIEW_DRAWABLE_ACTION_TAG;
}
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ if (useIcons) {
+ visitIconUri(i1, visitor);
+ visitIconUri(i2, visitor);
+ visitIconUri(i3, visitor);
+ visitIconUri(i4, visitor);
+ }
+ }
+
boolean isRelative = false;
boolean useIcons = false;
int d1, d2, d3, d4;
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 433d14f..083c0c9 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -29,6 +29,7 @@
import java.util.List;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Stream;
/**
@@ -84,6 +85,17 @@
return emptyIfNull(result);
}
+ /** Add all elements matching {@code predicate} in {@code source} to {@code dest}. */
+ public static <T> void addIf(@Nullable List<T> source, @NonNull Collection<? super T> dest,
+ @Nullable Predicate<? super T> predicate) {
+ for (int i = 0; i < size(source); i++) {
+ final T item = source.get(i);
+ if (predicate.test(item)) {
+ dest.add(item);
+ }
+ }
+ }
+
/**
* Returns a list of items resulting from applying the given function to each element of the
* provided list.
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index e85b782..7fd83bc 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -16,18 +16,25 @@
package com.android.internal.util;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
+import android.text.TextUtils;
import android.util.Slog;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Objects;
+import java.util.function.Predicate;
/**
* Helper functions for dumping the state of system services.
+ * Test:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
*/
public final class DumpUtils {
private static final String TAG = "DumpUtils";
@@ -153,4 +160,99 @@
PrintWriter pw) {
return checkDumpPermission(context, tag, pw) && checkUsageStatsPermission(context, tag, pw);
}
+
+ /**
+ * Return whether a package name is considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isPlatformPackage(@Nullable String packageName) {
+ return (packageName != null)
+ && (packageName.equals("android")
+ || packageName.startsWith("android.")
+ || packageName.startsWith("com.android."));
+ }
+
+ /**
+ * Return whether a package name is considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isPlatformPackage(@Nullable ComponentName cname) {
+ return (cname != null) && isPlatformPackage(cname.getPackageName());
+ }
+
+ /**
+ * Return whether a package name is considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isPlatformPackage(@Nullable ComponentName.WithComponentName wcn) {
+ return (wcn != null) && isPlatformPackage(wcn.getComponentName());
+ }
+
+ /**
+ * Return whether a package name is NOT considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isNonPlatformPackage(@Nullable String packageName) {
+ return (packageName != null) && !isPlatformPackage(packageName);
+ }
+
+ /**
+ * Return whether a package name is NOT considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isNonPlatformPackage(@Nullable ComponentName cname) {
+ return (cname != null) && isNonPlatformPackage(cname.getPackageName());
+ }
+
+ /**
+ * Return whether a package name is NOT considered to be part of the platform.
+ * @hide
+ */
+ public static boolean isNonPlatformPackage(@Nullable ComponentName.WithComponentName wcn) {
+ return (wcn != null) && !isPlatformPackage(wcn.getComponentName());
+ }
+
+ /**
+ * Used for dumping providers and services. Return a predicate for a given filter string.
+ * @hide
+ */
+ public static <TRec extends ComponentName.WithComponentName> Predicate<TRec> filterRecord(
+ @Nullable String filterString) {
+
+ if (TextUtils.isEmpty(filterString)) {
+ return rec -> false;
+ }
+
+ // Dump all?
+ if ("all".equals(filterString)) {
+ return Objects::nonNull;
+ }
+
+ // Dump all platform?
+ if ("all-platform".equals(filterString)) {
+ return DumpUtils::isPlatformPackage;
+ }
+
+ // Dump all non-platform?
+ if ("all-non-platform".equals(filterString)) {
+ return DumpUtils::isNonPlatformPackage;
+ }
+
+ // Is the filter a component name? If so, do an exact match.
+ final ComponentName filterCname = ComponentName.unflattenFromString(filterString);
+ if (filterCname != null) {
+ // Do exact component name check.
+ return rec -> (rec != null) && filterCname.equals(rec.getComponentName());
+ }
+
+ // Otherwise, do a partial match against the component name.
+ // Also if the filter is a hex-decimal string, do the object ID match too.
+ final int id = ParseUtils.parseIntWithBase(filterString, 16, -1);
+ return rec -> {
+ final ComponentName cn = rec.getComponentName();
+ return ((id != -1) && (System.identityHashCode(rec) == id))
+ || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
+ };
+ }
}
+
diff --git a/core/java/com/android/internal/util/ParseUtils.java b/core/java/com/android/internal/util/ParseUtils.java
new file mode 100644
index 0000000..a591f4a
--- /dev/null
+++ b/core/java/com/android/internal/util/ParseUtils.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.android.internal.util;
+
+import android.annotation.Nullable;
+
+/**
+ * Various numeric -> strings conversion.
+ *
+ * Test:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
+ */
+public final class ParseUtils {
+ private ParseUtils() {
+ }
+
+ /** Parse a value as a base-10 integer. */
+ public static int parseInt(@Nullable String value, int defValue) {
+ return parseIntWithBase(value, 10, defValue);
+ }
+
+ /** Parse a value as an integer of a given base. */
+ public static int parseIntWithBase(@Nullable String value, int base, int defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Integer.parseInt(value, base);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a base-10 long. */
+ public static long parseLong(@Nullable String value, long defValue) {
+ return parseLongWithBase(value, 10, defValue);
+ }
+
+ /** Parse a value as a long of a given base. */
+ public static long parseLongWithBase(@Nullable String value, int base, long defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Long.parseLong(value, base);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a float. */
+ public static float parseFloat(@Nullable String value, float defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a double. */
+ public static double parseDouble(@Nullable String value, double defValue) {
+ if (value == null) {
+ return defValue;
+ }
+ try {
+ return Double.parseDouble(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ /** Parse a value as a boolean. */
+ public static boolean parseBoolean(@Nullable String value, boolean defValue) {
+ if ("true".equals(value)) {
+ return true;
+ }
+ if ("false".equals(value)) {
+ return false;
+ }
+ return parseInt(value, defValue ? 1 : 0) != 0;
+ }
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fc030ca..909efea 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -37,9 +37,6 @@
<item><xliff:g id="id">@string/status_bar_nfc</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_tty</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_speakerphone</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cdma_eri</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_data_connection</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item>
@@ -49,10 +46,13 @@
<item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_location</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index dce8a65..3a71851 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -19,8 +19,12 @@
<!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. -->
<zen version="7">
- <allow alarms="true" media="true" system="false" calls="false" messages="false" reminders="false"
- events="false" />
+ <allow alarms="true" media="true" system="false" calls="false" messages="false"
+ reminders="false" events="false" />
+
<!-- all visual effects that exist as of P -->
<disallow suppressedVisualEffect="511" />
+
+ <!-- whether there are notification channels that can bypass dnd -->
+ <state areChannelsBypassingDnd="false" />
</zen>
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
new file mode 100644
index 0000000..45b19bc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 com.android.internal.util;
+
+import static com.android.internal.util.DumpUtils.filterRecord;
+import static com.android.internal.util.DumpUtils.isNonPlatformPackage;
+import static com.android.internal.util.DumpUtils.isPlatformPackage;
+
+import android.content.ComponentName;
+
+import junit.framework.TestCase;
+
+/**
+ * Run with:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/DumpTest.java
+ */
+public class DumpUtilsTest extends TestCase {
+
+ private static ComponentName cn(String componentName) {
+ if (componentName == null) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(componentName);
+ }
+
+ private static ComponentName.WithComponentName wcn(String componentName) {
+ if (componentName == null) {
+ return null;
+ }
+ return () -> cn(componentName);
+ }
+
+ public void testIsPlatformPackage() {
+ assertTrue(isPlatformPackage("android"));
+ assertTrue(isPlatformPackage("android.abc"));
+ assertTrue(isPlatformPackage("com.android.abc"));
+
+ assertFalse(isPlatformPackage((String) null));
+ assertFalse(isPlatformPackage("com.google"));
+
+ assertTrue(isPlatformPackage(cn("android/abc")));
+ assertTrue(isPlatformPackage(cn("android.abc/abc")));
+ assertTrue(isPlatformPackage(cn("com.android.def/abc")));
+
+ assertFalse(isPlatformPackage(cn(null)));
+ assertFalse(isPlatformPackage(cn("com.google.def/abc")));
+
+ assertTrue(isPlatformPackage(wcn("android/abc")));
+ assertTrue(isPlatformPackage(wcn("android.abc/abc")));
+ assertTrue(isPlatformPackage(wcn("com.android.def/abc")));
+
+ assertFalse(isPlatformPackage(wcn(null)));
+ assertFalse(isPlatformPackage(wcn("com.google.def/abc")));
+ }
+
+ public void testIsNonPlatformPackage() {
+ assertFalse(isNonPlatformPackage("android"));
+ assertFalse(isNonPlatformPackage("android.abc"));
+ assertFalse(isNonPlatformPackage("com.android.abc"));
+
+ assertFalse(isNonPlatformPackage((String) null));
+ assertTrue(isNonPlatformPackage("com.google"));
+
+ assertFalse(isNonPlatformPackage(cn("android/abc")));
+ assertFalse(isNonPlatformPackage(cn("android.abc/abc")));
+ assertFalse(isNonPlatformPackage(cn("com.android.def/abc")));
+
+ assertFalse(isNonPlatformPackage(cn(null)));
+ assertTrue(isNonPlatformPackage(cn("com.google.def/abc")));
+
+ assertFalse(isNonPlatformPackage(wcn("android/abc")));
+ assertFalse(isNonPlatformPackage(wcn("android.abc/abc")));
+ assertFalse(isNonPlatformPackage(wcn("com.android.def/abc")));
+
+ assertFalse(isNonPlatformPackage(wcn(null)));
+ assertTrue(isNonPlatformPackage(wcn("com.google.def/abc")));
+ }
+
+ public void testFilterRecord() {
+ assertFalse(filterRecord(null).test(wcn("com.google.p/abc")));
+ assertFalse(filterRecord(null).test(wcn("com.android.p/abc")));
+
+ assertTrue(filterRecord("all").test(wcn("com.google.p/abc")));
+ assertTrue(filterRecord("all").test(wcn("com.android.p/abc")));
+ assertFalse(filterRecord("all").test(wcn(null)));
+
+ assertFalse(filterRecord("all-platform").test(wcn("com.google.p/abc")));
+ assertTrue(filterRecord("all-platform").test(wcn("com.android.p/abc")));
+ assertFalse(filterRecord("all-platform").test(wcn(null)));
+
+ assertTrue(filterRecord("all-non-platform").test(wcn("com.google.p/abc")));
+ assertFalse(filterRecord("all-non-platform").test(wcn("com.android.p/abc")));
+ assertFalse(filterRecord("all-non-platform").test(wcn(null)));
+
+ // Partial string match.
+ assertTrue(filterRecord("abc").test(wcn("com.google.p/.abc")));
+ assertFalse(filterRecord("abc").test(wcn("com.google.p/.def")));
+ assertTrue(filterRecord("com").test(wcn("com.google.p/.xyz")));
+
+ // Full component name match.
+ assertTrue(filterRecord("com.google/com.google.abc").test(wcn("com.google/.abc")));
+ assertFalse(filterRecord("com.google/com.google.abc").test(wcn("com.google/.abc.def")));
+
+
+ // Hex ID match
+ ComponentName.WithComponentName component = wcn("com.google/.abc");
+
+ assertTrue(filterRecord(
+ Integer.toHexString(System.identityHashCode(component))).test(component));
+ // Same component name, but different ID, no match.
+ assertFalse(filterRecord(
+ Integer.toHexString(System.identityHashCode(component))).test(
+ wcn("com.google/.abc")));
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
new file mode 100644
index 0000000..f00c48c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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 com.android.internal.util;
+
+import junit.framework.TestCase;
+
+/**
+ * Run with:
+ atest /android/pi-dev/frameworks/base/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
+ */
+public class ParseUtilsTest extends TestCase {
+ public void testParseInt() {
+ assertEquals(1, ParseUtils.parseInt(null, 1));
+ assertEquals(1, ParseUtils.parseInt("", 1));
+ assertEquals(1, ParseUtils.parseInt("1x", 1));
+ assertEquals(2, ParseUtils.parseInt("2", 1));
+
+ assertEquals(2, ParseUtils.parseInt("+2", 1));
+ assertEquals(-2, ParseUtils.parseInt("-2", 1));
+ }
+
+ public void testParseIntWithBase() {
+ assertEquals(1, ParseUtils.parseIntWithBase(null, 10, 1));
+ assertEquals(1, ParseUtils.parseIntWithBase("", 10, 1));
+ assertEquals(1, ParseUtils.parseIntWithBase("1x", 10, 1));
+ assertEquals(2, ParseUtils.parseIntWithBase("2", 10, 1));
+ assertEquals(10, ParseUtils.parseIntWithBase("10", 10, 1));
+ assertEquals(3, ParseUtils.parseIntWithBase("10", 3, 1));
+
+ assertEquals(3, ParseUtils.parseIntWithBase("+10", 3, 1));
+ assertEquals(-3, ParseUtils.parseIntWithBase("-10", 3, 1));
+ }
+
+ public void testParseLong() {
+ assertEquals(1L, ParseUtils.parseLong(null, 1));
+ assertEquals(1L, ParseUtils.parseLong("", 1));
+ assertEquals(1L, ParseUtils.parseLong("1x", 1));
+ assertEquals(2L, ParseUtils.parseLong("2", 1));
+ }
+
+ public void testParseLongWithBase() {
+ assertEquals(1L, ParseUtils.parseLongWithBase(null, 10, 1));
+ assertEquals(1L, ParseUtils.parseLongWithBase("", 10, 1));
+ assertEquals(1L, ParseUtils.parseLongWithBase("1x", 10, 1));
+ assertEquals(2L, ParseUtils.parseLongWithBase("2", 10, 1));
+ assertEquals(10L, ParseUtils.parseLongWithBase("10", 10, 1));
+ assertEquals(3L, ParseUtils.parseLongWithBase("10", 3, 1));
+
+ assertEquals(3L, ParseUtils.parseLongWithBase("+10", 3, 1));
+ assertEquals(-3L, ParseUtils.parseLongWithBase("-10", 3, 1));
+
+ assertEquals(10_000_000_000L, ParseUtils.parseLongWithBase("+10000000000", 10, 1));
+ assertEquals(-10_000_000_000L, ParseUtils.parseLongWithBase("-10000000000", 10, 1));
+
+ assertEquals(10_000_000_000L, ParseUtils.parseLongWithBase(null, 10, 10_000_000_000L));
+ }
+
+ public void testParseFloat() {
+ assertEquals(0.5f, ParseUtils.parseFloat(null, 0.5f));
+ assertEquals(0.5f, ParseUtils.parseFloat("", 0.5f));
+ assertEquals(0.5f, ParseUtils.parseFloat("1x", 0.5f));
+ assertEquals(1.5f, ParseUtils.parseFloat("1.5", 0.5f));
+ }
+
+ public void testParseDouble() {
+ assertEquals(0.5, ParseUtils.parseDouble(null, 0.5));
+ assertEquals(0.5, ParseUtils.parseDouble("", 0.5));
+ assertEquals(0.5, ParseUtils.parseDouble("1x", 0.5));
+ assertEquals(1.5, ParseUtils.parseDouble("1.5", 0.5));
+ }
+
+ public void testParseBoolean() {
+ assertEquals(false, ParseUtils.parseBoolean(null, false));
+ assertEquals(true, ParseUtils.parseBoolean(null, true));
+
+ assertEquals(false, ParseUtils.parseBoolean("", false));
+ assertEquals(true, ParseUtils.parseBoolean("", true));
+
+ assertEquals(true, ParseUtils.parseBoolean("true", false));
+ assertEquals(true, ParseUtils.parseBoolean("true", true));
+
+ assertEquals(false, ParseUtils.parseBoolean("false", false));
+ assertEquals(false, ParseUtils.parseBoolean("false", true));
+
+ assertEquals(true, ParseUtils.parseBoolean("1", false));
+ assertEquals(true, ParseUtils.parseBoolean("1", true));
+
+ assertEquals(false, ParseUtils.parseBoolean("0", false));
+ assertEquals(false, ParseUtils.parseBoolean("0", true));
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 8253083..ed63089 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -382,9 +382,9 @@
<!-- Instructions telling the user remaining times when enter SIM PIN view. -->
<plurals name="kg_password_default_pin_message">
- <item quantity="one">Enter SIM PIN, you have <xliff:g id="number">%d</xliff:g> remaining
+ <item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining
attempt before you must contact your carrier to unlock your device.</item>
- <item quantity="other">Enter SIM PIN, you have <xliff:g id="number">%d</xliff:g> remaining
+ <item quantity="other">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining
attempts.</item>
</plurals>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ae40db0..6c507be 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -515,16 +515,12 @@
<string name="accessibility_quick_settings_airplane_changed_off">Airplane mode turned off.</string>
<!-- Announcement made when the airplane mode changes to on (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_airplane_changed_on">Airplane mode turned on.</string>
- <!-- Content description of the do not disturb tile in quick settings when on in the default priority mode (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_quick_settings_dnd_priority_on">Do not disturb on.</string>
<!-- Content description of the do not disturb tile in quick settings when on in none (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_quick_settings_dnd_none_on">Do not disturb on, total silence.</string>
+ <string name="accessibility_quick_settings_dnd_none_on">total silence</string>
<!-- Content description of the do not disturb tile in quick settings when on in alarms only (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_quick_settings_dnd_alarms_on">Do not disturb on, alarms only.</string>
+ <string name="accessibility_quick_settings_dnd_alarms_on">alarms only</string>
<!-- Content description of the do not disturb tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_dnd">Do not disturb.</string>
- <!-- Content description of the do not disturb tile in quick settings when off (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_quick_settings_dnd_off">Do not disturb off.</string>
<!-- Announcement made when do not disturb changes to off (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_dnd_changed_off">Do not disturb turned off.</string>
<!-- Announcement made when do not disturb changes to on (not shown on the screen). [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index f13be73..d22aab5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -41,7 +41,12 @@
void setInteractionState(int flags) = 4;
/**
- * Notifies SystemUI that split screen has been invoked.
- */
+ * Notifies SystemUI that split screen has been invoked.
+ */
void onSplitScreenInvoked() = 5;
+
+ /**
+ * Notifies SystemUI that Overview is shown.
+ */
+ void onOverviewShown(boolean fromHome) = 6;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
index bff0d9b..8d451c1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java
@@ -35,7 +35,7 @@
*/
public static final int QUICK_STEP_DRAG_SLOP_PX = convertDpToPixel(10);
public static final int QUICK_SCRUB_DRAG_SLOP_PX = convertDpToPixel(20);
- public static final int QUICK_STEP_TOUCH_SLOP_PX = convertDpToPixel(40);
+ public static final int QUICK_STEP_TOUCH_SLOP_PX = convertDpToPixel(24);
public static final int QUICK_SCRUB_TOUCH_SLOP_PX = convertDpToPixel(35);
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 3443334..e1540ea 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -118,6 +118,19 @@
}
}
+ public void onOverviewShown(boolean fromHome) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mHandler.post(() -> {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onOverviewShown(fromHome);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void setInteractionState(@InteractionType int flags) {
long token = Binder.clearCallingIdentity();
try {
@@ -306,6 +319,12 @@
}
}
+ public void notifyQuickScrubStarted() {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onQuickScrubStarted();
+ }
+ }
+
private void updateEnabledState() {
mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
MATCH_DIRECT_BOOT_UNAWARE,
@@ -325,5 +344,7 @@
default void onConnectionChanged(boolean isConnected) {}
default void onQuickStepStarted() {}
default void onInteractionFlagsChanged(@InteractionType int flags) {}
+ default void onOverviewShown(boolean fromHome) {}
+ default void onQuickScrubStarted() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 7f7a769..f595d77 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -50,8 +50,10 @@
Key.QS_NIGHTDISPLAY_ADDED,
Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
Key.SEEN_MULTI_USER,
- Key.NUM_APPS_LAUNCHED,
- Key.HAS_SEEN_RECENTS_ONBOARDING,
+ Key.HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING,
+ Key.HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING,
+ Key.OVERVIEW_OPENED_COUNT,
+ Key.OVERVIEW_OPENED_FROM_HOME_COUNT,
Key.SEEN_RINGER_GUIDANCE_COUNT,
Key.QS_HAS_TURNED_OFF_MOBILE_DATA,
Key.TOUCHED_RINGER_TOGGLE,
@@ -88,8 +90,10 @@
*/
String QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT = "QsLongPressTooltipShownCount";
String SEEN_MULTI_USER = "HasSeenMultiUser";
- String NUM_APPS_LAUNCHED = "NumAppsLaunched";
- String HAS_SEEN_RECENTS_ONBOARDING = "HasSeenRecentsOnboarding";
+ String OVERVIEW_OPENED_COUNT = "OverviewOpenedCount";
+ String OVERVIEW_OPENED_FROM_HOME_COUNT = "OverviewOpenedFromHomeCount";
+ String HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING = "HasSeenRecentsSwipeUpOnboarding";
+ String HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING = "HasSeenRecentsQuickScrubOnboarding";
String SEEN_RINGER_GUIDANCE_COUNT = "RingerGuidanceCount";
String QS_TILE_SPECS_REVEALED = "QsTileSpecsRevealed";
String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData";
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index a61ce8c..4e7c3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -69,8 +69,8 @@
SystemUIFactory.createFromConfig(this);
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
- IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -86,11 +86,21 @@
}
}
- IntentFilter localeChangedFilter = new IntentFilter(
- Intent.ACTION_LOCALE_CHANGED);
- registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
+
}
- }, filter);
+ }, bootCompletedFilter);
+
+ IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+ if (!mBootCompleted) return;
+ // Update names of SystemUi notification channels
+ NotificationChannels.createAll(context);
+ }
+ }
+ }, localeChangedFilter);
} else {
// We don't need to startServices for sub-process that is doing some tasks.
// (screenshots, sweetsweetdesserts or tuner ..)
@@ -239,14 +249,4 @@
public SystemUI[] getServices() {
return mServices;
}
-
- private final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
- // Update names of SystemUi notification channels
- NotificationChannels.createAll(context);
- }
- }
- };
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index c390764..03a76da 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -113,7 +113,7 @@
// The display buffers will be empty and need to be filled.
mHost.dozeTimeTick();
// The first frame may arrive when the display isn't ready yet.
- mHandler.postDelayed(mHost::dozeTimeTick, 100);
+ mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 100);
}
scheduleTimeTick();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 06183e9..a25c466 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -28,6 +28,7 @@
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.service.quicksettings.Tile;
+import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;
@@ -50,6 +51,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
/** Quick settings tile: Bluetooth **/
public class BluetoothTile extends QSTileImpl<BooleanState> {
@@ -131,32 +133,34 @@
}
state.slash.isSlashed = !enabled;
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
+ state.secondaryLabel = TextUtils.emptyIfNull(
+ getSecondaryLabel(enabled, connected, state.isTransient));
if (enabled) {
if (connected) {
state.icon = new BluetoothConnectedTileIcon();
- state.contentDescription = mContext.getString(
- R.string.accessibility_bluetooth_name, state.label);
-
- state.label = mController.getLastDeviceName();
+ if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
+ state.label = mController.getConnectedDeviceName();
+ }
+ state.contentDescription =
+ mContext.getString(R.string.accessibility_bluetooth_name, state.label)
+ + ", " + state.secondaryLabel;
} else if (state.isTransient) {
state.icon = ResourceIcon.get(R.drawable.ic_bluetooth_transient_animation);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_bluetooth_connecting);
+ state.contentDescription = state.secondaryLabel;
} else {
state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_bluetooth_on) + ","
+ R.string.accessibility_quick_settings_bluetooth) + ","
+ mContext.getString(R.string.accessibility_not_connected);
}
state.state = Tile.STATE_ACTIVE;
} else {
state.icon = ResourceIcon.get(R.drawable.ic_qs_bluetooth_on);
state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_bluetooth_off);
+ R.string.accessibility_quick_settings_bluetooth);
state.state = Tile.STATE_INACTIVE;
}
- state.secondaryLabel = getSecondaryLabel(enabled, connected, state.isTransient);
state.dualLabelContentDescription = mContext.getResources().getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
@@ -176,9 +180,18 @@
if (isTransient) {
return mContext.getString(R.string.quick_settings_bluetooth_secondary_label_transient);
}
- final CachedBluetoothDevice lastDevice = mController.getLastDevice();
- if (enabled && connected && lastDevice != null) {
+ List<CachedBluetoothDevice> connectedDevices = mController.getConnectedDevices();
+ if (enabled && connected && !connectedDevices.isEmpty()) {
+ if (connectedDevices.size() > 1) {
+ // TODO(b/76102598): add a new string for "X connected devices" after P
+ return mContext.getResources().getQuantityString(
+ R.plurals.quick_settings_hotspot_secondary_label_num_devices,
+ connectedDevices.size(),
+ connectedDevices.size());
+ }
+
+ CachedBluetoothDevice lastDevice = connectedDevices.get(0);
final int batteryLevel = lastDevice.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 16c2a75..67900d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -36,6 +36,7 @@
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.quicksettings.Tile;
+import android.text.TextUtils;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
@@ -223,25 +224,27 @@
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.slash.isSlashed = !state.value;
state.label = getTileLabel();
- state.secondaryLabel = ZenModeConfig.getDescription(mContext,zen != Global.ZEN_MODE_OFF,
- mController.getConfig(), false);
+ state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext,
+ zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_dnd_priority_on) + ", "
+ state.contentDescription =
+ mContext.getString(R.string.accessibility_quick_settings_dnd) + ", "
+ state.secondaryLabel;
break;
case Global.ZEN_MODE_NO_INTERRUPTIONS:
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_dnd_none_on) + ", "
- + state.secondaryLabel;
+ state.contentDescription =
+ mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
+ mContext.getString(R.string.accessibility_quick_settings_dnd_none_on)
+ + ", " + state.secondaryLabel;
break;
case ZEN_MODE_ALARMS:
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_dnd_alarms_on) + ", "
- + state.secondaryLabel;
+ state.contentDescription =
+ mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
+ mContext.getString(R.string.accessibility_quick_settings_dnd_alarms_on)
+ + ", " + state.secondaryLabel;
break;
default:
state.contentDescription = mContext.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 901c7ae..ffa1444 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -19,6 +19,12 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static com.android.systemui.Prefs.Key.HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING;
+import static com.android.systemui.Prefs.Key.HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING;
+import static com.android.systemui.Prefs.Key.OVERVIEW_OPENED_COUNT;
+import static com.android.systemui.Prefs.Key.OVERVIEW_OPENED_FROM_HOME_COUNT;
+
+import android.annotation.StringRes;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
@@ -31,8 +37,6 @@
import android.os.Build;
import android.os.SystemProperties;
import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -65,10 +69,16 @@
private static final boolean ONBOARDING_ENABLED = false;
private static final long SHOW_DELAY_MS = 500;
private static final long SHOW_HIDE_DURATION_MS = 300;
- // Don't show the onboarding until the user has launched this number of apps.
- private static final int SHOW_ON_APP_LAUNCH = 2;
- // After explicitly dismissing, show again after launching this number of apps.
- private static final int SHOW_ON_APP_LAUNCH_AFTER_DISMISS = 5;
+ // Show swipe-up tips after opening overview from home this number of times.
+ private static final int SWIPE_UP_SHOW_ON_OVERVIEW_OPENED_FROM_HOME_COUNT = 3;
+ // Show quick scrub tips after opening overview this number of times.
+ private static final int QUICK_SCRUB_SHOW_ON_OVERVIEW_OPENED_COUNT = 10;
+ // After explicitly dismissing, show again after launching this number of apps for swipe-up
+ // tips.
+ private static final int SWIPE_UP_SHOW_ON_APP_LAUNCH_AFTER_DISMISS = 5;
+ // After explicitly dismissing, show again after launching this number of apps for QuickScrub
+ // tips.
+ private static final int QUICK_SCRUB_SHOW_ON_APP_LAUNCH_AFTER_DISMISS = 10;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -82,11 +92,14 @@
private final int mOnboardingToastArrowRadius;
private int mNavBarHeight;
+ private boolean mOverviewProxyListenerRegistered;
private boolean mTaskListenerRegistered;
private boolean mLayoutAttachedToWindow;
private int mLastTaskId;
- private boolean mHasDismissed;
- private int mNumAppsLaunchedSinceDismiss;
+ private boolean mHasDismissedSwipeUpTip;
+ private boolean mHasDismissedQuickScrubTip;
+ private int mNumAppsLaunchedSinceSwipeUpTipDismiss;
+ private int mNumAppsLaunchedSinceQuickScrubTipDismiss;
private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
@Override
@@ -107,18 +120,40 @@
int activityType = info.configuration.windowConfiguration.getActivityType();
if (activityType == ACTIVITY_TYPE_STANDARD) {
mLastTaskId = info.id;
- int numAppsLaunched = mHasDismissed ? mNumAppsLaunchedSinceDismiss
- : Prefs.getInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
- int showOnAppLaunch = mHasDismissed ? SHOW_ON_APP_LAUNCH_AFTER_DISMISS
- : SHOW_ON_APP_LAUNCH;
- numAppsLaunched++;
- if (numAppsLaunched >= showOnAppLaunch) {
- show();
+
+ boolean alreadySeenSwipeUpOnboarding = hasSeenSwipeUpOnboarding();
+ boolean alreadySeenQuickScrubsOnboarding = hasSeenQuickScrubOnboarding();
+ if (alreadySeenSwipeUpOnboarding && alreadySeenQuickScrubsOnboarding) {
+ onDisconnectedFromLauncher();
+ return;
+ }
+
+ if (!alreadySeenSwipeUpOnboarding) {
+ if (getOpenedOverviewFromHomeCount()
+ >= SWIPE_UP_SHOW_ON_OVERVIEW_OPENED_FROM_HOME_COUNT) {
+ if (mHasDismissedSwipeUpTip) {
+ mNumAppsLaunchedSinceSwipeUpTipDismiss++;
+ if (mNumAppsLaunchedSinceSwipeUpTipDismiss
+ == SWIPE_UP_SHOW_ON_APP_LAUNCH_AFTER_DISMISS) {
+ mNumAppsLaunchedSinceSwipeUpTipDismiss = 0;
+ show(R.string.recents_swipe_up_onboarding);
+ }
+ } else {
+ show(R.string.recents_swipe_up_onboarding);
+ }
+ }
} else {
- if (mHasDismissed) {
- mNumAppsLaunchedSinceDismiss = numAppsLaunched;
- } else {
- Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, numAppsLaunched);
+ if (getOpenedOverviewCount() >= QUICK_SCRUB_SHOW_ON_OVERVIEW_OPENED_COUNT) {
+ if (mHasDismissedQuickScrubTip) {
+ mNumAppsLaunchedSinceQuickScrubTipDismiss++;
+ if (mNumAppsLaunchedSinceQuickScrubTipDismiss
+ == QUICK_SCRUB_SHOW_ON_APP_LAUNCH_AFTER_DISMISS) {
+ mNumAppsLaunchedSinceQuickScrubTipDismiss = 0;
+ show(R.string.recents_quick_scrub_onboarding);
+ }
+ } else {
+ show(R.string.recents_quick_scrub_onboarding);
+ }
}
}
} else {
@@ -127,13 +162,36 @@
}
};
+ private OverviewProxyService.OverviewProxyListener mOverviewProxyListener =
+ new OverviewProxyService.OverviewProxyListener() {
+ @Override
+ public void onOverviewShown(boolean fromHome) {
+ boolean alreadySeenRecentsOnboarding = hasSeenSwipeUpOnboarding();
+ if (!alreadySeenRecentsOnboarding && !fromHome) {
+ setHasSeenSwipeUpOnboarding(true);
+ }
+ if (fromHome) {
+ setOpenedOverviewFromHomeCount(getOpenedOverviewFromHomeCount() + 1);
+ }
+ setOpenedOverviewCount(getOpenedOverviewCount() + 1);
+ }
+
+ @Override
+ public void onQuickScrubStarted() {
+ boolean alreadySeenQuickScrubsOnboarding = hasSeenQuickScrubOnboarding();
+ if (!alreadySeenQuickScrubsOnboarding) {
+ setHasSeenQuickScrubOnboarding(true);
+ }
+ }
+ };
+
private final View.OnAttachStateChangeListener mOnAttachStateChangeListener
= new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
if (view == mLayout) {
mLayoutAttachedToWindow = true;
- mHasDismissed = false;
+ mHasDismissedSwipeUpTip = false;
}
}
@@ -167,8 +225,19 @@
mLayout.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
mDismissView.setOnClickListener(v -> {
hide(true);
- mHasDismissed = true;
- mNumAppsLaunchedSinceDismiss = 0;
+ if (v.getTag().equals(R.string.recents_swipe_up_onboarding)) {
+ mHasDismissedSwipeUpTip = true;
+ mNumAppsLaunchedSinceSwipeUpTipDismiss = 0;
+ } else {
+ if (mHasDismissedQuickScrubTip) {
+ // If user dismisses the quick scrub tip twice, we consider user has seen it
+ // and do not show it again.
+ setHasSeenQuickScrubOnboarding(true);
+ } else {
+ mHasDismissedQuickScrubTip = true;
+ }
+ mNumAppsLaunchedSinceQuickScrubTipDismiss = 0;
+ }
});
ViewGroup.LayoutParams arrowLp = mArrowView.getLayoutParams();
@@ -181,8 +250,10 @@
mArrowView.setBackground(arrowDrawable);
if (RESET_PREFS_FOR_DEBUG) {
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
- Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, 0);
+ setHasSeenSwipeUpOnboarding(false);
+ setHasSeenQuickScrubOnboarding(false);
+ setOpenedOverviewCount(0);
+ setOpenedOverviewFromHomeCount(0);
}
}
@@ -190,30 +261,35 @@
if (!ONBOARDING_ENABLED) {
return;
}
- boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
- if (!mTaskListenerRegistered && !alreadySeenRecentsOnboarding) {
+
+ if (hasSeenSwipeUpOnboarding() && hasSeenQuickScrubOnboarding()) {
+ return;
+ }
+
+ if (!mOverviewProxyListenerRegistered) {
+ mOverviewProxyService.addCallback(mOverviewProxyListener);
+ mOverviewProxyListenerRegistered = true;
+ }
+ if (!mTaskListenerRegistered) {
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
mTaskListenerRegistered = true;
}
}
- public void onQuickStepStarted() {
- boolean alreadySeenRecentsOnboarding = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, false);
- if (!alreadySeenRecentsOnboarding) {
- Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_RECENTS_ONBOARDING, true);
- onDisconnectedFromLauncher();
- }
- }
-
public void onDisconnectedFromLauncher() {
+ if (mOverviewProxyListenerRegistered) {
+ mOverviewProxyService.removeCallback(mOverviewProxyListener);
+ mOverviewProxyListenerRegistered = false;
+ }
if (mTaskListenerRegistered) {
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
mTaskListenerRegistered = false;
}
- mHasDismissed = false;
- mNumAppsLaunchedSinceDismiss = 0;
+
+ mHasDismissedSwipeUpTip = false;
+ mHasDismissedQuickScrubTip = false;
+ mNumAppsLaunchedSinceSwipeUpTipDismiss = 0;
+ mNumAppsLaunchedSinceQuickScrubTipDismiss = 0;
hide(false);
}
@@ -223,11 +299,12 @@
}
}
- public void show() {
+ public void show(@StringRes int stringRes) {
if (!shouldShow()) {
return;
}
- mTextView.setText(R.string.recents_swipe_up_onboarding);
+ mDismissView.setTag(stringRes);
+ mTextView.setText(stringRes);
// Only show in portrait.
int orientation = mContext.getResources().getConfiguration().orientation;
if (!mLayoutAttachedToWindow && orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -259,7 +336,7 @@
private boolean shouldShow() {
return SystemProperties.getBoolean("persist.quickstep.onboarding.enabled",
!(mContext.getSystemService(UserManager.class)).isDemoUser() &&
- !ActivityManager.isRunningInTestHarness());
+ !ActivityManager.isRunningInTestHarness());
}
public void hide(boolean animate) {
@@ -299,4 +376,43 @@
lp.gravity = Gravity.BOTTOM;
return lp;
}
+
+ private boolean hasSeenSwipeUpOnboarding() {
+ return Prefs.getBoolean(mContext, HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING, false);
+ }
+
+ private void setHasSeenSwipeUpOnboarding(boolean hasSeenSwipeUpOnboarding) {
+ Prefs.putBoolean(mContext, HAS_SEEN_RECENTS_SWIPE_UP_ONBOARDING, hasSeenSwipeUpOnboarding);
+ if (hasSeenSwipeUpOnboarding && hasSeenQuickScrubOnboarding()) {
+ onDisconnectedFromLauncher();
+ }
+ }
+
+ private boolean hasSeenQuickScrubOnboarding() {
+ return Prefs.getBoolean(mContext, HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING, false);
+ }
+
+ private void setHasSeenQuickScrubOnboarding(boolean hasSeenQuickScrubOnboarding) {
+ Prefs.putBoolean(mContext, HAS_SEEN_RECENTS_QUICK_SCRUB_ONBOARDING,
+ hasSeenQuickScrubOnboarding);
+ if (hasSeenQuickScrubOnboarding && hasSeenSwipeUpOnboarding()) {
+ onDisconnectedFromLauncher();
+ }
+ }
+
+ private int getOpenedOverviewFromHomeCount() {
+ return Prefs.getInt(mContext, OVERVIEW_OPENED_FROM_HOME_COUNT, 0);
+ }
+
+ private void setOpenedOverviewFromHomeCount(int openedOverviewFromHomeCount) {
+ Prefs.putInt(mContext, OVERVIEW_OPENED_FROM_HOME_COUNT, openedOverviewFromHomeCount);
+ }
+
+ private int getOpenedOverviewCount() {
+ return Prefs.getInt(mContext, OVERVIEW_OPENED_COUNT, 0);
+ }
+
+ private void setOpenedOverviewCount(int openedOverviewCount) {
+ Prefs.putInt(mContext, OVERVIEW_OPENED_COUNT, openedOverviewCount);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 4388b41..011be88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -62,6 +62,10 @@
mEmptyText.setText(mText);
}
+ public int getTextResource() {
+ return mText;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 46dee95..04bfcdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -177,8 +177,6 @@
@Override
public void onQuickStepStarted() {
- mNavigationBarView.onQuickStepStarted();
-
// Use navbar dragging as a signal to hide the rotate button
setRotateSuggestionButtonState(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 86411ac..c2053b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -287,12 +287,6 @@
notifyVerticalChangedListener(mVertical);
}
- public void onQuickStepStarted() {
- if (mRecentsOnboarding != null) {
- mRecentsOnboarding.onQuickStepStarted();
- }
- }
-
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mDeadZone.onTouchEvent(event)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index d3790d4..ff5d0e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -215,16 +215,16 @@
int pos, touchDown, offset, trackSize;
if (mIsVertical) {
- exceededScrubTouchSlop = yDiff > QUICK_STEP_TOUCH_SLOP_PX && yDiff > xDiff;
- exceededSwipeUpTouchSlop = xDiff > QUICK_STEP_DRAG_SLOP_PX && xDiff > yDiff;
+ exceededScrubTouchSlop = yDiff > QUICK_SCRUB_TOUCH_SLOP_PX && yDiff > xDiff;
+ exceededSwipeUpTouchSlop = xDiff > QUICK_STEP_TOUCH_SLOP_PX && xDiff > yDiff;
exceededScrubDragSlop = yDiff > QUICK_SCRUB_DRAG_SLOP_PX && yDiff > xDiff;
pos = y;
touchDown = mTouchDownY;
offset = pos - mTrackRect.top;
trackSize = mTrackRect.height();
} else {
- exceededScrubTouchSlop = xDiff > QUICK_STEP_TOUCH_SLOP_PX && xDiff > yDiff;
- exceededSwipeUpTouchSlop = yDiff > QUICK_SCRUB_TOUCH_SLOP_PX && yDiff > xDiff;
+ exceededScrubTouchSlop = xDiff > QUICK_SCRUB_TOUCH_SLOP_PX && xDiff > yDiff;
+ exceededSwipeUpTouchSlop = yDiff > QUICK_STEP_TOUCH_SLOP_PX && yDiff > xDiff;
exceededScrubDragSlop = xDiff > QUICK_SCRUB_DRAG_SLOP_PX && xDiff > yDiff;
pos = x;
touchDown = mTouchDownX;
@@ -407,6 +407,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Failed to send start of quick scrub.", e);
}
+ mOverviewEventSender.notifyQuickScrubStarted();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 17cdf4d..1d64088 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -784,6 +784,12 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mZenController.addCallback(new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ updateEmptyShadeView();
+ }
+ });
mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
this,
mNotificationPanel,
@@ -4683,7 +4689,8 @@
// tapping on a notification, editing QS or being dismissed by
// FLAG_DISMISS_KEYGUARD_ACTIVITY.
ScrimState state = mIsOccluded || mNotificationPanel.needsScrimming()
- || mStatusBarKeyguardViewManager.willDismissWithAction() ?
+ || mStatusBarKeyguardViewManager.willDismissWithAction()
+ || mStatusBarKeyguardViewManager.isFullscreenBouncer() ?
ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
mScrimController.transitionTo(state);
} else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e207eb0..04557b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -542,6 +542,10 @@
return mBouncer.isShowing();
}
+ public boolean isFullscreenBouncer() {
+ return mBouncer.isFullscreenBouncer();
+ }
+
private long getNavBarShowDelay() {
if (mStatusBar.isKeyguardFadingAway()) {
return mStatusBar.getKeyguardFadingAwayDelay();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index b693ebb..42e02d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -21,6 +21,7 @@
import com.android.systemui.statusbar.policy.BluetoothController.Callback;
import java.util.Collection;
+import java.util.List;
public interface BluetoothController extends CallbackController<Callback>, Dumpable {
boolean isBluetoothSupported();
@@ -30,7 +31,7 @@
boolean isBluetoothConnected();
boolean isBluetoothConnecting();
- String getLastDeviceName();
+ String getConnectedDeviceName();
void setBluetoothEnabled(boolean enabled);
Collection<CachedBluetoothDevice> getDevices();
void connect(CachedBluetoothDevice device);
@@ -39,7 +40,7 @@
int getMaxConnectionState(CachedBluetoothDevice device);
int getBondState(CachedBluetoothDevice device);
- CachedBluetoothDevice getLastDevice();
+ List<CachedBluetoothDevice> getConnectedDevices();
public interface Callback {
void onBluetoothStateChange(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index cd17cfc..44e87ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -31,6 +31,7 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.Dependency;
import java.io.FileDescriptor;
@@ -38,10 +39,11 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.WeakHashMap;
public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
- CachedBluetoothDevice.Callback {
+ CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
private static final String TAG = "BluetoothController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -51,10 +53,10 @@
private final WeakHashMap<CachedBluetoothDevice, ActuallyCachedState> mCachedState =
new WeakHashMap<>();
private final Handler mBgHandler;
+ private final List<CachedBluetoothDevice> mConnectedDevices = new ArrayList<>();
private boolean mEnabled;
private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
- private CachedBluetoothDevice mLastDevice;
private final H mHandler = new H(Looper.getMainLooper());
private int mState;
@@ -65,6 +67,7 @@
if (mLocalBluetoothManager != null) {
mLocalBluetoothManager.getEventManager().setReceiverHandler(mBgHandler);
mLocalBluetoothManager.getEventManager().registerCallback(this);
+ mLocalBluetoothManager.getProfileManager().addServiceListener(this);
onBluetoothStateChanged(
mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
}
@@ -88,11 +91,10 @@
}
pw.print(" mEnabled="); pw.println(mEnabled);
pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState));
- pw.print(" mLastDevice="); pw.println(mLastDevice);
+ pw.print(" mConnectedDevices="); pw.println(mConnectedDevices);
pw.print(" mCallbacks.size="); pw.println(mHandler.mCallbacks.size());
pw.println(" Bluetooth Devices:");
- for (CachedBluetoothDevice device :
- mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) {
+ for (CachedBluetoothDevice device : getDevices()) {
pw.println(" " + getDeviceString(device));
}
}
@@ -121,8 +123,8 @@
}
@Override
- public CachedBluetoothDevice getLastDevice() {
- return mLastDevice;
+ public List<CachedBluetoothDevice> getConnectedDevices() {
+ return mConnectedDevices;
}
@Override
@@ -186,8 +188,11 @@
}
@Override
- public String getLastDeviceName() {
- return mLastDevice != null ? mLastDevice.getName() : null;
+ public String getConnectedDeviceName() {
+ if (mConnectedDevices.size() == 1) {
+ return mConnectedDevices.get(0).getName();
+ }
+ return null;
}
@Override
@@ -200,10 +205,7 @@
private void updateConnected() {
// Make sure our connection state is up to date.
int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
- if (mLastDevice != null && !mLastDevice.isConnected()) {
- // Clear out last device if no longer connected.
- mLastDevice = null;
- }
+ mConnectedDevices.clear();
// If any of the devices are in a higher state than the adapter, move the adapter into
// that state.
for (CachedBluetoothDevice device : getDevices()) {
@@ -211,13 +213,12 @@
if (maxDeviceState > state) {
state = maxDeviceState;
}
- if (mLastDevice == null && device.isConnected()) {
- // Set as last connected device only if we don't have one.
- mLastDevice = device;
+ if (device.isConnected()) {
+ mConnectedDevices.add(device);
}
}
- if (mLastDevice == null && state == BluetoothAdapter.STATE_CONNECTED) {
+ if (mConnectedDevices.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) {
// If somehow we think we are connected, but have no connected devices, we aren't
// connected.
state = BluetoothAdapter.STATE_DISCONNECTED;
@@ -271,7 +272,6 @@
@Override
public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
mCachedState.remove(cachedDevice);
- mLastDevice = cachedDevice;
updateConnected();
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
@@ -293,6 +293,15 @@
return state;
}
+ @Override
+ public void onServiceConnected() {
+ updateConnected();
+ mHandler.sendEmptyMessage(H.MSG_PAIRED_DEVICES_CHANGED);
+ }
+
+ @Override
+ public void onServiceDisconnected() {}
+
private static class ActuallyCachedState implements Runnable {
private final WeakReference<CachedBluetoothDevice> mDevice;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index bc5a848..7c64811 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -111,6 +111,7 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.function.BiConsumer;
/**
@@ -4039,14 +4040,21 @@
public void updateEmptyShadeView(boolean visible) {
int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
int newVisibility = visible ? VISIBLE : GONE;
- if (oldVisibility != newVisibility) {
+
+ boolean changedVisibility = oldVisibility != newVisibility;
+ if (changedVisibility || newVisibility != GONE) {
if (newVisibility != GONE) {
+ int oldText = mEmptyShadeView.getTextResource();
+ int newText;
if (mStatusBar.areNotificationsHidden()) {
- mEmptyShadeView.setText(R.string.dnd_suppressing_shade_text);
+ newText = R.string.dnd_suppressing_shade_text;
} else {
- mEmptyShadeView.setText(R.string.empty_shade_text);
+ newText = R.string.empty_shade_text;
}
- showFooterView(mEmptyShadeView);
+ if (changedVisibility || !Objects.equals(oldText, newText)) {
+ mEmptyShadeView.setText(newText);
+ showFooterView(mEmptyShadeView);
+ }
} else {
hideFooterView(mEmptyShadeView, true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4f5ff60..5c7ce59 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -765,9 +765,9 @@
if (max != row.slider.getMax()) {
row.slider.setMax(max);
}
- // update A11y slider min
+ // update slider min
final int min = ss.levelMin * 100;
- if (isA11yStream && min != row.slider.getMin()) {
+ if (min != row.slider.getMin()) {
row.slider.setMin(min);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 4e7550b..54153a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -35,6 +35,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -68,6 +69,8 @@
mMockAdapter = mock(LocalBluetoothAdapter.class);
when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockAdapter);
when(mMockBluetoothManager.getEventManager()).thenReturn(mock(BluetoothEventManager.class));
+ when(mMockBluetoothManager.getProfileManager())
+ .thenReturn(mock(LocalBluetoothProfileManager.class));
mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
mTestableLooper.getLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
index dd2b581..eeb4209 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
@@ -141,6 +141,19 @@
}
@Test
+ public void updateEmptyView_noNotificationsToDndSuppressing() {
+ mStackScroller.setEmptyShadeView(mEmptyShadeView);
+ when(mEmptyShadeView.willBeGone()).thenReturn(true);
+ when(mBar.areNotificationsHidden()).thenReturn(false);
+ mStackScroller.updateEmptyShadeView(true);
+ verify(mEmptyShadeView).setText(R.string.empty_shade_text);
+
+ when(mBar.areNotificationsHidden()).thenReturn(true);
+ mStackScroller.updateEmptyShadeView(true);
+ verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
+ }
+
+ @Test
@UiThreadTest
public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
mStackScroller.setExpandedHeight(0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 44c4983..cac6bf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -21,6 +21,8 @@
import com.android.systemui.statusbar.policy.BluetoothController.Callback;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
public class FakeBluetoothController extends BaseLeakChecker<Callback> implements
BluetoothController {
@@ -55,7 +57,7 @@
}
@Override
- public String getLastDeviceName() {
+ public String getConnectedDeviceName() {
return null;
}
@@ -95,7 +97,7 @@
}
@Override
- public CachedBluetoothDevice getLastDevice() {
- return null;
+ public List<CachedBluetoothDevice> getConnectedDevices() {
+ return Collections.emptyList();
}
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 1f1ed59..cd2e2a4 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -178,6 +178,16 @@
TEXT_SELECTION_INVOCATION_LINK = 2;
}
+ // Access method for hidden API events. Type of data tagged with
+ // FIELD_HIDDEN_API_ACCESS_METHOD.
+ // This must be kept in sync with enum AccessMethod in art/runtime/hidden_api.h
+ enum HiddenApiAccessMethod {
+ ACCESS_METHOD_NONE = 0; // never logged, included for completeness
+ ACCESS_METHOD_REFLECTION = 1;
+ ACCESS_METHOD_JNI = 2;
+ ACCESS_METHOD_LINKING = 3; // never logged, included for completeness
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -5664,6 +5674,25 @@
// OS: P
BLUETOOTH_FRAGMENT = 1390;
+ // Enclosing category for group of FIELD_HIDDEN_API_FOO events, logged when
+ // an app uses a hidden API.
+ ACTION_HIDDEN_API_ACCESSED = 1391;
+
+ // Tagged data for ACTION_HIDDEN_API_ACCESSED. The metod of the hidden API
+ // access; see enum HiddenApiAccessMethod
+ // OS: P
+ FIELD_HIDDEN_API_ACCESS_METHOD = 1392;
+
+ // Tagged data for ACTION_HIDDEN_API_ACCESSED. Indicates that access was
+ // denied to the API.
+ // OS: P
+ FIELD_HIDDEN_API_ACCESS_DENIED = 1393;
+
+ // Tagged data for ACTION_HIDDEN_API_ACCESSED. The signature of the hidden
+ // API that was accessed.
+ // OS: P
+ FIELD_HIDDEN_API_SIGNATURE = 1394;
+
// This value should never appear in log outputs - it is reserved for
// internal platform metrics use.
NOTIFICATION_SHADE_COUNT = 1395;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 06707da..1d62eb7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -271,7 +271,8 @@
// Sanitize structure before it's sent to service.
final ComponentName componentNameFromApp = structure.getActivityComponent();
- if (!mComponentName.equals(componentNameFromApp)) {
+ if (componentNameFromApp == null || !mComponentName.getPackageName()
+ .equals(componentNameFromApp.getPackageName())) {
Slog.w(TAG, "Activity " + mComponentName + " forged different component on "
+ "AssistStructure: " + componentNameFromApp);
structure.setActivityComponent(mComponentName);
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index f678eed..5b446ca 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -3950,6 +3950,7 @@
if (mSwitchingDialog != null) {
mSwitchingDialog.dismiss();
mSwitchingDialog = null;
+ mSwitchingDialogTitleView = null;
}
updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 379658f..99e0459 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2667,9 +2667,17 @@
public void mkdirs(String callingPkg, String appPath) {
final int userId = UserHandle.getUserId(Binder.getCallingUid());
final UserEnvironment userEnv = new UserEnvironment(userId);
+ final String propertyName = "sys.user." + userId + ".ce_available";
// Ignore requests to create directories while storage is locked
- if (!isUserKeyUnlocked(userId)) return;
+ if (!isUserKeyUnlocked(userId)) {
+ throw new IllegalStateException("Failed to prepare " + appPath);
+ }
+
+ // Ignore requests to create directories if CE storage is not available
+ if (!SystemProperties.getBoolean(propertyName, false)) {
+ throw new IllegalStateException("Failed to prepare " + appPath);
+ }
// Validate that reported package name belongs to caller
final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index c8e0a5e..2e258c1 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -96,6 +96,7 @@
"android.hardware.camera.provider@2.4::ICameraProvider",
"android.hardware.graphics.composer@2.1::IComposer",
"android.hardware.media.omx@1.0::IOmx",
+ "android.hardware.media.omx@1.0::IOmxStore",
"android.hardware.sensors@1.0::ISensors",
"android.hardware.vr@1.0::IVr"
);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index fc047bc..228171f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -24,15 +24,19 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
import android.app.ActivityThread;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.ServiceStartArgs;
+import android.content.ComponentName.WithComponentName;
import android.content.IIntentSender;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
@@ -55,6 +59,8 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.server.AppStateTracker;
import com.android.server.LocalServices;
@@ -4063,57 +4069,26 @@
* - the first arg isn't the flattened component name of an existing service:
* dump all services whose component contains the first arg as a substring
*/
- protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, String[] args,
- int opti, boolean dumpAll) {
- ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+ protected boolean dumpService(FileDescriptor fd, PrintWriter pw, final String name,
+ String[] args, int opti, boolean dumpAll) {
+ final ArrayList<ServiceRecord> services = new ArrayList<>();
+
+ final Predicate<ServiceRecord> filter = DumpUtils.filterRecord(name);
synchronized (mAm) {
int[] users = mAm.mUserController.getUsers();
- if ("all".equals(name)) {
- for (int user : users) {
- ServiceMap smap = mServiceMap.get(user);
- if (smap == null) {
- continue;
- }
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
- for (int i=0; i<alls.size(); i++) {
- ServiceRecord r1 = alls.valueAt(i);
- services.add(r1);
- }
- }
- } else {
- ComponentName componentName = name != null
- ? ComponentName.unflattenFromString(name) : null;
- int objectId = 0;
- if (componentName == null) {
- // Not a '/' separated full component name; maybe an object ID?
- try {
- objectId = Integer.parseInt(name, 16);
- name = null;
- componentName = null;
- } catch (RuntimeException e) {
- }
- }
- for (int user : users) {
- ServiceMap smap = mServiceMap.get(user);
- if (smap == null) {
- continue;
- }
- ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
- for (int i=0; i<alls.size(); i++) {
- ServiceRecord r1 = alls.valueAt(i);
- if (componentName != null) {
- if (r1.name.equals(componentName)) {
- services.add(r1);
- }
- } else if (name != null) {
- if (r1.name.flattenToString().contains(name)) {
- services.add(r1);
- }
- } else if (System.identityHashCode(r1) == objectId) {
- services.add(r1);
- }
+ for (int user : users) {
+ ServiceMap smap = mServiceMap.get(user);
+ if (smap == null) {
+ continue;
+ }
+ ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+ for (int i=0; i<alls.size(); i++) {
+ ServiceRecord r1 = alls.valueAt(i);
+
+ if (filter.test(r1)) {
+ services.add(r1);
}
}
}
@@ -4123,6 +4098,9 @@
return false;
}
+ // Sort by component name.
+ services.sort(Comparator.comparing(WithComponentName::getComponentName));
+
boolean needSep = false;
for (int i=0; i<services.size(); i++) {
if (needSep) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 92cf1d52..6951c50 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -95,6 +95,7 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE;
import static android.os.Process.THREAD_GROUP_DEFAULT;
+import static android.os.Process.THREAD_GROUP_RESTRICTED;
import static android.os.Process.THREAD_GROUP_TOP_APP;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
@@ -2945,7 +2946,11 @@
? Collections.emptyList()
: Arrays.asList(exemptions.split(","));
}
- zygoteProcess.setApiBlacklistExemptions(mExemptions);
+ if (!zygoteProcess.setApiBlacklistExemptions(mExemptions)) {
+ Slog.e(TAG, "Failed to set API blacklist exemptions!");
+ // leave mExemptionsStr as is, so we don't try to send the same list again.
+ mExemptions = Collections.emptyList();
+ }
}
int logSampleRate = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.HIDDEN_API_ACCESS_LOG_SAMPLING_RATE, -1);
@@ -13193,6 +13198,7 @@
mHandler.obtainMessage(DISPATCH_SCREEN_AWAKE_MSG, isAwake ? 1 : 0, 0)
.sendToTarget();
}
+ updateOomAdjLocked();
}
}
@@ -18182,6 +18188,9 @@
case ProcessList.SCHED_GROUP_TOP_APP:
schedGroup = 'T';
break;
+ case ProcessList.SCHED_GROUP_RESTRICTED:
+ schedGroup = 'R';
+ break;
default:
schedGroup = '?';
break;
@@ -23007,8 +23016,8 @@
app.curSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
app.adjType = "pers-top-activity";
} else if (app.hasTopUi) {
+ // sched group/proc state adjustment is below
app.systemNoUi = false;
- app.curSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
app.adjType = "pers-top-ui";
} else if (activitiesSize > 0) {
for (int j = 0; j < activitiesSize; j++) {
@@ -23019,7 +23028,15 @@
}
}
if (!app.systemNoUi) {
- app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+ if (mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ // screen on, promote UI
+ app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+ app.curSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+ } else {
+ // screen off, restrict UI scheduling
+ app.curProcState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ app.curSchedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
+ }
}
return (app.curAdj=app.maxAdj);
}
@@ -23877,6 +23894,15 @@
}
}
+ // Put bound foreground services in a special sched group for additional
+ // restrictions on screen off
+ if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE &&
+ mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
+ schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
+ }
+ }
+
// Do final modification to adj. Everything we do between here and applying
// the final setAdj must be done in this function, because we will also use
// it when computing the final cached adj later. Note that we don't need to
@@ -24299,6 +24325,9 @@
case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
processGroup = THREAD_GROUP_TOP_APP;
break;
+ case ProcessList.SCHED_GROUP_RESTRICTED:
+ processGroup = THREAD_GROUP_RESTRICTED;
+ break;
default:
processGroup = THREAD_GROUP_DEFAULT;
break;
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index 7b9b659..cd39bcd 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -32,7 +32,7 @@
import java.util.ArrayList;
import java.util.HashMap;
-final class ContentProviderRecord {
+final class ContentProviderRecord implements ComponentName.WithComponentName {
final ActivityManagerService service;
public final ProviderInfo info;
final int uid;
@@ -260,4 +260,8 @@
}
}
}
+
+ public ComponentName getComponentName() {
+ return name;
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index bf7aef9..784d62e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -128,13 +128,15 @@
// Activity manager's version of Process.THREAD_GROUP_BG_NONINTERACTIVE
static final int SCHED_GROUP_BACKGROUND = 0;
+ // Activity manager's version of Process.THREAD_GROUP_RESTRICTED
+ static final int SCHED_GROUP_RESTRICTED = 1;
// Activity manager's version of Process.THREAD_GROUP_DEFAULT
- static final int SCHED_GROUP_DEFAULT = 1;
+ static final int SCHED_GROUP_DEFAULT = 2;
// Activity manager's version of Process.THREAD_GROUP_TOP_APP
- static final int SCHED_GROUP_TOP_APP = 2;
+ static final int SCHED_GROUP_TOP_APP = 3;
// Activity manager's version of Process.THREAD_GROUP_TOP_APP
// Disambiguate between actual top app and processes bound to the top app
- static final int SCHED_GROUP_TOP_APP_BOUND = 3;
+ static final int SCHED_GROUP_TOP_APP_BOUND = 4;
// The minimum number of cached apps we want to be able to keep around,
// without empty apps being able to push them out of memory.
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index 8a905f8..2f52002 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -17,22 +17,28 @@
package com.android.server.am;
import android.content.ComponentName;
+import android.content.ComponentName.WithComponentName;
import android.os.Binder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Keeps track of content providers by authority (name) and class. It separates the mapping by
@@ -325,7 +331,9 @@
private ArrayList<ContentProviderRecord> getProvidersForName(String name) {
ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
- ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
+ final ArrayList<ContentProviderRecord> ret = new ArrayList<>();
+
+ final Predicate<ContentProviderRecord> filter = DumpUtils.filterRecord(name);
synchronized (mAm) {
allProviders.addAll(mSingletonByClass.values());
@@ -333,39 +341,11 @@
allProviders.addAll(mProvidersByClassPerUser.valueAt(i).values());
}
- if ("all".equals(name)) {
- providers.addAll(allProviders);
- } else {
- ComponentName componentName = name != null
- ? ComponentName.unflattenFromString(name) : null;
- int objectId = 0;
- if (componentName == null) {
- // Not a '/' separated full component name; maybe an object ID?
- try {
- objectId = Integer.parseInt(name, 16);
- name = null;
- componentName = null;
- } catch (RuntimeException e) {
- }
- }
-
- for (int i=0; i<allProviders.size(); i++) {
- ContentProviderRecord r1 = allProviders.get(i);
- if (componentName != null) {
- if (r1.name.equals(componentName)) {
- providers.add(r1);
- }
- } else if (name != null) {
- if (r1.name.flattenToString().contains(name)) {
- providers.add(r1);
- }
- } else if (System.identityHashCode(r1) == objectId) {
- providers.add(r1);
- }
- }
- }
+ CollectionUtils.addIf(allProviders, ret, filter);
}
- return providers;
+ // Sort by component name.
+ ret.sort(Comparator.comparing(WithComponentName::getComponentName));
+ return ret;
}
protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8a174ed..32887e4 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -56,7 +56,7 @@
/**
* A running application service.
*/
-final class ServiceRecord extends Binder {
+final class ServiceRecord extends Binder implements ComponentName.WithComponentName {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ServiceRecord" : TAG_AM;
// Maximum number of delivery attempts before giving up.
@@ -757,4 +757,8 @@
.append(' ').append(shortName).append('}');
return stringName = sb.toString();
}
+
+ public ComponentName getComponentName() {
+ return name;
+ }
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index decae18..a55870f 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1980,6 +1980,9 @@
}
static String formatTime(long time) {
+ if (time == 0) {
+ return "N/A";
+ }
Time tobj = new Time();
tobj.set(time);
return tobj.format("%Y-%m-%d %H:%M:%S");
@@ -2334,13 +2337,28 @@
pw.print("]");
pw.println();
+ pw.println(" Per source last syncs:");
+ for (int j = 0; j < SyncStorageEngine.SOURCES.length; j++) {
+ pw.print(" ");
+ pw.print(String.format("%8s", SyncStorageEngine.SOURCES[j]));
+ pw.print(" Success: ");
+ pw.print(formatTime(event.second.perSourceLastSuccessTimes[j]));
+
+ pw.print(" Failure: ");
+ pw.println(formatTime(event.second.perSourceLastFailureTimes[j]));
+ }
+
+ pw.println(" Last syncs:");
for (int j = 0; j < event.second.getEventCount(); j++) {
- pw.print(" ");
+ pw.print(" ");
pw.print(formatTime(event.second.getEventTime(j)));
pw.print(' ');
pw.print(event.second.getEvent(j));
pw.println();
}
+ if (event.second.getEventCount() == 0) {
+ pw.println(" N/A");
+ }
}
}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index f54a9a0..6a343f8 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -128,8 +128,13 @@
public static final long NOT_IN_BACKOFF_MODE = -1;
- /** String names for the sync source types. */
- public static final String[] SOURCES = { "OTHER",
+ /**
+ * String names for the sync source types.
+ *
+ * KEEP THIS AND {@link SyncStatusInfo#SOURCE_COUNT} IN SYNC.
+ */
+ public static final String[] SOURCES = {
+ "OTHER",
"LOCAL",
"POLL",
"USER",
@@ -1231,12 +1236,7 @@
if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
writeStatusNow = true;
}
- status.lastSuccessTime = lastSyncTime;
- status.lastSuccessSource = item.source;
- status.lastFailureTime = 0;
- status.lastFailureSource = -1;
- status.lastFailureMesg = null;
- status.initialFailureTime = 0;
+ status.setLastSuccess(item.source, lastSyncTime);
ds.successCount++;
ds.successTime += elapsedTime;
} else if (!MESG_CANCELED.equals(resultMessage)) {
@@ -1246,12 +1246,8 @@
status.totalStats.numFailures++;
status.todayStats.numFailures++;
- status.lastFailureTime = lastSyncTime;
- status.lastFailureSource = item.source;
- status.lastFailureMesg = resultMessage;
- if (status.initialFailureTime == 0) {
- status.initialFailureTime = lastSyncTime;
- }
+ status.setLastFailure(item.source, lastSyncTime, resultMessage);
+
ds.failureCount++;
ds.failureTime += elapsedTime;
} else {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 53e741e..5948864 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2397,6 +2397,11 @@
}
@Override
+ public boolean areChannelsBypassingDnd() {
+ return mRankingHelper.areChannelsBypassingDnd();
+ }
+
+ @Override
public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException {
checkCallerIsSystem();
@@ -3278,7 +3283,6 @@
policy = new Policy(policy.priorityCategories,
policy.priorityCallSenders, policy.priorityMessageSenders,
newVisualEffects);
-
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy);
mZenModeHelper.setNotificationPolicy(policy);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 72a1a71..2aec3ea 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -15,10 +15,6 @@
*/
package com.android.server.notification;
-import static android.app.Notification.EXTRA_AUDIO_CONTENTS_URI;
-import static android.app.Notification.EXTRA_BACKGROUND_IMAGE_URI;
-import static android.app.Notification.EXTRA_HISTORIC_MESSAGES;
-import static android.app.Notification.EXTRA_MESSAGES;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -32,10 +28,8 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.Notification;
-import android.app.Notification.MessagingStyle;
import android.app.NotificationChannel;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -55,7 +49,6 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -75,7 +68,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.ArrayUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -1029,36 +1021,14 @@
*/
private void calculateGrantableUris() {
final Notification notification = getNotification();
+ notification.visitUris((uri) -> {
+ visitGrantableUri(uri);
+ });
- noteGrantableUri(notification.sound);
if (notification.getChannelId() != null) {
NotificationChannel channel = getChannel();
if (channel != null) {
- noteGrantableUri(channel.getSound());
- }
- }
-
- final Bundle extras = notification.extras;
- if (extras != null) {
- noteGrantableUri(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI));
- noteGrantableUri(extras.getParcelable(EXTRA_BACKGROUND_IMAGE_URI));
- }
-
- if (MessagingStyle.class.equals(notification.getNotificationStyle()) && extras != null) {
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (!ArrayUtils.isEmpty(messages)) {
- for (MessagingStyle.Message message : MessagingStyle.Message
- .getMessagesFromBundleArray(messages)) {
- noteGrantableUri(message.getDataUri());
- }
- }
-
- final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
- if (!ArrayUtils.isEmpty(historic)) {
- for (MessagingStyle.Message message : MessagingStyle.Message
- .getMessagesFromBundleArray(historic)) {
- noteGrantableUri(message.getDataUri());
- }
+ visitGrantableUri(channel.getSound());
}
}
}
@@ -1071,7 +1041,7 @@
* {@link #mGrantableUris}. Otherwise, this will either log or throw
* {@link SecurityException} depending on target SDK of enqueuing app.
*/
- private void noteGrantableUri(Uri uri) {
+ private void visitGrantableUri(Uri uri) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
// We can't grant Uri permissions from system
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 89bd660..febce31 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -17,13 +17,6 @@
import static android.app.NotificationManager.IMPORTANCE_NONE;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.Notification;
@@ -52,6 +45,13 @@
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -65,11 +65,11 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
public class RankingHelper implements RankingConfig {
private static final String TAG = "RankingHelper";
@@ -127,12 +127,15 @@
private String mPermissionControllerPackageName;
private String mServicesSystemSharedLibPackageName;
private String mSharedSystemSharedLibPackageName;
+ private boolean mAreChannelsBypassingDnd;
+ private ZenModeHelper mZenModeHelper;
public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
mContext = context;
mRankingHandler = rankingHandler;
mPm = pm;
+ mZenModeHelper= zenHelper;
mPreliminaryComparator = new NotificationComparator(mContext);
@@ -159,6 +162,7 @@
}
getSignatures();
+ updateChannelsBypassingDnd();
}
@SuppressWarnings("unchecked")
@@ -648,7 +652,12 @@
// system apps and dnd access apps can bypass dnd if the user hasn't changed any
// fields on the channel yet
if (existing.getUserLockedFields() == 0 && (isSystemApp || hasDndAccess)) {
- existing.setBypassDnd(channel.canBypassDnd());
+ boolean bypassDnd = channel.canBypassDnd();
+ existing.setBypassDnd(bypassDnd);
+
+ if (bypassDnd != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
}
updateConfig();
@@ -675,6 +684,9 @@
}
r.channels.put(channel.getId(), channel);
+ if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
MetricsLogger.action(getChannelLog(channel, pkg).setType(
MetricsProto.MetricsEvent.TYPE_OPEN));
}
@@ -781,6 +793,10 @@
// only log if there are real changes
MetricsLogger.action(getChannelLog(updatedChannel, pkg));
}
+
+ if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
updateConfig();
}
@@ -814,6 +830,10 @@
LogMaker lm = getChannelLog(channel, pkg);
lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
MetricsLogger.action(lm);
+
+ if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
+ updateChannelsBypassingDnd();
+ }
}
}
@@ -1026,6 +1046,45 @@
return count;
}
+ public void updateChannelsBypassingDnd() {
+ synchronized (mRecords) {
+ final int numRecords = mRecords.size();
+ for (int recordIndex = 0; recordIndex < numRecords; recordIndex++) {
+ final Record r = mRecords.valueAt(recordIndex);
+ final int numChannels = r.channels.size();
+
+ for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
+ NotificationChannel channel = r.channels.valueAt(channelIndex);
+ if (!channel.isDeleted() && channel.canBypassDnd()) {
+ if (!mAreChannelsBypassingDnd) {
+ mAreChannelsBypassingDnd = true;
+ updateZenPolicy(true);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ if (mAreChannelsBypassingDnd) {
+ mAreChannelsBypassingDnd = false;
+ updateZenPolicy(false);
+ }
+ }
+
+ public void updateZenPolicy(boolean areChannelsBypassingDnd) {
+ NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
+ mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
+ policy.priorityCategories, policy.priorityCallSenders,
+ policy.priorityMessageSenders, policy.suppressedVisualEffects,
+ (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
+ : 0)));
+ }
+
+ public boolean areChannelsBypassingDnd() {
+ return mAreChannelsBypassingDnd;
+ }
+
/**
* Sets importance.
*/
@@ -1225,12 +1284,16 @@
if (r.showBadge != DEFAULT_SHOW_BADGE) {
record.put("showBadge", Boolean.valueOf(r.showBadge));
}
+ JSONArray channels = new JSONArray();
for (NotificationChannel channel : r.channels.values()) {
- record.put("channel", channel.toJson());
+ channels.put(channel.toJson());
}
+ record.put("channels", channels);
+ JSONArray groups = new JSONArray();
for (NotificationChannelGroup group : r.groups.values()) {
- record.put("group", group.toJson());
+ groups.put(group.toJson());
}
+ record.put("groups", groups);
} catch (JSONException e) {
// pass
}
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index e0b8426..c64e745 100644
--- a/services/core/java/com/android/server/os/SchedulingPolicyService.java
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -18,8 +18,10 @@
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.IBinder;
import android.os.ISchedulingPolicyService;
import android.os.Process;
+import android.os.RemoteException;
import android.util.Log;
/**
@@ -35,7 +37,36 @@
private static final int PRIORITY_MIN = 1;
private static final int PRIORITY_MAX = 3;
+ private static final String[] MEDIA_PROCESS_NAMES = new String[] {
+ "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service
+ };
+ private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ requestCpusetBoost(false /*enable*/, null /*client*/);
+ }
+ };
+ // Current process that received a cpuset boost
+ private int mBoostedPid = -1;
+ // Current client registered to the death recipient
+ private IBinder mClient;
+
public SchedulingPolicyService() {
+ // system_server (our host) could have crashed before. The app may not survive
+ // it, but mediaserver/media.codec could have, and mediaserver probably tried
+ // to disable the boost while we were dead.
+ // We do a restore of media.codec to default cpuset upon service restart to
+ // catch this case. We can't leave media.codec in boosted state, because we've
+ // lost the death recipient of mClient from mediaserver after the restart,
+ // if mediaserver dies in the future we won't have a notification to reset.
+ // (Note that if mediaserver thinks we're in boosted state before the crash,
+ // the state could go out of sync temporarily until mediaserver enables/disable
+ // boost next time, but this won't be a big issue.)
+ int[] nativePids = Process.getPidsForCommands(MEDIA_PROCESS_NAMES);
+ if (nativePids != null && nativePids.length == 1) {
+ mBoostedPid = nativePids[0];
+ disableCpusetBoost(nativePids[0]);
+ }
}
// TODO(b/35196900) We should pass the period in time units, rather
@@ -74,6 +105,94 @@
return PackageManager.PERMISSION_GRANTED;
}
+ // Request to move media.codec process between SP_FOREGROUND and SP_TOP_APP.
+ public int requestCpusetBoost(boolean enable, IBinder client) {
+ if (!isPermitted()) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ int[] nativePids = Process.getPidsForCommands(MEDIA_PROCESS_NAMES);
+ if (nativePids == null || nativePids.length != 1) {
+ Log.e(TAG, "requestCpusetBoost: can't find media.codec process");
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ synchronized (mDeathRecipient) {
+ if (enable) {
+ return enableCpusetBoost(nativePids[0], client);
+ } else {
+ return disableCpusetBoost(nativePids[0]);
+ }
+ }
+ }
+
+ private int enableCpusetBoost(int pid, IBinder client) {
+ if (mBoostedPid == pid) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+ // The mediacodec process has changed, clean up the old pid and
+ // client before we boost the new process, so that the state
+ // is left clean if things go wrong.
+ mBoostedPid = -1;
+ if (mClient != null) {
+ try {
+ mClient.unlinkToDeath(mDeathRecipient, 0);
+ } catch (Exception e) {
+ } finally {
+ mClient = null;
+ }
+ }
+
+ try {
+ client.linkToDeath(mDeathRecipient, 0);
+
+ Log.i(TAG, "Moving " + pid + " to group " + Process.THREAD_GROUP_TOP_APP);
+ Process.setProcessGroup(pid, Process.THREAD_GROUP_TOP_APP);
+
+ mBoostedPid = pid;
+ mClient = client;
+
+ return PackageManager.PERMISSION_GRANTED;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed enableCpusetBoost: " + e);
+ try {
+ // unlink if things go wrong and don't crash.
+ client.unlinkToDeath(mDeathRecipient, 0);
+ } catch (Exception e1) {}
+ }
+
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ private int disableCpusetBoost(int pid) {
+ int boostedPid = mBoostedPid;
+
+ // Clean up states first.
+ mBoostedPid = -1;
+ if (mClient != null) {
+ try {
+ mClient.unlinkToDeath(mDeathRecipient, 0);
+ } catch (Exception e) {
+ } finally {
+ mClient = null;
+ }
+ }
+
+ // Try restore the old thread group, no need to fail as the
+ // mediacodec process could be dead just now.
+ if (boostedPid == pid) {
+ try {
+ Log.i(TAG, "Moving " + pid + " back to group default");
+ Process.setProcessGroup(pid, Process.THREAD_GROUP_DEFAULT);
+ } catch (Exception e) {
+ Log.w(TAG, "Couldn't move pid " + pid + " back to group default");
+ }
+ }
+
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
private boolean isPermitted() {
// schedulerservice hidl
if (Binder.getCallingPid() == Process.myPid()) {
@@ -81,9 +200,10 @@
}
switch (Binder.getCallingUid()) {
- case Process.AUDIOSERVER_UID: // fastcapture, fastmixer
+ case Process.AUDIOSERVER_UID: // fastcapture, fastmixer
+ case Process.MEDIA_UID: // mediaserver
case Process.CAMERASERVER_UID: // camera high frame rate recording
- case Process.BLUETOOTH_UID: // Bluetooth audio playback
+ case Process.BLUETOOTH_UID: // Bluetooth audio playback
return true;
default:
return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 50ac4db..9fce12c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -23603,7 +23603,8 @@
private SigningDetails getSigningDetails(int uid) {
synchronized (mPackages) {
- final Object obj = mSettings.getUserIdLPr(uid);
+ final int appId = UserHandle.getAppId(uid);
+ final Object obj = mSettings.getUserIdLPr(appId);
if (obj != null) {
if (obj instanceof SharedUserSetting) {
return ((SharedUserSetting) obj).signatures.mSigningDetails;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index d123099c..0ccbb25 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -674,7 +674,7 @@
final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<WallpaperData>();
final SparseArray<Boolean> mUserRestorecon = new SparseArray<Boolean>();
- int mCurrentUserId;
+ int mCurrentUserId = UserHandle.USER_NULL;
boolean mInAmbientMode;
static class WallpaperData {
@@ -1166,7 +1166,11 @@
mIPackageManager = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mMonitor = new MyPackageMonitor();
- mMonitor.register(context, null, UserHandle.ALL, true);
+ mColorsChangedListeners = new SparseArray<>();
+ }
+
+ void initialize() {
+ mMonitor.register(mContext, null, UserHandle.ALL, true);
getWallpaperDir(UserHandle.USER_SYSTEM).mkdirs();
// Initialize state from the persistent store, then guarantee that the
@@ -1174,8 +1178,6 @@
// it from defaults if necessary.
loadSettingsLocked(UserHandle.USER_SYSTEM, false);
getWallpaperSafeLocked(UserHandle.USER_SYSTEM, FLAG_SYSTEM);
-
- mColorsChangedListeners = new SparseArray<>();
}
private static File getWallpaperDir(int userId) {
@@ -1193,6 +1195,8 @@
void systemReady() {
if (DEBUG) Slog.v(TAG, "systemReady");
+ initialize();
+
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM);
// If we think we're going to be using the system image wallpaper imagery, make
// sure we have something to render
@@ -1344,6 +1348,9 @@
final WallpaperData systemWallpaper;
final WallpaperData lockWallpaper;
synchronized (mLock) {
+ if (mCurrentUserId == userId) {
+ return;
+ }
mCurrentUserId = userId;
systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index ff2f687..a24ac21 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -82,7 +82,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.ResourceId;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -556,16 +555,16 @@
}
Animation loadAnimationAttr(LayoutParams lp, int animAttr) {
- int anim = ResourceId.ID_NULL;
+ int anim = 0;
Context context = mContext;
- if (ResourceId.isValid(animAttr)) {
+ if (animAttr >= 0) {
AttributeCache.Entry ent = getCachedAnimations(lp);
if (ent != null) {
context = ent.context;
anim = ent.array.getResourceId(animAttr, 0);
}
}
- if (ResourceId.isValid(anim)) {
+ if (anim != 0) {
return AnimationUtils.loadAnimation(context, anim);
}
return null;
@@ -573,7 +572,7 @@
Animation loadAnimationRes(LayoutParams lp, int resId) {
Context context = mContext;
- if (ResourceId.isValid(resId)) {
+ if (resId >= 0) {
AttributeCache.Entry ent = getCachedAnimations(lp);
if (ent != null) {
context = ent.context;
@@ -584,16 +583,16 @@
}
private Animation loadAnimationRes(String packageName, int resId) {
- int anim = ResourceId.ID_NULL;
+ int anim = 0;
Context context = mContext;
- if (ResourceId.isValid(resId)) {
+ if (resId >= 0) {
AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
if (ent != null) {
context = ent.context;
anim = resId;
}
}
- if (ResourceId.isValid(anim)) {
+ if (anim != 0) {
return AnimationUtils.loadAnimation(context, anim);
}
return null;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 5676f58..a701d42 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -1662,7 +1662,9 @@
}
SurfaceControl getAppAnimationLayer() {
- return getAppAnimationLayer(needsZBoost());
+ return getAppAnimationLayer(isActivityTypeHome() ? ANIMATION_LAYER_HOME
+ : needsZBoost() ? ANIMATION_LAYER_BOOSTED
+ : ANIMATION_LAYER_STANDARD);
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 79eb2c9..4fd31ff 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3190,6 +3190,7 @@
*/
SurfaceControl mAppAnimationLayer = null;
SurfaceControl mBoostedAppAnimationLayer = null;
+ SurfaceControl mHomeAppAnimationLayer = null;
/**
* Given that the split-screen divider does not have an AppWindowToken, it
@@ -3552,6 +3553,7 @@
int layer = 0;
int layerForAnimationLayer = 0;
int layerForBoostedAnimationLayer = 0;
+ int layerForHomeAnimationLayer = 0;
for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
for (int i = 0; i < mChildren.size(); i++) {
@@ -3578,6 +3580,9 @@
layerForBoostedAnimationLayer = layer++;
}
}
+ if (state == HOME_STACK_STATE) {
+ layerForHomeAnimationLayer = layer++;
+ }
}
if (mAppAnimationLayer != null) {
t.setLayer(mAppAnimationLayer, layerForAnimationLayer);
@@ -3585,11 +3590,22 @@
if (mBoostedAppAnimationLayer != null) {
t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
}
+ if (mHomeAppAnimationLayer != null) {
+ t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
+ }
}
@Override
- SurfaceControl getAppAnimationLayer(boolean boosted) {
- return boosted ? mBoostedAppAnimationLayer : mAppAnimationLayer;
+ SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
+ switch (animationLayer) {
+ case ANIMATION_LAYER_BOOSTED:
+ return mBoostedAppAnimationLayer;
+ case ANIMATION_LAYER_HOME:
+ return mHomeAppAnimationLayer;
+ case ANIMATION_LAYER_STANDARD:
+ default:
+ return mAppAnimationLayer;
+ }
}
SurfaceControl getSplitScreenDividerAnchor() {
@@ -3606,12 +3622,16 @@
mBoostedAppAnimationLayer = makeChildSurface(null)
.setName("boostedAnimationLayer")
.build();
+ mHomeAppAnimationLayer = makeChildSurface(null)
+ .setName("homeAnimationLayer")
+ .build();
mSplitScreenDividerAnchor = makeChildSurface(null)
.setName("splitScreenDividerAnchor")
.build();
getPendingTransaction()
.show(mAppAnimationLayer)
.show(mBoostedAppAnimationLayer)
+ .show(mHomeAppAnimationLayer)
.show(mSplitScreenDividerAnchor);
scheduleAnimation();
} else {
@@ -3619,6 +3639,8 @@
mAppAnimationLayer = null;
mBoostedAppAnimationLayer.destroy();
mBoostedAppAnimationLayer = null;
+ mHomeAppAnimationLayer.destroy();
+ mHomeAppAnimationLayer = null;
mSplitScreenDividerAnchor.destroy();
mSplitScreenDividerAnchor = null;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 95223d8..f87538a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -564,7 +564,7 @@
public SurfaceControl getAnimationLeashParent() {
// Reparent to the animation layer so that we aren't clipped by the non-minimized
// stack bounds, currently we only animate the task for the recents animation
- return getAppAnimationLayer(false /* boosted */);
+ return getAppAnimationLayer(ANIMATION_LAYER_STANDARD);
}
boolean isTaskAnimating() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 60e7c0d..331a0bd 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -29,6 +29,8 @@
import static com.android.server.wm.WindowContainerProto.VISIBLE;
import android.annotation.CallSuper;
+import android.annotation.IntDef;
+import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -60,6 +62,25 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
+ /** Animation layer that happens above all animating {@link TaskStack}s. */
+ static final int ANIMATION_LAYER_STANDARD = 0;
+
+ /** Animation layer that happens above all {@link TaskStack}s. */
+ static final int ANIMATION_LAYER_BOOSTED = 1;
+
+ /**
+ * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME}
+ * activities that happens below all {@link TaskStack}s.
+ */
+ static final int ANIMATION_LAYER_HOME = 2;
+
+ @IntDef(prefix = { "ANIMATION_LAYER_" }, value = {
+ ANIMATION_LAYER_STANDARD,
+ ANIMATION_LAYER_BOOSTED,
+ ANIMATION_LAYER_HOME,
+ })
+ @interface AnimationLayer {}
+
static final int POSITION_TOP = Integer.MAX_VALUE;
static final int POSITION_BOTTOM = Integer.MIN_VALUE;
@@ -1125,15 +1146,12 @@
}
/**
- * @param boosted If true, returns an animation layer that happens above all {@link TaskStack}s
- * Otherwise, the layer will be positioned above all animating
- * {@link TaskStack}s.
* @return The layer on which all app animations are happening.
*/
- SurfaceControl getAppAnimationLayer(boolean boosted) {
+ SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
final WindowContainer parent = getParent();
if (parent != null) {
- return parent.getAppAnimationLayer(boosted);
+ return parent.getAppAnimationLayer(animationLayer);
}
return null;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index bda6b8a..8183a74 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -172,9 +172,13 @@
when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
.thenReturn(SOUND_URI);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mock(ZenModeHelper.class),
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ mHelper = new RankingHelper(getContext(), mPm, mHandler, mockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()});
+ when(mockZenModeHelper.getNotificationPolicy()).thenReturn(new NotificationManager.Policy(
+ 0, 0, 0));
+
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
.setGroup("G")
@@ -1129,6 +1133,50 @@
}
@Test
+ public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+
+ // create notification channel that can bypass dnd
+ // expected result: areChannelsBypassingDnd = true
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
+ assertTrue(mHelper.areChannelsBypassingDnd());
+
+ // delete channels
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+ mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
+ assertFalse(mHelper.areChannelsBypassingDnd());
+
+ }
+
+ @Test
+ public void testUpdateCanChannelsBypassDnd() throws Exception {
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+
+ // update channel so it CAN bypass dnd:
+ // expected result: areChannelsBypassingDnd = true
+ channel.setBypassDnd(true);
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+ assertTrue(mHelper.areChannelsBypassingDnd());
+
+ // update channel so it can't bypass dnd:
+ // expected result: areChannelsBypassingDnd = false
+ channel.setBypassDnd(false);
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ }
+
+ @Test
public void testCreateDeletedChannel() throws Exception {
long[] vibration = new long[]{100, 67, 145, 156};
NotificationChannel channel =
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 5f01518..920a605 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -63,6 +63,10 @@
import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
import android.net.NetworkScoreManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -191,6 +195,7 @@
long mCheckIdleIntervalMillis;
long mAppIdleParoleIntervalMillis;
+ long mAppIdleParoleWindowMillis;
long mAppIdleParoleDurationMillis;
long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
@@ -227,6 +232,7 @@
// TODO: Provide a mechanism to set an external bucketing service
private AppWidgetManager mAppWidgetManager;
+ private ConnectivityManager mConnectivityManager;
private PowerManager mPowerManager;
private PackageManager mPackageManager;
Injector mInjector;
@@ -326,6 +332,7 @@
settingsObserver.updateSettings();
mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
mPowerManager = mContext.getSystemService(PowerManager.class);
mInjector.registerDisplayListener(mDisplayListener, mHandler);
@@ -414,7 +421,7 @@
postParoleEndTimeout();
} else {
mLastAppIdleParoledTime = now;
- postNextParoleTimeout(now);
+ postNextParoleTimeout(now, false);
}
postParoleStateChanged();
}
@@ -428,13 +435,18 @@
}
}
- private void postNextParoleTimeout(long now) {
+ private void postNextParoleTimeout(long now, boolean forced) {
if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
// Compute when the next parole needs to happen. We check more frequently than necessary
// since the message handler delays are based on elapsedRealTime and not wallclock time.
// The comparison is done in wallclock time.
long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
+ if (forced) {
+ // Set next timeout for the end of the parole window
+ // If parole is not set by the end of the window it will be forced
+ timeLeft += mAppIdleParoleWindowMillis;
+ }
if (timeLeft < 0) {
timeLeft = 0;
}
@@ -653,23 +665,49 @@
return THRESHOLD_BUCKETS[bucketIndex];
}
- /** Check if it's been a while since last parole and let idle apps do some work */
+ /**
+ * Check if it's been a while since last parole and let idle apps do some work.
+ * If network is not available, delay parole until it is available up until the end of the
+ * parole window. Force the parole to be set if end of the parole window is reached.
+ */
void checkParoleTimeout() {
boolean setParoled = false;
+ boolean waitForNetwork = false;
+ NetworkInfo activeNetwork = mConnectivityManager.getActiveNetworkInfo();
+ boolean networkActive = activeNetwork != null &&
+ activeNetwork.isConnected();
+
synchronized (mAppIdleLock) {
final long now = mInjector.currentTimeMillis();
if (!mAppIdleTempParoled) {
final long timeSinceLastParole = now - mLastAppIdleParoledTime;
if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
- setParoled = true;
+ if (networkActive) {
+ // If network is active set parole
+ setParoled = true;
+ } else {
+ if (timeSinceLastParole
+ > mAppIdleParoleIntervalMillis + mAppIdleParoleWindowMillis) {
+ if (DEBUG) Slog.d(TAG, "Crossed end of parole window, force parole");
+ setParoled = true;
+ } else {
+ if (DEBUG) Slog.d(TAG, "Network unavailable, delaying parole");
+ waitForNetwork = true;
+ postNextParoleTimeout(now, true);
+ }
+ }
} else {
if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
- postNextParoleTimeout(now);
+ postNextParoleTimeout(now, false);
}
}
}
+ if (waitForNetwork) {
+ mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
+ }
if (setParoled) {
+ // Set parole if network is available
setAppIdleParoled(true);
}
}
@@ -1321,6 +1359,10 @@
TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
pw.println();
+ pw.print(" mAppIdleParoleWindowMillis=");
+ TimeUtils.formatDuration(mAppIdleParoleWindowMillis, pw);
+ pw.println();
+
pw.print(" mAppIdleParoleDurationMillis=");
TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
pw.println();
@@ -1537,6 +1579,17 @@
}
}
+ private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
+
+ private final ConnectivityManager.NetworkCallback mNetworkCallback
+ = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ mConnectivityManager.unregisterNetworkCallback(this);
+ checkParoleTimeout();
+ }
+ };
+
private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
@@ -1569,6 +1622,7 @@
private static final String KEY_IDLE_DURATION = "idle_duration2";
private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
private static final String KEY_PAROLE_INTERVAL = "parole_interval";
+ private static final String KEY_PAROLE_WINDOW = "parole_window";
private static final String KEY_PAROLE_DURATION = "parole_duration";
private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
@@ -1635,6 +1689,10 @@
mAppIdleParoleIntervalMillis = mParser.getDurationMillis(KEY_PAROLE_INTERVAL,
COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
+ // Default: 2 hours to wait on network
+ mAppIdleParoleWindowMillis = mParser.getDurationMillis(KEY_PAROLE_WINDOW,
+ COMPRESS_TIME ? ONE_MINUTE * 2 : 2 * 60 * ONE_MINUTE);
+
mAppIdleParoleDurationMillis = mParser.getDurationMillis(KEY_PAROLE_DURATION,
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f9fa336..f777f1d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -62,6 +62,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -112,6 +113,7 @@
UserManager mUserManager;
PackageManager mPackageManager;
PackageManagerInternal mPackageManagerInternal;
+ PackageMonitor mPackageMonitor;
IDeviceIdleController mDeviceIdleController;
DevicePolicyManagerInternal mDpmInternal;
@@ -843,14 +845,19 @@
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
+ final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
+ PackageManager.MATCH_ANY_USER, userId);
// If the calling app is asking about itself, continue, else check for permission.
- if (mPackageManagerInternal.getPackageUid(packageName, PackageManager.MATCH_ANY_USER,
- userId) != callingUid) {
+ if (packageUid != callingUid) {
if (!hasPermission(callingPackage)) {
throw new SecurityException(
"Don't have permission to query app standby bucket");
}
}
+ if (packageUid < 0) {
+ throw new IllegalArgumentException(
+ "Cannot get standby bucket for non existent package (" + packageName + ")");
+ }
final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid,
userId);
final long token = Binder.clearCallingIdentity();
@@ -886,11 +893,17 @@
: UsageStatsManager.REASON_MAIN_PREDICTED;
final long token = Binder.clearCallingIdentity();
try {
+ final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
+ PackageManager.MATCH_ANY_USER, userId);
// Caller cannot set their own standby state
- if (mPackageManagerInternal.getPackageUid(packageName,
- PackageManager.MATCH_ANY_USER, userId) == callingUid) {
+ if (packageUid == callingUid) {
throw new IllegalArgumentException("Cannot set your own standby bucket");
}
+ if (packageUid < 0) {
+ throw new IllegalArgumentException(
+ "Cannot set standby bucket for non existent package (" + packageName
+ + ")");
+ }
mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
SystemClock.elapsedRealtime());
} finally {