Merge "Remove the notification when FGS while-in-use permission is restricted." into rvc-dev
diff --git a/apex/extservices/Android.bp b/apex/extservices/Android.bp
index c89f694..021246c 100644
--- a/apex/extservices/Android.bp
+++ b/apex/extservices/Android.bp
@@ -20,6 +20,7 @@
apex_defaults {
name: "com.android.extservices-defaults",
+ updatable: true,
key: "com.android.extservices.key",
certificate: ":com.android.extservices.certificate",
apps: ["ExtServices"],
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index b86aba6..8c08e75 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -3034,7 +3034,7 @@
}
void resetExecutionQuota(@NonNull String pkgName, int userId) {
- mQuotaController.clearAppStats(pkgName, userId);
+ mQuotaController.clearAppStats(userId, pkgName);
}
void resetScheduleQuota() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 4393a95..7256371 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -54,12 +54,14 @@
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
@@ -74,6 +76,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.PriorityQueue;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -301,10 +304,10 @@
private final SparseArrayMap<List<TimingSession>> mTimingSessions = new SparseArrayMap<>();
/**
- * List of alarm listeners for each package that listen for when each package comes back within
- * quota.
+ * Listener to track and manage when each package comes back within quota.
*/
- private final SparseArrayMap<QcAlarmListener> mInQuotaAlarmListeners = new SparseArrayMap<>();
+ @GuardedBy("mLock")
+ private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();
/** Cached calculation results for each app, with the standby buckets as the array indices. */
private final SparseArrayMap<ExecutionStats[]> mExecutionStatsCache = new SparseArrayMap<>();
@@ -579,7 +582,7 @@
Slog.wtf(TAG, "Told app removed but given null package name.");
return;
}
- clearAppStats(packageName, UserHandle.getUserId(uid));
+ clearAppStats(UserHandle.getUserId(uid), packageName);
mForegroundUids.delete(uid);
mUidToPackageCache.remove(uid);
}
@@ -589,13 +592,13 @@
mTrackedJobs.delete(userId);
mPkgTimers.delete(userId);
mTimingSessions.delete(userId);
- mInQuotaAlarmListeners.delete(userId);
+ mInQuotaAlarmListener.removeAlarmsLocked(userId);
mExecutionStatsCache.delete(userId);
mUidToPackageCache.clear();
}
/** Drop all historical stats and stop tracking any active sessions for the specified app. */
- public void clearAppStats(@NonNull String packageName, int userId) {
+ public void clearAppStats(int userId, @NonNull String packageName) {
mTrackedJobs.delete(userId, packageName);
Timer timer = mPkgTimers.get(userId, packageName);
if (timer != null) {
@@ -606,11 +609,7 @@
mPkgTimers.delete(userId, packageName);
}
mTimingSessions.delete(userId, packageName);
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null) {
- mAlarmManager.cancel(alarmListener);
- mInQuotaAlarmListeners.delete(userId, packageName);
- }
+ mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
mExecutionStatsCache.delete(userId, packageName);
}
@@ -1208,12 +1207,7 @@
// exempted.
maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
} else {
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null && alarmListener.isWaiting()) {
- mAlarmManager.cancel(alarmListener);
- // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
- alarmListener.setTriggerTime(0);
- }
+ mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
}
return changed;
}
@@ -1229,12 +1223,7 @@
final String packageName = jobStatus.getSourcePackageName();
final int realStandbyBucket = jobStatus.getStandbyBucket();
if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
- if (alarmListener != null && alarmListener.isWaiting()) {
- mAlarmManager.cancel(alarmListener);
- // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
- alarmListener.setTriggerTime(0);
- }
+ mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
} else {
mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
}
@@ -1285,7 +1274,6 @@
final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
standbyBucket);
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
&& stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
&& isUnderJobCountQuota
@@ -1297,21 +1285,11 @@
+ getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
+ "ms in its quota.");
}
- if (alarmListener != null) {
- // Cancel any pending alarm.
- mAlarmManager.cancel(alarmListener);
- // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
- alarmListener.setTriggerTime(0);
- }
+ mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
return;
}
- if (alarmListener == null) {
- alarmListener = new QcAlarmListener(userId, packageName);
- mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
- }
-
// The time this app will have quota again.
long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
@@ -1325,27 +1303,7 @@
inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
stats.sessionRateLimitExpirationTimeElapsed);
}
- // Only schedule the alarm if:
- // 1. There isn't one currently scheduled
- // 2. The new alarm is significantly earlier than the previous alarm (which could be the
- // case if the package moves into a higher standby bucket). If it's earlier but not
- // significantly so, then we essentially delay the job a few extra minutes.
- // 3. The alarm is after the current alarm by more than the quota buffer.
- // TODO: this might be overengineering. Simplify if proven safe.
- if (!alarmListener.isWaiting()
- || inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
- || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
- if (DEBUG) {
- Slog.d(TAG, "Scheduling start alarm for " + pkgString);
- }
- // If the next time this app will have quota is at least 3 minutes before the
- // alarm is supposed to go off, reschedule the alarm.
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
- ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
- alarmListener.setTriggerTime(inQuotaTimeElapsed);
- } else if (DEBUG) {
- Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
- }
+ mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed);
}
private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) {
@@ -1875,32 +1833,161 @@
}
}
- private class QcAlarmListener implements AlarmManager.OnAlarmListener {
- private final int mUserId;
- private final String mPackageName;
- private volatile long mTriggerTimeElapsed;
-
- QcAlarmListener(int userId, String packageName) {
- mUserId = userId;
- mPackageName = packageName;
+ static class AlarmQueue extends PriorityQueue<Pair<Package, Long>> {
+ AlarmQueue() {
+ super(1, (o1, o2) -> (int) (o1.second - o2.second));
}
- boolean isWaiting() {
- return mTriggerTimeElapsed > 0;
+ /**
+ * Remove any instances of the Package from the queue.
+ *
+ * @return true if an instance was removed, false otherwise.
+ */
+ boolean remove(@NonNull Package pkg) {
+ boolean removed = false;
+ Pair[] alarms = toArray(new Pair[size()]);
+ for (int i = alarms.length - 1; i >= 0; --i) {
+ if (pkg.equals(alarms[i].first)) {
+ remove(alarms[i]);
+ removed = true;
+ }
+ }
+ return removed;
+ }
+ }
+
+ /** Track when UPTCs are expected to come back into quota. */
+ private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
+ @GuardedBy("mLock")
+ private final AlarmQueue mAlarmQueue = new AlarmQueue();
+ /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
+ @GuardedBy("mLock")
+ private long mTriggerTimeElapsed = 0;
+
+ @GuardedBy("mLock")
+ void addAlarmLocked(int userId, @NonNull String pkgName, long inQuotaTimeElapsed) {
+ final Package pkg = new Package(userId, pkgName);
+ mAlarmQueue.remove(pkg);
+ mAlarmQueue.offer(new Pair<>(pkg, inQuotaTimeElapsed));
+ setNextAlarmLocked();
}
- void setTriggerTime(long timeElapsed) {
- mTriggerTimeElapsed = timeElapsed;
+ @GuardedBy("mLock")
+ void removeAlarmLocked(@NonNull Package pkg) {
+ if (mAlarmQueue.remove(pkg)) {
+ setNextAlarmLocked();
+ }
}
- long getTriggerTimeElapsed() {
- return mTriggerTimeElapsed;
+ @GuardedBy("mLock")
+ void removeAlarmLocked(int userId, @NonNull String packageName) {
+ removeAlarmLocked(new Package(userId, packageName));
+ }
+
+ @GuardedBy("mLock")
+ void removeAlarmsLocked(int userId) {
+ boolean removed = false;
+ Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
+ for (int i = alarms.length - 1; i >= 0; --i) {
+ final Package pkg = (Package) alarms[i].first;
+ if (userId == pkg.userId) {
+ mAlarmQueue.remove(alarms[i]);
+ removed = true;
+ }
+ }
+ if (removed) {
+ setNextAlarmLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void setNextAlarmLocked() {
+ if (mAlarmQueue.size() > 0) {
+ final long nextTriggerTimeElapsed = mAlarmQueue.peek().second;
+ // Only schedule the alarm if one of the following is true:
+ // 1. There isn't one currently scheduled
+ // 2. The new alarm is significantly earlier than the previous alarm. If it's
+ // earlier but not significantly so, then we essentially delay the job a few extra
+ // minutes.
+ // 3. The alarm is after the current alarm.
+ if (mTriggerTimeElapsed == 0
+ || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS
+ || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed);
+ }
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
+ ALARM_TAG_QUOTA_CHECK, this, mHandler);
+ mTriggerTimeElapsed = nextTriggerTimeElapsed;
+ }
+ } else {
+ mAlarmManager.cancel(this);
+ mTriggerTimeElapsed = 0;
+ }
}
@Override
public void onAlarm() {
- mHandler.obtainMessage(MSG_CHECK_PACKAGE, mUserId, 0, mPackageName).sendToTarget();
- mTriggerTimeElapsed = 0;
+ synchronized (mLock) {
+ while (mAlarmQueue.size() > 0) {
+ final Pair<Package, Long> alarm = mAlarmQueue.peek();
+ if (alarm.second <= sElapsedRealtimeClock.millis()) {
+ mHandler.obtainMessage(MSG_CHECK_PACKAGE, alarm.first.userId, 0,
+ alarm.first.packageName).sendToTarget();
+ mAlarmQueue.remove(alarm);
+ } else {
+ break;
+ }
+ }
+ setNextAlarmLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ void dumpLocked(IndentingPrintWriter pw) {
+ pw.println("In quota alarms:");
+ pw.increaseIndent();
+
+ if (mAlarmQueue.size() == 0) {
+ pw.println("NOT WAITING");
+ } else {
+ Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
+ for (int i = 0; i < alarms.length; ++i) {
+ final Package pkg = (Package) alarms[i].first;
+ pw.print(pkg);
+ pw.print(": ");
+ pw.print(alarms[i].second);
+ pw.println();
+ }
+ }
+
+ pw.decreaseIndent();
+ }
+
+ @GuardedBy("mLock")
+ void dumpLocked(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(
+ StateControllerProto.QuotaController.InQuotaAlarmListener.TRIGGER_TIME_ELAPSED,
+ mTriggerTimeElapsed);
+
+ Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
+ for (int i = 0; i < alarms.length; ++i) {
+ final long aToken = proto.start(
+ StateControllerProto.QuotaController.InQuotaAlarmListener.ALARMS);
+
+ final Package pkg = (Package) alarms[i].first;
+ pkg.dumpDebug(proto,
+ StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.PKG);
+ proto.write(
+ StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED,
+ (Long) alarms[i].second);
+
+ proto.end(aToken);
+ }
+
+ proto.end(token);
}
}
@@ -2618,23 +2705,7 @@
pw.decreaseIndent();
pw.println();
- pw.println("In quota alarms:");
- pw.increaseIndent();
- for (int u = 0; u < mInQuotaAlarmListeners.numMaps(); ++u) {
- final int userId = mInQuotaAlarmListeners.keyAt(u);
- for (int p = 0; p < mInQuotaAlarmListeners.numElementsForKey(userId); ++p) {
- final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);
-
- pw.print(string(userId, pkgName));
- pw.print(": ");
- if (alarmListener.isWaiting()) {
- pw.println(alarmListener.getTriggerTimeElapsed());
- } else {
- pw.println("NOT WAITING");
- }
- }
- }
+ mInQuotaAlarmListener.dumpLocked(pw);
pw.decreaseIndent();
}
@@ -2768,22 +2839,13 @@
}
}
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, pkgName);
- if (alarmListener != null) {
- final long alToken = proto.start(
- StateControllerProto.QuotaController.PackageStats.IN_QUOTA_ALARM_LISTENER);
- proto.write(StateControllerProto.QuotaController.AlarmListener.IS_WAITING,
- alarmListener.isWaiting());
- proto.write(
- StateControllerProto.QuotaController.AlarmListener.TRIGGER_TIME_ELAPSED,
- alarmListener.getTriggerTimeElapsed());
- proto.end(alToken);
- }
-
proto.end(psToken);
}
}
+ mInQuotaAlarmListener.dumpLocked(proto,
+ StateControllerProto.QuotaController.IN_QUOTA_ALARM_LISTENER);
+
proto.end(mToken);
proto.end(token);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 932c25d..46d449a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -21,6 +21,7 @@
import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
@@ -43,6 +44,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import libcore.io.IoUtils;
@@ -244,9 +246,9 @@
* @param elapsedRealtime mark as used time if non-zero
* @param timeout set the timeout of the specified bucket, if non-zero. Can only be used
* with bucket values of ACTIVE and WORKING_SET.
- * @return
+ * @return {@code appUsageHistory}
*/
- public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName,
+ AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId,
int newBucket, int usageReason, long elapsedRealtime, long timeout) {
int bucketingReason = REASON_MAIN_USAGE | usageReason;
final boolean isUserUsage = isUserUsage(bucketingReason);
@@ -284,11 +286,7 @@
if (appUsageHistory.currentBucket > newBucket) {
appUsageHistory.currentBucket = newBucket;
- if (DEBUG) {
- Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory
- .currentBucket
- + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
- }
+ logAppStandbyBucketChanged(packageName, userId, newBucket, bucketingReason);
}
appUsageHistory.bucketingReason = bucketingReason;
@@ -313,7 +311,8 @@
int usageReason, long nowElapsed, long timeout) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true);
- return reportUsage(history, packageName, newBucket, usageReason, nowElapsed, timeout);
+ return reportUsage(history, packageName, userId, newBucket, usageReason, nowElapsed,
+ timeout);
}
private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
@@ -372,6 +371,7 @@
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory =
getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+ final boolean changed = appUsageHistory.currentBucket != bucket;
appUsageHistory.currentBucket = bucket;
appUsageHistory.bucketingReason = reason;
@@ -385,9 +385,8 @@
appUsageHistory.bucketActiveTimeoutTime = elapsed;
appUsageHistory.bucketWorkingSetTimeoutTime = elapsed;
}
- if (DEBUG) {
- Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
- + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
+ if (changed) {
+ logAppStandbyBucketChanged(packageName, userId, bucket, reason);
}
}
@@ -485,18 +484,19 @@
/* Returns the new standby bucket the app is assigned to */
public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
- ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
- AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
- elapsedRealtime, true);
+ final int newBucket;
+ final int reason;
if (idle) {
- appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
- appUsageHistory.bucketingReason = REASON_MAIN_FORCED_BY_USER;
+ newBucket = STANDBY_BUCKET_RARE;
+ reason = REASON_MAIN_FORCED_BY_USER;
} else {
- appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+ newBucket = STANDBY_BUCKET_ACTIVE;
// This is to pretend that the app was just used, don't freeze the state anymore.
- appUsageHistory.bucketingReason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION;
+ reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION;
}
- return appUsageHistory.currentBucket;
+ setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason, false);
+
+ return newBucket;
}
public void clearUsage(String packageName, int userId) {
@@ -551,13 +551,27 @@
return 0;
}
+ /**
+ * Log a standby bucket change to statsd, and also logcat if debug logging is enabled.
+ */
+ private void logAppStandbyBucketChanged(String packageName, int userId, int bucket,
+ int reason) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.APP_STANDBY_BUCKET_CHANGED,
+ packageName, userId, bucket,
+ (reason & REASON_MAIN_MASK), (reason & REASON_SUB_MASK));
+ if (DEBUG) {
+ Slog.d(TAG, "Moved " + packageName + " to bucket=" + bucket
+ + ", reason=0x0" + Integer.toHexString(reason));
+ }
+ }
+
@VisibleForTesting
File getUserFile(int userId) {
return new File(new File(new File(mStorageDir, "users"),
Integer.toString(userId)), APP_IDLE_FILENAME);
}
-
/**
* Check if App Idle File exists on disk
* @param userId
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 7a1b4f2..5992253 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -831,24 +831,24 @@
if (eventType == UsageEvents.Event.NOTIFICATION_SEEN
|| eventType == UsageEvents.Event.SLICE_PINNED) {
// Mild usage elevates to WORKING_SET but doesn't change usage time.
- mAppIdleHistory.reportUsage(appHistory, pkg,
+ mAppIdleHistory.reportUsage(appHistory, pkg, userId,
STANDBY_BUCKET_WORKING_SET, subReason,
0, elapsedRealtime + mNotificationSeenTimeoutMillis);
nextCheckDelay = mNotificationSeenTimeoutMillis;
} else if (eventType == UsageEvents.Event.SYSTEM_INTERACTION) {
- mAppIdleHistory.reportUsage(appHistory, pkg,
+ mAppIdleHistory.reportUsage(appHistory, pkg, userId,
STANDBY_BUCKET_ACTIVE, subReason,
0, elapsedRealtime + mSystemInteractionTimeoutMillis);
nextCheckDelay = mSystemInteractionTimeoutMillis;
} else if (eventType == UsageEvents.Event.FOREGROUND_SERVICE_START) {
// Only elevate bucket if this is the first usage of the app
if (prevBucket != STANDBY_BUCKET_NEVER) return;
- mAppIdleHistory.reportUsage(appHistory, pkg,
+ mAppIdleHistory.reportUsage(appHistory, pkg, userId,
STANDBY_BUCKET_ACTIVE, subReason,
0, elapsedRealtime + mInitialForegroundServiceStartTimeoutMillis);
nextCheckDelay = mInitialForegroundServiceStartTimeoutMillis;
} else {
- mAppIdleHistory.reportUsage(appHistory, pkg,
+ mAppIdleHistory.reportUsage(appHistory, pkg, userId,
STANDBY_BUCKET_ACTIVE, subReason,
elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis);
nextCheckDelay = mStrongUsageTimeoutMillis;
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index cf70878..ba7572a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -1,6 +1,13 @@
{
"presubmit": [
{
+ "name": "CtsUsageStatsTestCases",
+ "options": [
+ {"include-filter": "android.app.usage.cts.UsageStatsTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.usage"},
@@ -10,6 +17,9 @@
],
"postsubmit": [
{
+ "name": "CtsUsageStatsTestCases"
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{"include-filter": "com.android.server.usage"}
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
index d746ea6..0171b0d 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -20,6 +20,7 @@
apex_defaults {
name: "com.android.permission-defaults",
+ updatable: true,
key: "com.android.permission.key",
certificate: ":com.android.permission.certificate",
java_libs: [
diff --git a/apex/sdkextensions/Android.bp b/apex/sdkextensions/Android.bp
index 25765af..322d5e1 100644
--- a/apex/sdkextensions/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -26,6 +26,7 @@
apex_defaults {
name: "com.android.sdkext-defaults",
+ updatable: true,
java_libs: [ "framework-sdkextensions" ],
prebuilts: [
"derive_sdk.rc",
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 4c96263..2df3eea 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -32,6 +32,7 @@
compile_multilib: "both",
prebuilts: ["com.android.os.statsd.init.rc"],
name: "com.android.os.statsd-defaults",
+ updatable: true,
key: "com.android.os.statsd.key",
certificate: ":com.android.os.statsd.certificate",
}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 0f52f15..1bd770a 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -69,6 +69,8 @@
"android.util",
],
+ plugins: ["java_api_finder"],
+
hostdex: true, // for hiddenapi check
visibility: [
"//frameworks/base/apex/statsd:__subpackages__",
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index 0f325e3..ff56bb5 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -31,6 +31,8 @@
"android_module_lib_stubs_current",
],
+ plugins: ["java_api_finder"],
+
apex_available: [
"com.android.os.statsd",
"test_com.android.os.statsd",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 5535b66..b86dc35 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -396,6 +396,7 @@
ForegroundServiceAppOpSessionEnded foreground_service_app_op_session_ended =
256 [(module) = "framework"];
DisplayJankReported display_jank_reported = 257;
+ AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"];
SdkExtensionStatus sdk_extension_status = 354;
}
@@ -1227,18 +1228,8 @@
// Name of source package (for historical reasons, since BatteryStats tracked it).
optional string package_name = 3;
- // These enum values match the STANDBY_BUCKET_XXX constants defined in UsageStatsManager.java.
- enum Bucket {
- UNKNOWN = 0;
- EXEMPTED = 5;
- ACTIVE = 10;
- WORKING_SET = 20;
- FREQUENT = 30;
- RARE = 40;
- NEVER = 50;
- }
// The App Standby bucket of the app that scheduled the alarm at the time the alarm fired.
- optional Bucket app_standby_bucket = 4;
+ optional AppStandbyBucketChanged.Bucket app_standby_bucket = 4;
}
/**
@@ -8724,3 +8715,45 @@
// Total number of L5 sv status messages reports, where sv is used in fix since boot
optional int64 l5_sv_status_reports_used_in_fix = 14;
}
+
+/**
+ * Logs when an app is moved to a different standby bucket.
+ *
+ * Logged from:
+ * frameworks/base/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+ */
+message AppStandbyBucketChanged {
+ optional string package_name = 1;
+
+ // Should be 0, 10, 11, 12, etc. where 0 is the owner. See UserHandle for more documentation.
+ optional int32 user_id = 2;
+
+ // These enum values match the constants defined in UsageStatsManager.java.
+ enum Bucket {
+ BUCKET_UNKNOWN = 0;
+ BUCKET_EXEMPTED = 5;
+ BUCKET_ACTIVE = 10;
+ BUCKET_WORKING_SET = 20;
+ BUCKET_FREQUENT = 30;
+ BUCKET_RARE = 40;
+ BUCKET_RESTRICTED = 45;
+ BUCKET_NEVER = 50;
+ }
+ optional Bucket bucket = 3;
+
+ enum MainReason {
+ MAIN_UNKNOWN = 0;
+ MAIN_DEFAULT = 0x0100;
+ MAIN_TIMEOUT = 0x0200;
+ MAIN_USAGE = 0x0300;
+ MAIN_FORCED_BY_USER = 0x0400;
+ MAIN_PREDICTED = 0x0500;
+ MAIN_FORCED_BY_SYSTEM = 0x0600;
+ }
+ optional MainReason main_reason = 4;
+
+ // A more detailed reason for the standby bucket change. The sub reason name is dependent on
+ // the main reason. Values are one of the REASON_SUB_XXX constants defined in
+ // UsageStatsManager.java.
+ optional int32 sub_reason = 5;
+}
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 0edf40b..933f48d 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -66,10 +66,8 @@
{
lock_guard<mutex> lk(*cv_mutex);
for (const StatsEventParcel& parcel: output) {
- uint8_t* buf = reinterpret_cast<uint8_t*>(
- const_cast<int8_t*>(parcel.buffer.data()));
- shared_ptr<LogEvent> event = make_shared<LogEvent>(
- buf, parcel.buffer.size(), /*uid=*/-1, /*pid=*/-1);
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1);
+ event->parseBuffer((uint8_t*)parcel.buffer.data(), parcel.buffer.size());
sharedData->push_back(event);
}
*pullSuccess = success;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 3e46d13..974e203 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -65,18 +65,6 @@
#define ATTRIBUTION_CHAIN_TYPE 0x09
#define ERROR_TYPE 0x0F
-// Msg is expected to begin at the start of the serialized atom -- it should not
-// include the android_log_header_t or the StatsEventTag.
-LogEvent::LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid)
- : mBuf(msg),
- mRemainingLen(len),
- mLogdTimestampNs(time(nullptr)),
- mLogUid(uid),
- mLogPid(pid)
-{
- initNew();
-}
-
LogEvent::LogEvent(const LogEvent& event) {
mTagId = event.mTagId;
mLogUid = event.mLogUid;
@@ -86,6 +74,12 @@
mValues = event.mValues;
}
+LogEvent::LogEvent(int32_t uid, int32_t pid)
+ : mLogdTimestampNs(time(nullptr)),
+ mLogUid(uid),
+ mLogPid(pid) {
+}
+
LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs) {
mLogdTimestampNs = wallClockTimestampNs;
mElapsedTimestampNs = elapsedTimestampNs;
@@ -189,9 +183,6 @@
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
}
-LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, timestampNs) {
-}
-
LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) {
mLogdTimestampNs = timestampNs;
mTagId = tagId;
@@ -467,7 +458,10 @@
// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
// stats_event.c
-void LogEvent::initNew() {
+bool LogEvent::parseBuffer(uint8_t* buf, size_t len) {
+ mBuf = buf;
+ mRemainingLen = (uint32_t)len;
+
int32_t pos[] = {1, 1, 1};
bool last[] = {false, false, false};
@@ -529,6 +523,7 @@
if (mRemainingLen != 0) mValid = false;
mBuf = nullptr;
+ return mValid;
}
uint8_t LogEvent::getTypeId(uint8_t typeInfo) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index e167e67..3940aa8 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -20,7 +20,6 @@
#include <android/util/ProtoOutputStream.h>
#include <private/android_logger.h>
-#include <stats_event.h>
#include <string>
#include <vector>
@@ -61,14 +60,32 @@
};
/**
- * Wrapper for the log_msg structure.
+ * This class decodes the structured, serialized encoding of an atom into a
+ * vector of FieldValues.
*/
class LogEvent {
public:
/**
- * Read a LogEvent from the socket
+ * \param uid user id of the logging caller
+ * \param pid process id of the logging caller
*/
- explicit LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid);
+ explicit LogEvent(int32_t uid, int32_t pid);
+
+ /**
+ * Parses the atomId, timestamp, and vector of values from a buffer
+ * containing the StatsEvent/AStatsEvent encoding of an atom.
+ *
+ * \param buf a buffer that begins at the start of the serialized atom (it
+ * should not include the android_log_header_t or the StatsEventTag)
+ * \param len size of the buffer
+ *
+ * \return success of the initialization
+ */
+ bool parseBuffer(uint8_t* buf, size_t len);
+
+ // TODO(b/149590301): delete unused functions below once LogEvent uses the
+ // new socket schema within test code. Really we would like the only entry
+ // points into LogEvent to be the above constructor and parseBuffer functions.
/**
* Constructs a LogEvent with synthetic data for testing. Must call init() before reading.
@@ -76,9 +93,6 @@
explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs);
// For testing. The timestamp is used as both elapsed real time and logd timestamp.
- explicit LogEvent(int32_t tagId, int64_t timestampNs);
-
- // For testing. The timestamp is used as both elapsed real time and logd timestamp.
explicit LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid);
/**
@@ -192,10 +206,6 @@
return &mValues;
}
- bool isValid() {
- return mValid;
- }
-
inline LogEvent makeCopy() {
return LogEvent(*this);
}
@@ -222,12 +232,6 @@
*/
LogEvent(const LogEvent&);
-
- /**
- * Parsing function for new encoding scheme.
- */
- void initNew();
-
void parseInt32(int32_t* pos, int32_t depth, bool* last);
void parseInt64(int32_t* pos, int32_t depth, bool* last);
void parseString(int32_t* pos, int32_t depth, bool* last);
@@ -238,13 +242,14 @@
void parseAttributionChain(int32_t* pos, int32_t depth, bool* last);
/**
- * mBuf is a pointer to the current location in the buffer being parsed.
- * Because the buffer lives on the StatsSocketListener stack, this pointer
- * is only valid during the LogEvent constructor. It will be set to null at
- * the end of initNew.
+ * The below three variables are only valid during the execution of
+ * parseBuffer. There are no guarantees about the state of these variables
+ * before/after.
+ *
+ * TODO (b/150312423): These shouldn't be member variables. We should pass
+ * them around as parameters.
*/
uint8_t* mBuf;
-
uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed
bool mValid = true; // stores whether the event we received from the socket is valid
diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp
index 8f0f480..b877cc9 100755
--- a/cmds/statsd/src/socket/StatsSocketListener.cpp
+++ b/cmds/statsd/src/socket/StatsSocketListener.cpp
@@ -126,7 +126,10 @@
uint32_t pid = cred->pid;
int64_t oldestTimestamp;
- if (!mQueue->push(std::make_unique<LogEvent>(msg, len, uid, pid), &oldestTimestamp)) {
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
+ logEvent->parseBuffer(msg, len);
+
+ if (!mQueue->push(std::move(logEvent), &oldestTimestamp)) {
StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp);
}
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 7542faf..7458cbf 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -54,8 +54,9 @@
size_t size;
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
- LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
- EXPECT_TRUE(logEvent.isValid());
+ LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+ EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
EXPECT_EQ(1001, logEvent.GetPid());
@@ -102,8 +103,9 @@
size_t size;
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
- LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
- EXPECT_TRUE(logEvent.isValid());
+ LogEvent logEvent(/*uid=*/ 1000, /*pid=*/ 1001);
+ EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
EXPECT_EQ(1001, logEvent.GetPid());
@@ -137,8 +139,9 @@
size_t size;
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
- LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
- EXPECT_TRUE(logEvent.isValid());
+ LogEvent logEvent(/*uid=*/ 1000, /*pid=*/ 1001);
+ EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
EXPECT_EQ(1001, logEvent.GetPid());
@@ -165,8 +168,9 @@
size_t size;
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
- LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
- EXPECT_TRUE(logEvent.isValid());
+ LogEvent logEvent(/*uid=*/ 1000, /*pid=*/ 1001);
+ EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
EXPECT_EQ(1001, logEvent.GetPid());
@@ -200,8 +204,9 @@
size_t size;
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
- LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
- EXPECT_TRUE(logEvent.isValid());
+ LogEvent logEvent(/*uid=*/ 1000, /*pid=*/ 1001);
+ EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
EXPECT_EQ(1001, logEvent.GetPid());
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 36094b2..8701e17 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -13,6 +13,7 @@
// limitations under the License.
#include "src/condition/SimpleConditionTracker.h"
+#include "stats_event.h"
#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
@@ -31,6 +32,8 @@
namespace os {
namespace statsd {
+namespace {
+
const ConfigKey kConfigKey(0, 12345);
const int ATTRIBUTION_NODE_FIELD_ID = 1;
@@ -57,24 +60,33 @@
return simplePredicate;
}
-void writeAttributionNodesToEvent(LogEvent* event, const std::vector<int> &uids) {
- std::vector<AttributionNodeInternal> nodes;
- for (size_t i = 0; i < uids.size(); ++i) {
- AttributionNodeInternal node;
- node.set_uid(uids[i]);
- nodes.push_back(node);
+void makeWakeLockEvent(LogEvent* logEvent, uint32_t atomId, uint64_t timestamp,
+ const vector<int>& uids, const string& wl, int acquire) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomId);
+ AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+
+ vector<std::string> tags(uids.size()); // vector of empty strings
+ vector<const char*> cTags(uids.size());
+ for (int i = 0; i < cTags.size(); i++) {
+ cTags[i] = tags[i].c_str();
}
- event->write(nodes); // attribution chain.
+ AStatsEvent_writeAttributionChain(statsEvent, reinterpret_cast<const uint32_t*>(uids.data()),
+ cTags.data(), uids.size());
+
+ AStatsEvent_writeString(statsEvent, wl.c_str());
+ AStatsEvent_writeInt32(statsEvent, acquire);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ logEvent->parseBuffer(buf, size);
+
+ AStatsEvent_release(statsEvent);
}
-// TODO(b/149590301): Update this helper to use new socket schema.
-//void makeWakeLockEvent(
-// LogEvent* event, const std::vector<int> &uids, const string& wl, int acquire) {
-// writeAttributionNodesToEvent(event, uids);
-// event->write(wl);
-// event->write(acquire);
-// event->init();
-//}
+} // anonymous namespace
+
std::map<int64_t, HashableDimensionKey> getWakeLockQueryKey(
const Position position,
@@ -265,138 +277,128 @@
EXPECT_TRUE(changedCache[0]);
}
-// TODO(b/149590301): Update these tests to use new socket schema.
-//TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
-// std::vector<sp<ConditionTracker>> allConditions;
-// for (Position position :
-// { Position::FIRST, Position::LAST}) {
-//
-// SimplePredicate simplePredicate = getWakeLockHeldCondition(
-// true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
-// position);
-// string conditionName = "WL_HELD_BY_UID2";
-//
-// unordered_map<int64_t, int> trackerNameIndexMap;
-// trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
-// trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
-// trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
-//
-// SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
-// 0 /*condition tracker index*/, simplePredicate,
-// trackerNameIndexMap);
-//
-// std::vector<int> uids = {111, 222, 333};
-//
-// LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
-// makeWakeLockEvent(&event, uids, "wl1", 1);
-//
-// // one matched start
-// vector<MatchingState> matcherState;
-// matcherState.push_back(MatchingState::kMatched);
-// matcherState.push_back(MatchingState::kNotMatched);
-// matcherState.push_back(MatchingState::kNotMatched);
-// vector<sp<ConditionTracker>> allPredicates;
-// vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-// vector<bool> changedCache(1, false);
-//
-// conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
-// changedCache);
-//
-// if (position == Position::FIRST ||
-// position == Position::LAST) {
-// EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
-// } else {
-// EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
-// }
-// EXPECT_TRUE(changedCache[0]);
-// if (position == Position::FIRST ||
-// position == Position::LAST) {
-// EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u);
-// EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
-// } else {
-// EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), uids.size());
-// }
-//
-// // Now test query
-// const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
-// conditionCache[0] = ConditionState::kNotEvaluated;
-//
-// conditionTracker.isConditionMet(queryKey, allPredicates,
-// false,
-// conditionCache);
-// EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
-//
-// // another wake lock acquired by this uid
-// LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
-// makeWakeLockEvent(&event2, uids, "wl2", 1);
-// matcherState.clear();
-// matcherState.push_back(MatchingState::kMatched);
-// matcherState.push_back(MatchingState::kNotMatched);
-// conditionCache[0] = ConditionState::kNotEvaluated;
-// changedCache[0] = false;
-// conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
-// changedCache);
-// EXPECT_FALSE(changedCache[0]);
-// if (position == Position::FIRST ||
-// position == Position::LAST) {
-// EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
-// } else {
-// EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
-// }
-// EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
-// EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
-//
-//
-// // wake lock 1 release
-// LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
-// makeWakeLockEvent(&event3, uids, "wl1", 0); // now release it.
-// matcherState.clear();
-// matcherState.push_back(MatchingState::kNotMatched);
-// matcherState.push_back(MatchingState::kMatched);
-// conditionCache[0] = ConditionState::kNotEvaluated;
-// changedCache[0] = false;
-// conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
-// changedCache);
-// // nothing changes, because wake lock 2 is still held for this uid
-// EXPECT_FALSE(changedCache[0]);
-// if (position == Position::FIRST ||
-// position == Position::LAST) {
-// EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
-// } else {
-// EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
-// }
-// EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
-// EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
-//
-// LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
-// makeWakeLockEvent(&event4, uids, "wl2", 0); // now release it.
-// matcherState.clear();
-// matcherState.push_back(MatchingState::kNotMatched);
-// matcherState.push_back(MatchingState::kMatched);
-// conditionCache[0] = ConditionState::kNotEvaluated;
-// changedCache[0] = false;
-// conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
-// changedCache);
-// EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
-// EXPECT_TRUE(changedCache[0]);
-// if (position == Position::FIRST ||
-// position == Position::LAST) {
-// EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u);
-// EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
-// } else {
-// EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), uids.size());
-// }
-//
-// // query again
-// conditionCache[0] = ConditionState::kNotEvaluated;
-// conditionTracker.isConditionMet(queryKey, allPredicates,
-// false,
-// conditionCache);
-// EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
-// }
-//
-//}
-//
+TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
+ std::vector<sp<ConditionTracker>> allConditions;
+ for (Position position : {Position::FIRST, Position::LAST}) {
+ SimplePredicate simplePredicate = getWakeLockHeldCondition(
+ true /*nesting*/, true /*default to false*/, true /*output slice by uid*/,
+ position);
+ string conditionName = "WL_HELD_BY_UID2";
+
+ unordered_map<int64_t, int> trackerNameIndexMap;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_ACQUIRE")] = 0;
+ trackerNameIndexMap[StringToId("WAKE_LOCK_RELEASE")] = 1;
+ trackerNameIndexMap[StringToId("RELEASE_ALL")] = 2;
+
+ SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
+ 0 /*condition tracker index*/, simplePredicate,
+ trackerNameIndexMap);
+
+ std::vector<int> uids = {111, 222, 333};
+
+ LogEvent event(/*uid=*/-1, /*pid=*/-1);
+ makeWakeLockEvent(&event, /*atomId=*/ 1, /*timestamp=*/ 0, uids, "wl1", /*acquire=*/ 1);
+
+ // one matched start
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ vector<sp<ConditionTracker>> allPredicates;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
+
+ conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
+ changedCache);
+
+ if (position == Position::FIRST || position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+ }
+ EXPECT_TRUE(changedCache[0]);
+ if (position == Position::FIRST || position == Position::LAST) {
+ EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u);
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+ } else {
+ EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(),
+ uids.size());
+ }
+
+ // Now test query
+ const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+
+ conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+ // another wake lock acquired by this uid
+ LogEvent event2(/*uid=*/-1, /*pid=*/-1);
+ makeWakeLockEvent(&event2, /*atomId=*/ 1, /*timestamp=*/ 0, uids, "wl2", /*acquire=*/ 1);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache,
+ changedCache);
+ EXPECT_FALSE(changedCache[0]);
+ if (position == Position::FIRST || position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+ }
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+
+
+ // wake lock 1 release
+ LogEvent event3(/*uid=*/-1, /*pid=*/-1);
+ makeWakeLockEvent(&event3, /*atomId=*/1, /*timestamp=*/0, uids, "wl1", /*acquire=*/0);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
+ changedCache);
+ // nothing changes, because wake lock 2 is still held for this uid
+ EXPECT_FALSE(changedCache[0]);
+ if (position == Position::FIRST || position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ } else {
+ EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
+ }
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+
+ LogEvent event4(/*uid=*/-1, /*pid=*/-1);
+ makeWakeLockEvent(&event, /*atomId=*/1, /*timestamp=*/ 0, uids, "wl2", /*acquire=*/0);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event4, matcherState, allPredicates, conditionCache,
+ changedCache);
+ EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
+ if (position == Position::FIRST || position == Position::LAST) {
+ EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u);
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ } else {
+ EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(),
+ uids.size());
+ }
+
+ // query again
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ conditionTracker.isConditionMet(queryKey, allPredicates, false, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ }
+
+}
+
//TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
// std::vector<sp<ConditionTracker>> allConditions;
//
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index e416b4c..1ff6621 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -28,6 +28,7 @@
#include "../metrics/metrics_test_helper.h"
#include "src/stats_log_util.h"
+#include "stats_event.h"
#include "tests/statsd_test_util.h"
#ifdef __ANDROID__
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 2bfce9b..d416f13 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -15,6 +15,7 @@
#include "statsd_test_util.h"
#include <aidl/android/util/StatsEventParcel.h>
+#include "stats_event.h"
using aidl::android::util::StatsEventParcel;
using std::shared_ptr;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index dbe3b7b..d7af1b9 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -529,6 +529,40 @@
private static int sPidFdSupported = PIDFD_UNKNOWN;
/**
+ * Value used to indicate that there is no special information about an application launch. App
+ * launches with this policy will occur through the primary or secondary Zygote with no special
+ * treatment.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_EMPTY = 0;
+
+ /**
+ * Flag used to indicate that an application launch is user-visible and latency sensitive. Any
+ * launch with this policy will use a Unspecialized App Process Pool if the target Zygote
+ * supports it.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE = 1 << 0;
+
+ /**
+ * Flag used to indicate that the launch is one in a series of app launches that will be
+ * performed in quick succession. For future use.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_BATCH_LAUNCH = 1 << 1;
+
+ /**
+ * Flag used to indicate that the current launch event is for a system process. All system
+ * processes are equally important, so none of them should be prioritized over the others.
+ *
+ * @hide
+ */
+ public static final int ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS = 1 << 2;
+
+ /**
* State associated with the zygote process.
* @hide
*/
@@ -567,6 +601,7 @@
* @param appDataDir null-ok the data directory of the app.
* @param invokeWith null-ok the command to invoke with.
* @param packageName null-ok the name of the package this process belongs to.
+ * @param zygotePolicyFlags Flags used to determine how to launch the application
* @param isTopApp whether the process starts for high priority application.
* @param disabledCompatChanges null-ok list of disabled compat changes for the process being
* started.
@@ -590,6 +625,7 @@
@Nullable String appDataDir,
@Nullable String invokeWith,
@Nullable String packageName,
+ int zygotePolicyFlags,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
@@ -598,7 +634,7 @@
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ true, isTopApp, disabledCompatChanges,
+ zygotePolicyFlags, isTopApp, disabledCompatChanges,
pkgDataInfoMap, zygoteArgs);
}
@@ -622,8 +658,8 @@
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges,
- /* pkgDataInfoMap */ null, zygoteArgs);
+ /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
+ disabledCompatChanges, /* pkgDataInfoMap */ null, zygoteArgs);
}
/**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 3846f89..34cec06 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -16,6 +16,9 @@
package android.os;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -119,6 +122,10 @@
mUsapPoolSecondarySocketAddress =
new LocalSocketAddress(Zygote.USAP_POOL_SECONDARY_SOCKET_NAME,
LocalSocketAddress.Namespace.RESERVED);
+
+ // This constructor is used to create the primary and secondary Zygotes, which can support
+ // Unspecialized App Process Pools.
+ mUsapPoolSupported = true;
}
public ZygoteProcess(LocalSocketAddress primarySocketAddress,
@@ -128,6 +135,10 @@
mUsapPoolSocketAddress = null;
mUsapPoolSecondarySocketAddress = null;
+
+ // This constructor is used to create the primary and secondary Zygotes, which CAN NOT
+ // support Unspecialized App Process Pools.
+ mUsapPoolSupported = false;
}
public LocalSocketAddress getPrimarySocketAddress() {
@@ -267,6 +278,14 @@
private ZygoteState secondaryZygoteState;
/**
+ * If this Zygote supports the creation and maintenance of a USAP pool.
+ *
+ * Currently only the primary and secondary Zygotes support USAP pools. Any
+ * child Zygotes will be unable to create or use a USAP pool.
+ */
+ private final boolean mUsapPoolSupported;
+
+ /**
* If the USAP pool should be created and used to start applications.
*
* Setting this value to false will disable the creation, maintenance, and use of the USAP
@@ -308,13 +327,14 @@
* @param appDataDir null-ok the data directory of the app.
* @param invokeWith null-ok the command to invoke with.
* @param packageName null-ok the name of the package this process belongs to.
+ * @param zygotePolicyFlags Flags used to determine how to launch the application.
+ * @param isTopApp Whether the process starts for high priority application.
* @param disabledCompatChanges null-ok list of disabled compat changes for the process being
* started.
- * @param zygoteArgs Additional arguments to supply to the zygote process.
- * @param isTopApp Whether the process starts for high priority application.
* @param pkgDataInfoMap Map from related package names to private data directory
* volume UUID and inode number.
*
+ * @param zygoteArgs Additional arguments to supply to the Zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
*/
@@ -329,7 +349,7 @@
@Nullable String appDataDir,
@Nullable String invokeWith,
@Nullable String packageName,
- boolean useUsapPool,
+ int zygotePolicyFlags,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
@@ -344,7 +364,7 @@
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
- packageName, useUsapPool, isTopApp, disabledCompatChanges,
+ packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
pkgDataInfoMap, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
@@ -391,7 +411,7 @@
*/
@GuardedBy("mLock")
private Process.ProcessStartResult zygoteSendArgsAndGetResult(
- ZygoteState zygoteState, boolean useUsapPool, @NonNull ArrayList<String> args)
+ ZygoteState zygoteState, int zygotePolicyFlags, @NonNull ArrayList<String> args)
throws ZygoteStartFailedEx {
// Throw early if any of the arguments are malformed. This means we can
// avoid writing a partial response to the zygote.
@@ -417,7 +437,7 @@
*/
String msgStr = args.size() + "\n" + String.join("\n", args) + "\n";
- if (useUsapPool && mUsapPoolEnabled && canAttemptUsap(args)) {
+ if (shouldAttemptUsapLaunch(zygotePolicyFlags, args)) {
try {
return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);
} catch (IOException ex) {
@@ -488,7 +508,43 @@
}
/**
- * Flags that may not be passed to a USAP.
+ * Test various member properties and parameters to determine if a launch event should be
+ * handled using an Unspecialized App Process Pool or not.
+ *
+ * @param zygotePolicyFlags Policy flags indicating special behavioral observations about the
+ * Zygote command
+ * @param args Arguments that will be passed to the Zygote
+ * @return If the command should be sent to a USAP Pool member or an actual Zygote
+ */
+ private boolean shouldAttemptUsapLaunch(int zygotePolicyFlags, ArrayList<String> args) {
+ return mUsapPoolSupported
+ && mUsapPoolEnabled
+ && policySpecifiesUsapPoolLaunch(zygotePolicyFlags)
+ && commandSupportedByUsap(args);
+ }
+
+ /**
+ * Tests a Zygote policy flag set for various properties that determine if it is eligible for
+ * being handled by an Unspecialized App Process Pool.
+ *
+ * @param zygotePolicyFlags Policy flags indicating special behavioral observations about the
+ * Zygote command
+ * @return If the policy allows for use of a USAP pool
+ */
+ private static boolean policySpecifiesUsapPoolLaunch(int zygotePolicyFlags) {
+ /*
+ * Zygote USAP Pool Policy: Launch the new process from the USAP Pool iff the launch event
+ * is latency sensitive but *NOT* a system process. All system processes are equally
+ * important so we don't want to prioritize one over another.
+ */
+ return (zygotePolicyFlags
+ & (ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS | ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE))
+ == ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+ }
+
+ /**
+ * Flags that may not be passed to a USAP. These may appear as prefixes to individual Zygote
+ * arguments.
*/
private static final String[] INVALID_USAP_FLAGS = {
"--query-abi-list",
@@ -505,10 +561,11 @@
/**
* Tests a command list to see if it is valid to send to a USAP.
+ *
* @param args Zygote/USAP command arguments
* @return True if the command can be passed to a USAP; false otherwise
*/
- private static boolean canAttemptUsap(ArrayList<String> args) {
+ private static boolean commandSupportedByUsap(ArrayList<String> args) {
for (String flag : args) {
for (String badFlag : INVALID_USAP_FLAGS) {
if (flag.startsWith(badFlag)) {
@@ -544,6 +601,7 @@
* @param startChildZygote Start a sub-zygote. This creates a new zygote process
* that has its state cloned from this zygote process.
* @param packageName null-ok the name of the package this process belongs to.
+ * @param zygotePolicyFlags Flags used to determine how to launch the application.
* @param isTopApp Whether the process starts for high priority application.
* @param disabledCompatChanges a list of disabled compat changes for the process being started.
* @param pkgDataInfoMap Map from related package names to private data directory volume UUID
@@ -565,7 +623,7 @@
@Nullable String invokeWith,
boolean startChildZygote,
@Nullable String packageName,
- boolean useUsapPool,
+ int zygotePolicyFlags,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
@Nullable Map<String, Pair<String, Long>>
@@ -692,7 +750,7 @@
// The USAP pool can not be used if the application will not use the systems graphics
// driver. If that driver is requested use the Zygote application start path.
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
- useUsapPool,
+ zygotePolicyFlags,
argsForZygote);
}
}
@@ -722,6 +780,10 @@
private long mLastPropCheckTimestamp = 0;
private boolean fetchUsapPoolEnabledPropWithMinInterval() {
+ // If this Zygote doesn't support USAPs there is no need to fetch any
+ // properties.
+ if (!mUsapPoolSupported) return false;
+
final long currentTimestamp = SystemClock.elapsedRealtime();
if (mIsFirstPropCheck
@@ -1219,7 +1281,7 @@
gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
- false /* useUsapPool */, false /* isTopApp */,
+ ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs);
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a47bd17..54cf693 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,6 +19,9 @@
import static android.system.OsConstants.S_IRWXG;
import static android.system.OsConstants.S_IRWXO;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START;
+
import android.app.ApplicationLoaders;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.SharedLibraryInfo;
@@ -52,7 +55,7 @@
import android.webkit.WebViewFactory;
import android.widget.TextView;
-import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import dalvik.system.DexFile;
@@ -863,11 +866,10 @@
Runnable caller;
try {
- // Report Zygote start time to tron unless it is a runtime restart
- if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
- MetricsLogger.histogram(null, "boot_zygote_init",
- (int) SystemClock.elapsedRealtime());
- }
+ // Store now for StatsLogging later.
+ final long startTime = SystemClock.elapsedRealtime();
+ final boolean isRuntimeRestarted = "1".equals(
+ SystemProperties.get("sys.boot_completed"));
String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
@@ -894,6 +896,17 @@
}
final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);
+ if (!isRuntimeRestarted) {
+ if (isPrimaryZygote) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START,
+ startTime);
+ } else if (zygoteSocketName.equals(Zygote.SECONDARY_SOCKET_NAME)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START,
+ startTime);
+ }
+ }
if (abiList == null) {
throw new RuntimeException("No ABI list supplied.");
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 4bef2e3..bd1bae6 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -670,7 +670,7 @@
repeated ExecutionStats execution_stats = 4;
- optional AlarmListener in_quota_alarm_listener = 5;
+ reserved 5; // in_quota_alarm_listener
}
repeated PackageStats package_stats = 5;
@@ -683,7 +683,25 @@
}
repeated UidPackageMapping uid_to_package_cache = 7;
- // Next tag: 8
+ message InQuotaAlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time at which the alarm is set to go off, in the elapsed realtime timebase.
+ optional int64 trigger_time_elapsed = 1;
+
+ message Alarm {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional Package pkg = 1;
+
+ // The time at which the package will be in quota, in the elapsed realtime timebase.
+ optional int64 in_quota_time_elapsed = 2;
+ }
+ repeated Alarm alarms = 2;
+ }
+ optional InQuotaAlarmListener in_quota_alarm_listener = 8;
+
+ // Next tag: 9
}
message StorageController {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 7a684b3..c79e72d 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -216,8 +216,8 @@
private native int nativeSetLnb(int lnbId);
private native int nativeSetLna(boolean enable);
private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
- private native int nativeGetAvSyncHwId(Filter filter);
- private native long nativeGetAvSyncTime(int avSyncId);
+ private native Integer nativeGetAvSyncHwId(Filter filter);
+ private native Long nativeGetAvSyncTime(int avSyncId);
private native int nativeConnectCiCam(int ciCamId);
private native int nativeDisconnectCiCam();
private native FrontendInfo nativeGetFrontendInfo(int id);
@@ -463,7 +463,8 @@
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public int getAvSyncHwId(@NonNull Filter filter) {
TunerUtils.checkTunerPermission(mContext);
- return nativeGetAvSyncHwId(filter);
+ Integer id = nativeGetAvSyncHwId(filter);
+ return id == null ? TunerConstants.INVALID_AV_SYNC_ID : id;
}
/**
@@ -478,7 +479,8 @@
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public long getAvSyncTime(int avSyncHwId) {
TunerUtils.checkTunerPermission(mContext);
- return nativeGetAvSyncTime(avSyncHwId);
+ Long time = nativeGetAvSyncTime(avSyncHwId);
+ return time == null ? TunerConstants.TIMESTAMP_UNAVAILABLE : time;
}
/**
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 82af658..6d89962 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -46,6 +46,19 @@
* Invalid AV Sync ID.
*/
public static final int INVALID_AV_SYNC_ID = Constants.Constant.INVALID_AV_SYNC_ID;
+ /**
+ * Timestamp is unavailable.
+ *
+ * <p>Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()},
+ * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()}, or
+ * {@link Tuner#getAvSyncTime(int)} when the requested timestamp is not available.
+ *
+ * @see android.media.tv.tuner.filter.TimeFilter#getSourceTime()
+ * @see android.media.tv.tuner.filter.TimeFilter#getTimeStamp()
+ * @see Tuner#getAvSyncTime(int)
+ * @hide
+ */
+ public static final long TIMESTAMP_UNAVAILABLE = -1L;
/** @hide */
@IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 5c39f29..e68ccfa 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -119,7 +119,6 @@
using ::android::hardware::tv::tuner::V1_0::ITuner;
using ::android::hardware::tv::tuner::V1_0::PlaybackSettings;
using ::android::hardware::tv::tuner::V1_0::RecordSettings;
-using ::android::hardware::tv::tuner::V1_0::Result;
struct fields_t {
jfieldID tunerContext;
@@ -845,22 +844,85 @@
return (int)result;
}
-bool JTuner::openDemux() {
+Result JTuner::openDemux() {
if (mTuner == nullptr) {
- return false;
+ return Result::NOT_INITIALIZED;
}
if (mDemux != nullptr) {
- return true;
+ return Result::SUCCESS;
}
- mTuner->openDemux([&](Result, uint32_t demuxId, const sp<IDemux>& demux) {
+ Result res;
+ mTuner->openDemux([&](Result r, uint32_t demuxId, const sp<IDemux>& demux) {
mDemux = demux;
mDemuxId = demuxId;
+ res = r;
ALOGD("open demux, id = %d", demuxId);
});
- if (mDemux == nullptr) {
- return false;
+ return res;
+}
+
+jobject JTuner::getAvSyncHwId(sp<Filter> filter) {
+ if (mDemux == NULL) {
+ return NULL;
}
- return true;
+
+ uint32_t avSyncHwId;
+ Result res;
+ sp<IFilter> iFilterSp = filter->getIFilter();
+ mDemux->getAvSyncHwId(iFilterSp,
+ [&](Result r, uint32_t id) {
+ res = r;
+ avSyncHwId = id;
+ });
+ if (res == Result::SUCCESS) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jclass integerClazz = env->FindClass("java/lang/Integer");
+ jmethodID intInit = env->GetMethodID(integerClazz, "<init>", "(I)V");
+ return env->NewObject(integerClazz, intInit, avSyncHwId);
+ }
+ return NULL;
+}
+
+jobject JTuner::getAvSyncTime(jint id) {
+ if (mDemux == NULL) {
+ return NULL;
+ }
+ uint64_t time;
+ Result res;
+ mDemux->getAvSyncTime(static_cast<uint32_t>(id),
+ [&](Result r, uint64_t ts) {
+ res = r;
+ time = ts;
+ });
+ if (res == Result::SUCCESS) {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ jclass longClazz = env->FindClass("java/lang/Long");
+ jmethodID longInit = env->GetMethodID(longClazz, "<init>", "(J)V");
+ return env->NewObject(longClazz, longInit, static_cast<jlong>(time));
+ }
+ return NULL;
+}
+
+int JTuner::connectCiCam(jint id) {
+ if (mDemux == NULL) {
+ Result r = openDemux();
+ if (r != Result::SUCCESS) {
+ return (int) r;
+ }
+ }
+ Result r = mDemux->connectCiCam(static_cast<uint32_t>(id));
+ return (int) r;
+}
+
+int JTuner::disconnectCiCam() {
+ if (mDemux == NULL) {
+ Result r = openDemux();
+ if (r != Result::SUCCESS) {
+ return (int) r;
+ }
+ }
+ Result r = mDemux->disconnectCiCam();
+ return (int) r;
}
jobject JTuner::openDescrambler() {
@@ -892,7 +954,7 @@
jobject JTuner::openFilter(DemuxFilterType type, int bufferSize) {
if (mDemux == NULL) {
- if (!openDemux()) {
+ if (openDemux() != Result::SUCCESS) {
return NULL;
}
}
@@ -917,7 +979,6 @@
env->NewObject(
env->FindClass("android/media/tv/tuner/filter/Filter"),
gFields.filterInitID,
- mObject,
(jint) fId);
sp<Filter> filterSp = new Filter(iFilterSp, filterObj);
@@ -931,7 +992,7 @@
jobject JTuner::openTimeFilter() {
if (mDemux == NULL) {
- if (!openDemux()) {
+ if (openDemux() != Result::SUCCESS) {
return NULL;
}
}
@@ -962,7 +1023,7 @@
jobject JTuner::openDvr(DvrType type, int bufferSize) {
ALOGD("JTuner::openDvr");
if (mDemux == NULL) {
- if (!openDemux()) {
+ if (openDemux() != Result::SUCCESS) {
return NULL;
}
}
@@ -1608,20 +1669,30 @@
return NULL;
}
-static int android_media_tv_Tuner_gat_av_sync_hw_id(JNIEnv*, jobject, jobject) {
- return 0;
+static jobject android_media_tv_Tuner_get_av_sync_hw_id(
+ JNIEnv *env, jobject thiz, jobject filter) {
+ sp<Filter> filterSp = getFilter(env, filter);
+ if (filterSp == NULL) {
+ ALOGD("Failed to get sync ID. Filter not found");
+ return NULL;
+ }
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->getAvSyncHwId(filterSp);
}
-static jlong android_media_tv_Tuner_gat_av_sync_time(JNIEnv*, jobject, jint) {
- return 0;
+static jobject android_media_tv_Tuner_get_av_sync_time(JNIEnv *env, jobject thiz, jint id) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->getAvSyncTime(id);
}
-static int android_media_tv_Tuner_connect_cicam(JNIEnv*, jobject, jint) {
- return 0;
+static int android_media_tv_Tuner_connect_cicam(JNIEnv *env, jobject thiz, jint id) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->connectCiCam(id);
}
-static int android_media_tv_Tuner_disconnect_cicam(JNIEnv*, jobject) {
- return 0;
+static int android_media_tv_Tuner_disconnect_cicam(JNIEnv *env, jobject thiz) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->disconnectCiCam();
}
static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thiz, jint id) {
@@ -2500,9 +2571,10 @@
{ "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
{ "nativeGetFrontendStatus", "([I)Landroid/media/tv/tuner/frontend/FrontendStatus;",
(void *)android_media_tv_Tuner_get_frontend_status },
- { "nativeGetAvSyncHwId", "(Landroid/media/tv/tuner/filter/Filter;)I",
- (void *)android_media_tv_Tuner_gat_av_sync_hw_id },
- { "nativeGetAvSyncTime", "(I)J", (void *)android_media_tv_Tuner_gat_av_sync_time },
+ { "nativeGetAvSyncHwId", "(Landroid/media/tv/tuner/filter/Filter;)Ljava/lang/Integer;",
+ (void *)android_media_tv_Tuner_get_av_sync_hw_id },
+ { "nativeGetAvSyncTime", "(I)Ljava/lang/Long;",
+ (void *)android_media_tv_Tuner_get_av_sync_time },
{ "nativeConnectCiCam", "(I)I", (void *)android_media_tv_Tuner_connect_cicam },
{ "nativeDisconnectCiCam", "()I", (void *)android_media_tv_Tuner_disconnect_cicam },
{ "nativeGetFrontendInfo", "(I)Landroid/media/tv/tuner/frontend/FrontendInfo;",
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 32d4899..b786fc4 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -60,6 +60,7 @@
using ::android::hardware::tv::tuner::V1_0::LnbId;
using ::android::hardware::tv::tuner::V1_0::PlaybackStatus;
using ::android::hardware::tv::tuner::V1_0::RecordStatus;
+using ::android::hardware::tv::tuner::V1_0::Result;
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
using DvrMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
@@ -141,6 +142,10 @@
struct JTuner : public RefBase {
JTuner(JNIEnv *env, jobject thiz);
sp<ITuner> getTunerService();
+ jobject getAvSyncHwId(sp<Filter> filter);
+ jobject getAvSyncTime(jint id);
+ int connectCiCam(jint id);
+ int disconnectCiCam();
jobject getFrontendIds();
jobject openFrontendById(int id);
jobject getFrontendInfo(int id);
@@ -158,7 +163,7 @@
jobject openDvr(DvrType type, int bufferSize);
protected:
- bool openDemux();
+ Result openDemux();
virtual ~JTuner();
private:
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index a9c6685..ea9b52c 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -30,11 +30,13 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<application
android:allowClearUserData="true"
android:label="@string/app_label"
android:allowBackup="false"
+ android:forceQueryable="true"
android:supportsRtl="true">
<service
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index 5e8779f..88c6185 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -39,4 +39,7 @@
<color name="dark_mode_icon_color_single_tone">#99000000</color>
<color name="light_mode_icon_color_single_tone">#ffffff</color>
+
+ <!-- Yellow 600, used for highlighting "important" conversations in settings & notifications -->
+ <color name="important_conversation">#f9ab00</color>
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2297ddf..e8e1d0b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -895,7 +895,7 @@
<!-- UI debug setting: enable verbose vendor logging [CHAR LIMIT=60] -->
<string name="enable_verbose_vendor_logging">Enable verbose vendor logging</string>
<!-- UI debug setting: enable verbose vendor logging summary [CHAR LIMIT=NONE] -->
- <string name="enable_verbose_vendor_logging_summary">Allow additional vendor logs to be included in bug reports, may contain private information</string>
+ <string name="enable_verbose_vendor_logging_summary">Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery, and/or use more storage.</string>
<!-- UI debug setting: scaling factor for window animations [CHAR LIMIT=25] -->
<string name="window_animation_scale_title">Window animation scale</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
index 885b7d3..9dc454f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -15,32 +15,48 @@
*/
package com.android.settingslib.notification;
+import android.annotation.ColorInt;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.IconDrawableFactory;
+import android.util.Log;
import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ShadowGenerator;
+import com.android.settingslib.R;
/**
* Factory for creating normalized conversation icons.
* We are not using Launcher's IconFactory because conversation rendering only runs on the UI
- * thread, so there is no need to manage a pool across multiple threads.
+ * thread, so there is no need to manage a pool across multiple threads. Launcher's rendering
+ * also includes shadows, which are only appropriate on top of wallpaper, not embedded in UI.
*/
public class ConversationIconFactory extends BaseIconFactory {
+ // Geometry of the various parts of the design. All values are 1dp on a 48x48dp icon grid.
+ // Space is left around the "head" (main avatar) for
+ // ........
+ // .HHHHHH.
+ // .HHHrrrr
+ // .HHHrBBr
+ // ....rrrr
+
+ private static final float BASE_ICON_SIZE = 48f;
+ private static final float RING_STROKE_WIDTH = 2f;
+ private static final float HEAD_SIZE = BASE_ICON_SIZE - RING_STROKE_WIDTH * 2 - 2; // 40
+ private static final float BADGE_SIZE = HEAD_SIZE * 0.4f; // 16
final LauncherApps mLauncherApps;
final PackageManager mPackageManager;
final IconDrawableFactory mIconDrawableFactory;
+ private int mImportantConversationColor;
public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm,
IconDrawableFactory iconDrawableFactory, int iconSizePx) {
@@ -49,65 +65,156 @@
mLauncherApps = la;
mPackageManager = pm;
mIconDrawableFactory = iconDrawableFactory;
+ mImportantConversationColor = context.getResources().getColor(
+ R.color.important_conversation, null);
}
- private int getBadgeSize() {
- return mContext.getResources().getDimensionPixelSize(
- com.android.launcher3.icons.R.dimen.profile_badge_size);
- }
/**
* Returns the conversation info drawable
*/
- private Drawable getConversationDrawable(ShortcutInfo shortcutInfo) {
+ private Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) {
return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
}
/**
- * Get the {@link Drawable} that represents the app icon
+ * Get the {@link Drawable} that represents the app icon, badged with the work profile icon
+ * if appropriate.
*/
- private Drawable getBadgedIcon(String packageName, int userId) {
+ private Drawable getAppBadge(String packageName, int userId) {
+ Drawable badge = null;
try {
final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
packageName, PackageManager.GET_META_DATA, userId);
- return mIconDrawableFactory.getBadgedIcon(appInfo, userId);
+ badge = mIconDrawableFactory.getBadgedIcon(appInfo, userId);
} catch (PackageManager.NameNotFoundException e) {
- return mPackageManager.getDefaultActivityIcon();
+ badge = mPackageManager.getDefaultActivityIcon();
}
+ return badge;
}
/**
- * Turns a Drawable into a Bitmap
+ * Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged
+ * with the launcher icon of the app specified by packageName.
*/
- BitmapInfo toBitmap(Drawable userBadgedAppIcon) {
- Bitmap bitmap = createIconBitmap(
- userBadgedAppIcon, 1f, getBadgeSize());
-
- Canvas c = new Canvas();
- ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
- c.setBitmap(bitmap);
- shadowGenerator.recreateIcon(Bitmap.createBitmap(bitmap), c);
- return createIconBitmap(bitmap);
+ public Drawable getConversationDrawable(ShortcutInfo info, String packageName, int uid,
+ boolean important) {
+ return getConversationDrawable(getBaseIconDrawable(info), packageName, uid, important);
}
/**
- * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
+ * Returns a {@link Drawable} for the entire conversation. The drawable will be badged
+ * with the launcher icon of the app specified by packageName.
*/
- public Bitmap getConversationBitmap(ShortcutInfo info, String packageName, int uid) {
- return getConversationBitmap(getConversationDrawable(info), packageName, uid);
+ public Drawable getConversationDrawable(Drawable baseIcon, String packageName, int uid,
+ boolean important) {
+ return new ConversationIconDrawable(baseIcon,
+ getAppBadge(packageName, UserHandle.getUserId(uid)),
+ mIconBitmapSize,
+ mImportantConversationColor,
+ important);
}
/**
- * Returns a {@link BitmapInfo} for the entire conversation icon including the badge.
+ * Custom Drawable that overlays a badge drawable (e.g. notification small icon or app icon) on
+ * a base icon (conversation/person avatar), plus decorations indicating conversation
+ * importance.
*/
- public Bitmap getConversationBitmap(Drawable baseIcon, String packageName, int uid) {
- int userId = UserHandle.getUserId(uid);
- Drawable badge = getBadgedIcon(packageName, userId);
- BitmapInfo iconInfo = createBadgedIconBitmap(baseIcon,
- UserHandle.of(userId),
- true /* shrinkNonAdaptiveIcons */);
+ public static class ConversationIconDrawable extends Drawable {
+ private Drawable mBaseIcon;
+ private Drawable mBadgeIcon;
+ private int mIconSize;
+ private Paint mRingPaint;
+ private boolean mShowRing;
- badgeWithDrawable(iconInfo.icon,
- new BitmapDrawable(mContext.getResources(), toBitmap(badge).icon));
- return iconInfo.icon;
+ public ConversationIconDrawable(Drawable baseIcon,
+ Drawable badgeIcon,
+ int iconSize,
+ @ColorInt int ringColor,
+ boolean showImportanceRing) {
+ mBaseIcon = baseIcon;
+ mBadgeIcon = badgeIcon;
+ mIconSize = iconSize;
+ mShowRing = showImportanceRing;
+ mRingPaint = new Paint();
+ mRingPaint.setStyle(Paint.Style.STROKE);
+ mRingPaint.setColor(ringColor);
+ }
+
+ /**
+ * Show or hide the importance ring.
+ */
+ public void setImportant(boolean important) {
+ if (important != mShowRing) {
+ mShowRing = important;
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mIconSize;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mIconSize;
+ }
+
+ // Similar to badgeWithDrawable, but relying on the bounds of each underlying drawable
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+
+ // scale to our internal 48x48 grid
+ final float scale = bounds.width() / BASE_ICON_SIZE;
+ final int centerX = bounds.centerX();
+ final int centerY = bounds.centerX();
+ final int ringStrokeWidth = (int) (RING_STROKE_WIDTH * scale);
+ final int headSize = (int) (HEAD_SIZE * scale);
+ final int badgeSize = (int) (BADGE_SIZE * scale);
+
+ if (mBaseIcon != null) {
+ mBaseIcon.setBounds(
+ centerX - headSize / 2,
+ centerY - headSize / 2,
+ centerX + headSize / 2,
+ centerY + headSize / 2);
+ mBaseIcon.draw(canvas);
+ } else {
+ Log.w("ConversationIconFactory", "ConversationIconDrawable has null base icon");
+ }
+ if (mBadgeIcon != null) {
+ mBadgeIcon.setBounds(
+ bounds.right - badgeSize - ringStrokeWidth,
+ bounds.bottom - badgeSize - ringStrokeWidth,
+ bounds.right - ringStrokeWidth,
+ bounds.bottom - ringStrokeWidth);
+ mBadgeIcon.draw(canvas);
+ } else {
+ Log.w("ConversationIconFactory", "ConversationIconDrawable has null badge icon");
+ }
+ if (mShowRing) {
+ mRingPaint.setStrokeWidth(ringStrokeWidth);
+ final float radius = badgeSize * 0.5f + ringStrokeWidth * 0.5f; // stroke outside
+ final float cx = bounds.right - badgeSize * 0.5f - ringStrokeWidth;
+ final float cy = bounds.bottom - badgeSize * 0.5f - ringStrokeWidth;
+ canvas.drawCircle(cx, cy, radius, mRingPaint);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // unimplemented
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ // unimplemented
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
}
}
diff --git a/packages/SystemUI/res-keyguard/drawable/bubble_manage_user_education_bg.xml b/packages/SystemUI/res-keyguard/drawable/bubble_manage_user_education_bg.xml
new file mode 100644
index 0000000..64db25b
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bubble_manage_user_education_bg.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorAccent"/>
+ <corners
+ android:radius="?android:attr/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml b/packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml
new file mode 100644
index 0000000..4b9219c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?android:attr/colorAccent"/>
+ <corners
+ android:bottomRightRadius="360dp"
+ android:topRightRadius="360dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_stack_user_education.xml b/packages/SystemUI/res/layout/bubble_stack_user_education.xml
new file mode 100644
index 0000000..81b28e6
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_stack_user_education.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/user_education_view"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingTop="48dp"
+ android:paddingBottom="48dp"
+ android:paddingStart="@dimen/bubble_stack_user_education_side_inset"
+ android:paddingEnd="16dp"
+ android:layout_marginEnd="24dp"
+ android:orientation="vertical"
+ android:background="@drawable/bubble_stack_user_education_bg">
+
+ <TextView
+ android:id="@+id/user_education_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="16dp"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+ android:maxLines="1"
+ android:text="@string/bubbles_user_education_title"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/>
+
+ <TextView
+ android:id="@+id/user_education_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/bubbles_user_education_description"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
new file mode 100644
index 0000000..0cabc32
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<com.android.systemui.bubbles.BubbleManageEducationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/manage_education_view"
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="@dimen/bubbles_manage_education_width">
+
+ <TextView
+ android:id="@+id/user_education_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:text="@string/bubbles_user_education_manage"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+ android:background="@drawable/bubble_manage_user_education_bg"
+ />
+
+ <View
+ android:id="@+id/user_education_pointer"
+ android:layout_width="@dimen/bubble_pointer_width"
+ android:layout_height="@dimen/bubble_pointer_height"
+ />
+
+ </LinearLayout>
+</com.android.systemui.bubbles.BubbleManageEducationView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 12b9254..aefe4a2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1159,7 +1159,7 @@
<!-- Extra padding around the dismiss target for bubbles -->
<dimen name="bubble_dismiss_slop">16dp</dimen>
<!-- Height of button allowing users to adjust settings for bubbles. -->
- <dimen name="bubble_settings_size">48dp</dimen>
+ <dimen name="bubble_manage_button_height">48dp</dimen>
<!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
<dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
<!-- How far, vertically, to animate the expanded view over when animating in/out. -->
@@ -1175,16 +1175,22 @@
<!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
<dimen name="bubble_stack_offscreen">9dp</dimen>
<!-- How far down the screen the stack starts. -->
- <dimen name="bubble_stack_starting_offset_y">96dp</dimen>
+ <dimen name="bubble_stack_starting_offset_y">120dp</dimen>
<!-- Space between the pointer triangle and the bubble expanded view -->
<dimen name="bubble_pointer_margin">8dp</dimen>
- <!-- Height of the permission prompt shown with bubbles -->
- <dimen name="bubble_permission_height">120dp</dimen>
<!-- Padding applied to the bubble dismiss target. Touches in this padding cause the bubbles to
snap to the dismiss target. -->
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+ <!-- Bubbles user education views -->
+ <dimen name="bubbles_manage_education_width">160dp</dimen>
+ <!-- The inset from the top bound of the manage button to place the user education. -->
+ <dimen name="bubbles_manage_education_top_inset">10dp</dimen>
+ <!-- Size of padding for the user education cling, this should at minimum be larger than
+ individual_bubble_size + some padding. -->
+ <dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
+
<!-- Size of the RAT type for CellularTile -->
<dimen name="celltile_rat_type_size">10sp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ff28b4d..496ab43 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2586,6 +2586,12 @@
<string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
<!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=20] -->
<string name="bubble_dismiss_text">Dismiss</string>
+ <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
+ <string name="bubbles_user_education_title">Keep chats up front</string>
+ <!-- Descriptive text for the bubble feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
+ <string name="bubbles_user_education_description">New chats from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> will appear as bubbles. Tap a bubble to open it. Drag to move it.\n\nTap the bubble</string>
+ <!-- Text for the bubble "manage" button feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=80]-->
+ <string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
<!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] -->
<string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f0a82c5..5e6589f 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -57,7 +57,9 @@
Key.SEEN_RINGER_GUIDANCE_COUNT,
Key.QS_HAS_TURNED_OFF_MOBILE_DATA,
Key.TOUCHED_RINGER_TOGGLE,
- Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP
+ Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
+ Key.HAS_SEEN_BUBBLES_EDUCATION,
+ Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION
})
public @interface Key {
@Deprecated
@@ -103,6 +105,8 @@
String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData";
String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle";
String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip";
+ String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding";
+ String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
index 3190662..e800011 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -16,6 +16,9 @@
package com.android.systemui.bubbles;
+import android.content.Context;
+import android.provider.Settings;
+
import java.util.List;
/**
@@ -41,6 +44,20 @@
static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
static final boolean DEBUG_EXPERIMENTS = true;
static final boolean DEBUG_OVERFLOW = false;
+ static final boolean DEBUG_USER_EDUCATION = false;
+
+ private static final boolean FORCE_SHOW_USER_EDUCATION = false;
+ private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
+ "force_show_bubbles_user_education";
+
+ /**
+ * @return whether we should force show user education for bubbles. Used for debugging & demos.
+ */
+ static boolean forceShowUserEducation(Context context) {
+ boolean forceShow = Settings.Secure.getInt(context.getContentResolver(),
+ FORCE_SHOW_USER_EDUCATION_SETTING, 0) != 0;
+ return FORCE_SHOW_USER_EDUCATION || forceShow;
+ }
static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
StringBuilder sb = new StringBuilder();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index e3983c5..a6f759f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -243,7 +243,7 @@
mPointerView.setVisibility(INVISIBLE);
mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
- R.dimen.bubble_settings_size);
+ R.dimen.bubble_manage_button_height);
mSettingsIcon = findViewById(R.id.settings_button);
mSettingsIcon.setOnClickListener(this);
@@ -531,6 +531,16 @@
}
/**
+ * Position of the manage button displayed in the expanded view. Used for placing user
+ * education about the manage button.
+ */
+ public Rect getManageButtonLocationOnScreen() {
+ mTempLoc = mSettingsIcon.getLocationOnScreen();
+ return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(),
+ mTempLoc[1] + mSettingsIcon.getHeight());
+ }
+
+ /**
* Removes and releases an ActivityView if one was previously created for this bubble.
*/
public void cleanUpExpandedState() {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
new file mode 100644
index 0000000..f4d6432
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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.systemui.bubbles;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.util.ContrastColorUtil;
+import com.android.systemui.R;
+import com.android.systemui.recents.TriangleShape;
+
+/**
+ * Educational view to highlight the manage button that allows a user to configure the settings
+ * for the bubble. Shown only the first time a user expands a bubble.
+ */
+public class BubbleManageEducationView extends LinearLayout {
+
+ private View mPointerView;
+ private View mManageView;
+
+ public BubbleManageEducationView(Context context) {
+ this(context, null);
+ }
+
+ public BubbleManageEducationView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mManageView = findViewById(R.id.manage_education_view);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.colorAccent,
+ android.R.attr.textColorPrimaryInverse});
+ final int bgColor = ta.getColor(0, Color.BLACK);
+ int textColor = ta.getColor(1, Color.WHITE);
+ ta.recycle();
+
+ textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
+ ((TextView) findViewById(R.id.user_education_description)).setTextColor(textColor);
+
+ final Resources res = getResources();
+ final int pointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
+ final int pointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
+
+ ShapeDrawable triangleShape =
+ new ShapeDrawable(TriangleShape.create(
+ pointerWidth, pointerHeight, false /* isPointingUp */));
+ triangleShape.setTint(bgColor);
+
+ mPointerView = findViewById(R.id.user_education_pointer);
+ mPointerView.setBackground(triangleShape);
+ }
+
+ /**
+ * Specifies the x value this pointer should point to.
+ */
+ public void setPointerPosition(int x) {
+ mPointerView.setTranslationX(x - (mPointerView.getWidth() / 2));
+ }
+
+ /**
+ * Specifies the position for the manage view.
+ */
+ public void setManageViewPosition(int x, int y) {
+ mManageView.setTranslationX(x);
+ mManageView.setTranslationY(y);
+ }
+
+ /**
+ * @return the height of the view that shows the educational text and pointer.
+ */
+ public int getManageViewHeight() {
+ return mManageView.getHeight();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index a0e7449..313bb42 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -62,11 +62,12 @@
R.dimen.bubble_overflow_icon_bitmap_size);
}
- public void setUpOverflow(ViewGroup parentViewGroup) {
+ void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) {
mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate(
R.layout.bubble_expanded_view, parentViewGroup /* root */,
false /* attachToRoot */);
mOverflowExpandedView.setOverflow(true);
+ mOverflowExpandedView.setStackView(stackView);
updateIcon(mContext, parentViewGroup);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index df8e394..6647069 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,9 +19,13 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION;
+import static com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION;
import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_DEFAULT;
import static com.android.systemui.bubbles.BadgedImageView.DOT_STATE_SUPPRESSED_FOR_FLYOUT;
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -33,6 +37,8 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
@@ -57,6 +63,7 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
+import android.widget.TextView;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
@@ -66,7 +73,9 @@
import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
@@ -88,6 +97,10 @@
public class BubbleStackView extends FrameLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
+ /** Animation durations for bubble stack user education views. **/
+ private static final int ANIMATE_STACK_USER_EDUCATION_DURATION = 200;
+ private static final int ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT = 40;
+
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
@@ -171,6 +184,12 @@
* previous one animates out.
*/
private Runnable mAfterFlyoutHidden;
+ /**
+ * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
+ * once it collapses.
+ */
+ @Nullable
+ private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
/** Layout change listener that moves the stack to the nearest valid position on rotation. */
private OnLayoutChangeListener mOrientationChangedListener;
@@ -319,6 +338,14 @@
private BubbleOverflow mBubbleOverflow;
+ private boolean mShouldShowUserEducation;
+ private boolean mAnimatingEducationAway;
+ private View mUserEducationView;
+
+ private boolean mShouldShowManageEducation;
+ private BubbleManageEducationView mManageEducationView;
+ private boolean mAnimatingManageEducationAway;
+
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator) {
@@ -361,6 +388,8 @@
mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
+ setUpUserEducation();
+
mBubbleContainer = new PhysicsAnimationLayout(context);
mBubbleContainer.setActiveController(mStackAnimationController);
mBubbleContainer.setElevation(elevation);
@@ -500,10 +529,50 @@
});
}
- void showExpandedViewContents(int displayId) {
- if (mExpandedBubble != null
- && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
- mExpandedBubble.setContentVisibility(true);
+ private void setUpUserEducation() {
+ if (mUserEducationView != null) {
+ removeView(mUserEducationView);
+ }
+ mShouldShowUserEducation = shouldShowBubblesEducation();
+ if (DEBUG_USER_EDUCATION) {
+ Log.d(TAG, "shouldShowUserEducation: " + mShouldShowUserEducation);
+ }
+ if (mShouldShowUserEducation) {
+ mUserEducationView = mInflater.inflate(R.layout.bubble_stack_user_education, this,
+ false /* attachToRoot */);
+ mUserEducationView.setVisibility(GONE);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.colorAccent,
+ android.R.attr.textColorPrimaryInverse});
+ final int bgColor = ta.getColor(0, Color.BLACK);
+ int textColor = ta.getColor(1, Color.WHITE);
+ ta.recycle();
+ textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
+
+ TextView title = mUserEducationView.findViewById(R.id.user_education_title);
+ TextView description = mUserEducationView.findViewById(R.id.user_education_description);
+ title.setTextColor(textColor);
+ description.setTextColor(textColor);
+
+ addView(mUserEducationView);
+ }
+
+ if (mManageEducationView != null) {
+ removeView(mManageEducationView);
+ }
+ mShouldShowManageEducation = shouldShowManageEducation();
+ if (DEBUG_USER_EDUCATION) {
+ Log.d(TAG, "shouldShowManageEducation: " + mShouldShowManageEducation);
+ }
+ if (mShouldShowManageEducation) {
+ mManageEducationView = (BubbleManageEducationView)
+ mInflater.inflate(R.layout.bubbles_manage_button_education, this,
+ false /* attachToRoot */);
+ mManageEducationView.setVisibility(GONE);
+ mManageEducationView.setElevation(mBubbleElevation);
+
+ addView(mManageEducationView);
}
}
@@ -522,8 +591,8 @@
private void setUpOverflow() {
int overflowBtnIndex = 0;
if (mBubbleOverflow == null) {
- mBubbleOverflow = new BubbleOverflow(mContext);
- mBubbleOverflow.setUpOverflow(this);
+ mBubbleOverflow = new BubbleOverflow(getContext());
+ mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
} else {
mBubbleContainer.removeView(mBubbleOverflow.getBtn());
mBubbleOverflow.updateIcon(mContext, this);
@@ -539,6 +608,7 @@
public void onThemeChanged() {
setUpFlyout();
setUpOverflow();
+ setUpUserEducation();
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -731,7 +801,7 @@
Bubble getExpandedBubble() {
if (mExpandedBubble == null
|| (mExpandedBubble.getIconView() == mBubbleOverflow.getBtn()
- && mExpandedBubble.getKey() == BubbleOverflow.KEY)) {
+ && BubbleOverflow.KEY.equals(mExpandedBubble.getKey()))) {
return null;
}
return (Bubble) mExpandedBubble;
@@ -743,6 +813,12 @@
Log.d(TAG, "addBubble: " + bubble);
}
+ if (getBubbleCount() == 0 && mShouldShowUserEducation) {
+ // Override the default stack position if we're showing user education.
+ mStackAnimationController.setStackPosition(
+ mStackAnimationController.getDefaultStartPosition());
+ }
+
if (getBubbleCount() == 0) {
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
@@ -881,6 +957,109 @@
}
/**
+ * If necessary, shows the user education view for the bubble stack. This appears the first
+ * time a user taps on a bubble.
+ *
+ * @return true if user education was shown, false otherwise.
+ */
+ private boolean maybeShowStackUserEducation() {
+ if (mShouldShowUserEducation && mUserEducationView.getVisibility() != VISIBLE) {
+ Bubble b = mBubbleData.getSelectedBubble();
+ TextView description = mUserEducationView.findViewById(R.id.user_education_description);
+ description.setText(mContext.getString(
+ R.string.bubbles_user_education_description, b.getAppName()));
+
+ mUserEducationView.setAlpha(0);
+ mUserEducationView.setVisibility(VISIBLE);
+ // Post so we have height of mUserEducationView
+ mUserEducationView.post(() -> {
+ final int viewHeight = mUserEducationView.getHeight();
+ PointF stackPosition = mStackAnimationController.getDefaultStartPosition();
+ final float translationY = stackPosition.y + (mBubbleSize / 2) - (viewHeight / 2);
+ mUserEducationView.setTranslationY(translationY);
+ mUserEducationView.animate()
+ .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .setInterpolator(FAST_OUT_SLOW_IN)
+ .alpha(1);
+ });
+ Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_EDUCATION, true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * If necessary, hides the user education view for the bubble stack.
+ *
+ * @param fromExpansion if true this indicates the hide is happening due to the bubble being
+ * expanded, false if due to a touch outside of the bubble stack.
+ */
+ void hideStackUserEducation(boolean fromExpansion) {
+ if (mShouldShowUserEducation
+ && mUserEducationView.getVisibility() == VISIBLE
+ && !mAnimatingEducationAway) {
+ mAnimatingEducationAway = true;
+ mUserEducationView.animate()
+ .alpha(0)
+ .setDuration(fromExpansion
+ ? ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT
+ : ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .withEndAction(() -> {
+ mAnimatingEducationAway = false;
+ mShouldShowUserEducation = shouldShowBubblesEducation();
+ mUserEducationView.setVisibility(GONE);
+ });
+ }
+ }
+
+ /**
+ * If necessary, toggles the user education view for the manage button. This is shown when the
+ * bubble stack is expanded for the first time.
+ *
+ * @param show whether the user education view should show or not.
+ */
+ void maybeShowManageEducation(boolean show) {
+ if (mManageEducationView == null) {
+ return;
+ }
+ if (show
+ && mShouldShowManageEducation
+ && mManageEducationView.getVisibility() != VISIBLE
+ && mIsExpanded) {
+ mManageEducationView.setAlpha(0);
+ mManageEducationView.setVisibility(VISIBLE);
+ mManageEducationView.post(() -> {
+ final Rect position =
+ mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen();
+ final int viewHeight = mManageEducationView.getManageViewHeight();
+ final int inset = getResources().getDimensionPixelSize(
+ R.dimen.bubbles_manage_education_top_inset);
+ mManageEducationView.bringToFront();
+ mManageEducationView.setManageViewPosition(position.left,
+ position.top - viewHeight + inset);
+ mManageEducationView.setPointerPosition(position.centerX() - position.left);
+ mManageEducationView.animate()
+ .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
+ });
+ Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, true);
+ } else if (!show
+ && mManageEducationView.getVisibility() == VISIBLE
+ && !mAnimatingManageEducationAway) {
+ mManageEducationView.animate()
+ .alpha(0)
+ .setDuration(mIsExpansionAnimating
+ ? ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT
+ : ANIMATE_STACK_USER_EDUCATION_DURATION)
+ .withEndAction(() -> {
+ mAnimatingManageEducationAway = false;
+ mShouldShowManageEducation = shouldShowManageEducation();
+ mManageEducationView.setVisibility(GONE);
+ });
+ }
+ }
+
+ /**
* Dismiss the stack of bubbles.
*
* @deprecated
@@ -923,7 +1102,17 @@
return null;
} else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
return mFlyout;
+ } else if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
+ View bubbleChild = mBubbleContainer.getChildAt(0);
+ if (isIntersecting(bubbleChild, x, y)) {
+ return this;
+ } else if (isIntersecting(mUserEducationView, x, y)) {
+ return mUserEducationView;
+ } else {
+ return null;
+ }
}
+
// If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
return this;
}
@@ -933,22 +1122,6 @@
}
/**
- * Collapses the stack of bubbles.
- * <p>
- * Must be called from the main thread.
- *
- * @deprecated use {@link #setExpanded(boolean)} and {@link #setSelectedBubble(Bubble)}
- */
- @Deprecated
- @MainThread
- void collapseStack() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "collapseStack()");
- }
- mBubbleData.setExpanded(false);
- }
-
- /**
* @deprecated use {@link #setExpanded(boolean)} and {@link #setSelectedBubble(Bubble)}
*/
@Deprecated
@@ -957,25 +1130,16 @@
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "collapseStack(endRunnable)");
}
- collapseStack();
+ mBubbleData.setExpanded(false);
// TODO - use the runnable at end of animation
endRunnable.run();
}
- /**
- * Expands the stack of bubbles.
- * <p>
- * Must be called from the main thread.
- *
- * @deprecated use {@link #setExpanded(boolean)} and {@link #setSelectedBubble(Bubble)}
- */
- @Deprecated
- @MainThread
- void expandStack() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "expandStack()");
+ void showExpandedViewContents(int displayId) {
+ if (mExpandedBubble != null
+ && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
+ mExpandedBubble.setContentVisibility(true);
}
- mBubbleData.setExpanded(true);
}
private void beforeExpandedViewAnimation() {
@@ -995,11 +1159,12 @@
mIsExpanded = false;
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
+ maybeShowManageEducation(false);
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(this.getBubblesOnScreen(),
- this.getExpandedBubble()));
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
+ getExpandedBubble()));
}
updateOverflowBtnVisibility(/* apply */ false);
mBubbleContainer.cancelAllAnimations();
@@ -1021,6 +1186,7 @@
private void animateExpansion() {
mIsExpanded = true;
+ hideStackUserEducation(true /* fromExpansion */);
beforeExpandedViewAnimation();
mBubbleContainer.setActiveController(mExpandedAnimationController);
@@ -1028,6 +1194,7 @@
mExpandedAnimationController.expandFromStack(() -> {
updatePointerPosition();
afterExpandedViewAnimation();
+ maybeShowManageEducation(true);
} /* after */);
mExpandedViewContainer.setTranslationX(getCollapsedX());
@@ -1074,11 +1241,19 @@
}
}
+ /** Called when the collapsed stack is tapped on. */
+ void onStackTapped() {
+ if (!maybeShowStackUserEducation()) {
+ mBubbleData.setExpanded(true);
+ }
+ }
+
/** Called when a drag operation on an individual bubble has started. */
public void onBubbleDragStart(View bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
}
+ maybeShowManageEducation(false);
mExpandedAnimationController.prepareForBubbleDrag(bubble);
}
@@ -1129,6 +1304,7 @@
return;
}
+ hideStackUserEducation(false /* fromExpansion */);
springInDismissTarget();
mStackAnimationController.moveStackFromTouch(x, y);
}
@@ -1191,14 +1367,13 @@
mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
}
- /**
- * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
- * once it collapses.
- */
- @Nullable private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
-
void onFlyoutTapped() {
- mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+ if (maybeShowStackUserEducation()) {
+ // If we're showing user education, don't open the bubble show the education first
+ mBubbleToExpandAfterFlyoutCollapse = null;
+ } else {
+ mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+ }
mFlyout.removeCallbacks(mHideFlyout);
mHideFlyout.run();
@@ -1221,6 +1396,8 @@
mFlyout.removeCallbacks(mHideFlyout);
animateFlyoutCollapsed(shouldDismiss, velX);
+
+ maybeShowStackUserEducation();
}
/**
@@ -1432,6 +1609,7 @@
if (flyoutMessage == null
|| flyoutMessage.message == null
|| !bubble.showFlyout()
+ || (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE)
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
@@ -1517,7 +1695,12 @@
@Override
public void getBoundsOnScreen(Rect outRect) {
- // If the bubble menu is open, the entire screen should capture touch events.
+ if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
+ // When user education shows then capture all touches
+ outRect.set(0, 0, getWidth(), getHeight());
+ return;
+ }
+
if (!mIsExpanded) {
if (getBubbleCount() > 0) {
mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
@@ -1704,6 +1887,18 @@
return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
}
+ /** Whether the educational view should appear for bubbles. **/
+ private boolean shouldShowBubblesEducation() {
+ return BubbleDebugConfig.forceShowUserEducation(getContext())
+ || !Prefs.getBoolean(getContext(), HAS_SEEN_BUBBLES_EDUCATION, false);
+ }
+
+ /** Whether the educational view should appear for the expanded view "manage" button. **/
+ private boolean shouldShowManageEducation() {
+ return BubbleDebugConfig.forceShowUserEducation(getContext())
+ || !Prefs.getBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false);
+ }
+
/** For debugging only */
List<Bubble> getBubblesOnScreen() {
List<Bubble> bubbles = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 5e3e747..46d1e0d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -24,7 +24,6 @@
import android.view.ViewConfiguration;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
/**
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
@@ -92,6 +91,7 @@
// anything, collapse the stack.
if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
mBubbleData.setExpanded(false);
+ mStack.hideStackUserEducation(false /* fromExpansion */);
resetForNextGesture();
return false;
}
@@ -102,6 +102,7 @@
// Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
// of expanded view).
+ mStack.maybeShowManageEducation(false);
resetForNextGesture();
return false;
}
@@ -217,9 +218,8 @@
}
} else if (mTouchedView == mStack.getExpandedBubbleView()) {
mBubbleData.setExpanded(false);
- } else if (isStack || isFlyout) {
- // Toggle expansion
- mBubbleData.setExpanded(!mBubbleData.isExpanded());
+ } else if (isStack) {
+ mStack.onStackTapped();
} else {
final String key = ((BadgedImageView) mTouchedView).getKey();
if (key == BubbleOverflow.KEY) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 245d4af..f22c8fa 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -879,9 +879,10 @@
}
/** Moves the stack to a position instantly, with no animation. */
- private void setStackPosition(PointF pos) {
+ public void setStackPosition(PointF pos) {
Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
mStackPosition.set(pos.x, pos.y);
+ mRestingStackPosition = mStackPosition;
// If we're not the active controller, we don't want to physically move the bubble views.
if (isActiveController()) {
@@ -902,10 +903,10 @@
}
}
- /** Returns the default stack position, which is on the top right. */
- private PointF getDefaultStartPosition() {
+ /** Returns the default stack position, which is on the top left. */
+ public PointF getDefaultStartPosition() {
return new PointF(
- getAllowableStackPositionRegion().right,
+ getAllowableStackPositionRegion().left,
getAllowableStackPositionRegion().top + mStackStartingVerticalOffset);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index bab7840..a5258fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -325,8 +325,9 @@
private void bindIcon() {
ImageView image = findViewById(R.id.conversation_icon);
if (mShortcutInfo != null) {
- image.setImageBitmap(mIconFactory.getConversationBitmap(
- mShortcutInfo, mPackageName, mAppUid));
+ image.setImageDrawable(mIconFactory.getConversationDrawable(
+ mShortcutInfo, mPackageName, mAppUid,
+ mNotificationChannel.isImportantConversation()));
} else {
if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
// TODO: maybe use a generic group icon, or a composite of recent senders
@@ -480,6 +481,9 @@
mContext.getString(R.string.notification_conversation_mute));
mute.setImageResource(R.drawable.ic_notifications_silence);
}
+
+ // update icon in case importance has changed
+ bindIcon();
}
private void updateChannel() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index c01f6c4..d746822 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -390,7 +390,7 @@
};
}
ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext,
- launcherApps, pmUser, IconDrawableFactory.newInstance(mContext),
+ launcherApps, pmUser, IconDrawableFactory.newInstance(mContext, false),
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_guts_conversation_icon_size));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 9003de1..d2d76c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
@@ -157,6 +158,7 @@
ServiceState mLastServiceState;
private boolean mUserSetup;
private boolean mSimDetected;
+ private boolean mForceCellularValidated;
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -284,12 +286,41 @@
mPhoneStateListener = new PhoneStateListener(mReceiverHandler::post) {
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
+ // For data switching from A to B, we assume B is validated for up to 2 seconds iff:
+ // 1) A and B are in the same subscription group e.g. CBRS data switch. And
+ // 2) A was validated before the switch.
+ // This is to provide smooth transition for UI without showing cross during data
+ // switch.
+ if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
+ if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
+ mForceCellularValidated = true;
+ mReceiverHandler.removeCallbacks(mClearForceValidated);
+ mReceiverHandler.postDelayed(mClearForceValidated, 2000);
+ }
mActiveMobileDataSubscription = subId;
doUpdateMobileControllers();
}
};
}
+ private final Runnable mClearForceValidated = () -> {
+ if (DEBUG) Log.d(TAG, ": mClearForceValidated");
+ mForceCellularValidated = false;
+ updateConnectivity();
+ };
+
+ boolean isInGroupDataSwitch(int subId1, int subId2) {
+ SubscriptionInfo info1 = mSubscriptionManager.getActiveSubscriptionInfo(subId1);
+ SubscriptionInfo info2 = mSubscriptionManager.getActiveSubscriptionInfo(subId2);
+ return (info1 != null && info2 != null && info1.getGroupUuid() != null
+ && info1.getGroupUuid().equals(info2.getGroupUuid()));
+ }
+
+ boolean keepCellularValidationBitInSwitch(int sourceSubId, int destSubId) {
+ return mValidatedTransports.get(TRANSPORT_CELLULAR)
+ && isInGroupDataSwitch(sourceSubId, destSubId);
+ }
+
public DataSaverController getDataSaverController() {
return mDataSaverController;
}
@@ -793,6 +824,8 @@
}
}
+ if (mForceCellularValidated) mValidatedTransports.set(TRANSPORT_CELLULAR);
+
if (CHATTY) {
Log.d(TAG, "updateConnectivity: mConnectedTransports=" + mConnectedTransports);
Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 138ea39..6c12c76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -30,6 +30,7 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
@@ -53,8 +54,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.provider.Settings;
@@ -117,7 +117,7 @@
@Mock
private ShortcutInfo mShortcutInfo;
@Mock
- private Bitmap mImage;
+ private Drawable mIconDrawable;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -183,8 +183,9 @@
when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
- when(mIconFactory.getConversationBitmap(any(ShortcutInfo.class), anyString(), anyInt()))
- .thenReturn(mImage);
+ when(mIconFactory.getConversationDrawable(
+ any(ShortcutInfo.class), anyString(), anyInt(), anyBoolean()))
+ .thenReturn(mIconDrawable);
mNotificationChannel = new NotificationChannel(
TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
@@ -233,7 +234,7 @@
mIconFactory,
true);
final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
- assertEquals(mImage, ((BitmapDrawable) view.getDrawable()).getBitmap());
+ assertEquals(mIconDrawable, view.getDrawable());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index ac40808..48a3b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -77,6 +77,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -89,6 +90,7 @@
* Functional tests for notification inflation from {@link NotificationEntryManager}.
*/
@SmallTest
+@Ignore("Flaking")
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NotificationEntryManagerInflationTest extends SysuiTestCase {
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
index 94ef11c..96a4d20 100644
--- a/packages/Tethering/apex/Android.bp
+++ b/packages/Tethering/apex/Android.bp
@@ -16,6 +16,7 @@
apex {
name: "com.android.tethering",
+ updatable: true,
java_libs: ["framework-tethering"],
apps: ["Tethering"],
manifest: "manifest.json",
diff --git a/packages/overlays/IconShapeFlowerOverlay/Android.mk b/packages/overlays/IconShapeFlowerOverlay/Android.mk
new file mode 100644
index 0000000..d410bb7
--- /dev/null
+++ b/packages/overlays/IconShapeFlowerOverlay/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright 2020, 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := IconShapeFlower
+
+LOCAL_PRODUCT_MODULE := true
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := IconShapeFlowerOverlay
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
diff --git a/packages/overlays/IconShapeFlowerOverlay/AndroidManifest.xml b/packages/overlays/IconShapeFlowerOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..9d20c6b
--- /dev/null
+++ b/packages/overlays/IconShapeFlowerOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.theme.icon.flower"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay
+ android:targetPackage="android"
+ android:category="android.theme.customization.adaptive_icon_shape"
+ android:priority="1"/>
+
+ <application android:label="@string/icon_shape_flower_overlay" android:hasCode="false"/>
+</manifest>
diff --git a/packages/overlays/IconShapeFlowerOverlay/res/values/config.xml b/packages/overlays/IconShapeFlowerOverlay/res/values/config.xml
new file mode 100644
index 0000000..73f4f21
--- /dev/null
+++ b/packages/overlays/IconShapeFlowerOverlay/res/values/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. -->
+ <string name="config_icon_mask" translatable="false">"M50,0 C60.6,0 69.9,5.3 75.6,13.5 78.5,17.8 82.3,21.5 86.6,24.5 94.7,30.1 100,39.4 100,50 100,60.6 94.7,69.9 86.5,75.6 82.2,78.5 78.5,82.3 75.5,86.6 69.9,94.7 60.6,100 50,100 39.4,100 30.1,94.7 24.4,86.5 21.5,82.2 17.7,78.5 13.4,75.5 5.3,69.9 0,60.6 0,50 0,39.4 5.3,30.1 13.5,24.4 17.8,21.5 21.5,17.7 24.5,13.4 30.1,5.3 39.4,0 50,0 Z"</string>
+ <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
+ <bool name="config_useRoundIcon">false</bool>
+ <!-- Corner radius of system dialogs -->
+ <dimen name="config_dialogCornerRadius">8dp</dimen>
+ <!-- Corner radius for bottom sheet system dialogs -->
+ <dimen name="config_bottomDialogCornerRadius">16dp</dimen>
+
+</resources>
diff --git a/packages/overlays/IconShapeFlowerOverlay/res/values/strings.xml b/packages/overlays/IconShapeFlowerOverlay/res/values/strings.xml
new file mode 100644
index 0000000..47c1479
--- /dev/null
+++ b/packages/overlays/IconShapeFlowerOverlay/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Flower icon overlay -->
+ <string name="icon_shape_flower_overlay" translatable="false">Flower</string>
+
+</resources>
diff --git a/packages/overlays/IconShapeHexagonOverlay/Android.mk b/packages/overlays/IconShapeHexagonOverlay/Android.mk
new file mode 100644
index 0000000..16ef399
--- /dev/null
+++ b/packages/overlays/IconShapeHexagonOverlay/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright 2020, 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := IconShapeHexagon
+
+LOCAL_PRODUCT_MODULE := true
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := IconShapeHexagonOverlay
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_RRO_PACKAGE)
diff --git a/packages/overlays/IconShapeHexagonOverlay/AndroidManifest.xml b/packages/overlays/IconShapeHexagonOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..bf408fd
--- /dev/null
+++ b/packages/overlays/IconShapeHexagonOverlay/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.theme.icon.hexagon"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay
+ android:targetPackage="android"
+ android:category="android.theme.customization.adaptive_icon_shape"
+ android:priority="1"/>
+
+ <application android:label="@string/icon_shape_hexagon_overlay" android:hasCode="false"/>
+</manifest>
diff --git a/packages/overlays/IconShapeHexagonOverlay/res/values/config.xml b/packages/overlays/IconShapeHexagonOverlay/res/values/config.xml
new file mode 100644
index 0000000..f7cb595
--- /dev/null
+++ b/packages/overlays/IconShapeHexagonOverlay/res/values/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. -->
+ <string name="config_icon_mask" translatable="false">"M12,0 88,0 100,50 88,100 12,100 0,50 12,0 Z"</string>
+ <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
+ <bool name="config_useRoundIcon">false</bool>
+ <!-- Corner radius of system dialogs -->
+ <dimen name="config_dialogCornerRadius">0dp</dimen>
+ <!-- Corner radius for bottom sheet system dialogs -->
+ <dimen name="config_bottomDialogCornerRadius">0dp</dimen>
+
+</resources>
diff --git a/packages/overlays/IconShapeHexagonOverlay/res/values/strings.xml b/packages/overlays/IconShapeHexagonOverlay/res/values/strings.xml
new file mode 100644
index 0000000..e00dc9d
--- /dev/null
+++ b/packages/overlays/IconShapeHexagonOverlay/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Hexagon icon overlay -->
+ <string name="icon_shape_hexagon_overlay" translatable="false">Hexagon</string>
+
+</resources>
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 461af34..cfc8b45 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -24,6 +24,7 @@
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
@@ -2937,8 +2938,10 @@
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null && !permissionsReviewRequired) {
+ // TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service
+ // was initiated from a notification tap or not.
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
- hostingRecord, false, isolated, false)) == null) {
+ hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 52c8adb..fbcb010 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -65,6 +65,10 @@
import static android.os.Process.SIGNAL_USR1;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_BATCH_LAUNCH;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS;
import static android.os.Process.ZYGOTE_PROCESS;
import static android.os.Process.getTotalMemory;
import static android.os.Process.isThreadInProcess;
@@ -3054,7 +3058,7 @@
info.targetSdkVersion = Build.VERSION.SDK_INT;
ProcessRecord proc = mProcessList.startProcessLocked(processName, info /* info */,
false /* knownToBeDead */, 0 /* intentFlags */,
- sNullHostingRecord /* hostingRecord */,
+ sNullHostingRecord /* hostingRecord */, ZYGOTE_POLICY_FLAG_EMPTY,
true /* allowWhileBooting */, true /* isolated */,
uid, true /* keepIfLarge */, abiOverride, entryPoint, entryPointArgs,
crashHandler);
@@ -3065,12 +3069,12 @@
@GuardedBy("this")
final ProcessRecord startProcessLocked(String processName,
ApplicationInfo info, boolean knownToBeDead, int intentFlags,
- HostingRecord hostingRecord, boolean allowWhileBooting,
+ HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting,
boolean isolated, boolean keepIfLarge) {
return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
- hostingRecord, allowWhileBooting, isolated, 0 /* isolatedUid */, keepIfLarge,
- null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */,
- null /* crashHandler */);
+ hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
+ keepIfLarge, null /* ABI override */, null /* entryPoint */,
+ null /* entryPointArgs */, null /* crashHandler */);
}
boolean isAllowedWhileBooting(ApplicationInfo ai) {
@@ -4953,7 +4957,8 @@
} catch (RemoteException e) {
app.resetPackageList(mProcessStats);
mProcessList.startProcessLocked(app,
- new HostingRecord("link fail", processName));
+ new HostingRecord("link fail", processName),
+ ZYGOTE_POLICY_FLAG_EMPTY);
return false;
}
@@ -5372,7 +5377,9 @@
for (int ip=0; ip<NP; ip++) {
if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "Starting process on hold: "
+ procs.get(ip));
- mProcessList.startProcessLocked(procs.get(ip), new HostingRecord("on-hold"));
+ mProcessList.startProcessLocked(procs.get(ip),
+ new HostingRecord("on-hold"),
+ ZYGOTE_POLICY_FLAG_BATCH_LAUNCH);
}
}
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -7224,8 +7231,9 @@
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0,
new HostingRecord("content provider",
- new ComponentName(cpi.applicationInfo.packageName,
- cpi.name)), false, false, false);
+ new ComponentName(cpi.applicationInfo.packageName,
+ cpi.name)),
+ ZYGOTE_POLICY_FLAG_EMPTY, false, false, false);
checkTime(startTime, "getContentProviderImpl: after start process");
if (proc == null) {
Slog.w(TAG, "Unable to launch app "
@@ -7259,10 +7267,8 @@
}
checkTime(startTime, "getContentProviderImpl: done!");
- grantImplicitAccess(userId, null /*intent*/,
- UserHandle.getAppId(Binder.getCallingUid()),
- UserHandle.getAppId(cpi.applicationInfo.uid)
- );
+ grantImplicitAccess(userId, null /*intent*/, callingUid,
+ UserHandle.getAppId(cpi.applicationInfo.uid));
}
// Wait for the provider to be published...
@@ -7785,7 +7791,8 @@
.getPersistentApplications(STOCK_PM_FLAGS | matchFlags).getList();
for (ApplicationInfo app : apps) {
if (!"android".equals(app.packageName)) {
- addAppLocked(app, null, false, null /* ABI override */);
+ addAppLocked(app, null, false, null /* ABI override */,
+ ZYGOTE_POLICY_FLAG_BATCH_LAUNCH);
}
}
} catch (RemoteException ex) {
@@ -8056,23 +8063,25 @@
@GuardedBy("this")
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
- String abiOverride) {
+ String abiOverride, int zygotePolicyFlags) {
return addAppLocked(info, customProcess, isolated, false /* disableHiddenApiChecks */,
- false /* mountExtStorageFull */, abiOverride);
+ false /* mountExtStorageFull */, abiOverride, zygotePolicyFlags);
}
@GuardedBy("this")
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
- boolean disableHiddenApiChecks, boolean mountExtStorageFull, String abiOverride) {
+ boolean disableHiddenApiChecks, boolean mountExtStorageFull, String abiOverride,
+ int zygotePolicyFlags) {
return addAppLocked(info, customProcess, isolated, disableHiddenApiChecks,
- false /* disableTestApiChecks */, mountExtStorageFull, abiOverride);
+ false /* disableTestApiChecks */, mountExtStorageFull, abiOverride,
+ zygotePolicyFlags);
}
// TODO: Move to ProcessList?
@GuardedBy("this")
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
boolean disableHiddenApiChecks, boolean disableTestApiChecks,
- boolean mountExtStorageFull, String abiOverride) {
+ boolean mountExtStorageFull, String abiOverride, int zygotePolicyFlags) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@@ -8107,7 +8116,8 @@
mPersistentStartingProcesses.add(app);
mProcessList.startProcessLocked(app, new HostingRecord("added application",
customProcess != null ? customProcess : app.processName),
- disableHiddenApiChecks, disableTestApiChecks, mountExtStorageFull, abiOverride);
+ zygotePolicyFlags, disableHiddenApiChecks, disableTestApiChecks,
+ mountExtStorageFull, abiOverride);
}
return app;
@@ -14614,7 +14624,8 @@
mProcessList.addProcessNameLocked(app);
app.pendingStart = false;
mProcessList.startProcessLocked(app,
- new HostingRecord("restart", app.processName));
+ new HostingRecord("restart", app.processName),
+ ZYGOTE_POLICY_FLAG_EMPTY);
return true;
} else if (app.pid > 0 && app.pid != MY_PID) {
// Goodbye!
@@ -14977,7 +14988,7 @@
ProcessRecord proc = startProcessLocked(app.processName, app,
false, 0,
new HostingRecord("backup", hostingName),
- false, false, false);
+ ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false, false);
if (proc == null) {
Slog.e(TAG, "Unable to start backup agent process " + r);
return false;
@@ -16616,7 +16627,8 @@
}
ProcessRecord app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks,
- disableTestApiChecks, mountExtStorageFull, abiOverride);
+ disableTestApiChecks, mountExtStorageFull, abiOverride,
+ ZYGOTE_POLICY_FLAG_EMPTY);
app.setActiveInstrumentation(activeInstr);
activeInstr.mFinished = false;
activeInstr.mSourceUid = callingUid;
@@ -18013,7 +18025,8 @@
mProcessList.mRemovedProcesses.remove(i);
if (app.isPersistent()) {
- addAppLocked(app.info, null, false, null /* ABI override */);
+ addAppLocked(app.info, null, false, null /* ABI override */,
+ ZYGOTE_POLICY_FLAG_BATCH_LAUNCH);
}
}
}
@@ -19220,8 +19233,8 @@
// preempted by other processes before attaching the process of top app.
startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
new HostingRecord(hostingType, hostingName, isTop),
- false /* allowWhileBooting */, false /* isolated */,
- true /* keepIfLarge */);
+ ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,
+ false /* isolated */, true /* keepIfLarge */);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 26ef707..3aec53a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+
import static com.android.server.am.ActivityManagerDebugConfig.*;
import android.app.ActivityManager;
@@ -1593,7 +1596,9 @@
+ receiverUid);
}
- if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
+ final boolean isActivityCapable =
+ (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0);
+ if (isActivityCapable) {
scheduleTempWhitelistLocked(receiverUid,
brOptions.getTemporaryAppWhitelistDuration(), r);
}
@@ -1648,6 +1653,7 @@
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord("broadcast", r.curComponent),
+ isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
== null) {
// Ah, this recipient is unavailable. Finish it if necessary,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 22559c4..f2bc1fe 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -25,6 +25,7 @@
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.getFreeMemory;
import static android.os.Process.getTotalMemory;
import static android.os.Process.killProcessQuiet;
@@ -80,13 +81,11 @@
import android.os.DropBoxManager;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IVold;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -1640,7 +1639,7 @@
*/
@GuardedBy("mService")
boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
- boolean disableHiddenApiChecks, boolean disableTestApiChecks,
+ int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
boolean mountExtStorageFull, String abiOverride) {
if (app.pendingStart) {
return true;
@@ -1733,8 +1732,7 @@
}
// Run the app in safe mode if its manifest requests so or the
// system is booted in safe mode.
- if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 ||
- mService.mSafeMode == true) {
+ if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 || mService.mSafeMode) {
runtimeFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
}
if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL) != 0) {
@@ -1846,8 +1844,8 @@
final String entryPoint = "android.app.ActivityThread";
return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,
- runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet, invokeWith,
- startTime);
+ runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi,
+ instructionSet, invokeWith, startTime);
} catch (RuntimeException e) {
Slog.e(ActivityManagerService.TAG, "Failure starting process " + app.processName, e);
@@ -1864,9 +1862,8 @@
}
@GuardedBy("mService")
- boolean startProcessLocked(HostingRecord hostingRecord,
- String entryPoint,
- ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
+ boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
+ int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
long startTime) {
app.pendingStart = true;
@@ -1895,15 +1892,15 @@
if (DEBUG_PROCESSES) Slog.i(TAG_PROCESSES,
"Posting procStart msg for " + app.toShortString());
mService.mProcStartHandler.post(() -> handleProcessStart(
- app, entryPoint, gids, runtimeFlags, mountExternal, requiredAbi,
- instructionSet, invokeWith, startSeq));
+ app, entryPoint, gids, runtimeFlags, zygotePolicyFlags, mountExternal,
+ requiredAbi, instructionSet, invokeWith, startSeq));
return true;
} else {
try {
final Process.ProcessStartResult startResult = startProcess(hostingRecord,
entryPoint, app,
- uid, gids, runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet,
- invokeWith, startTime);
+ uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,
+ requiredAbi, instructionSet, invokeWith, startTime);
handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
startSeq, false);
} catch (RuntimeException e) {
@@ -1923,8 +1920,8 @@
* <p>Note: this function doesn't hold the global AM lock intentionally.</p>
*/
private void handleProcessStart(final ProcessRecord app, final String entryPoint,
- final int[] gids, final int runtimeFlags, final int mountExternal,
- final String requiredAbi, final String instructionSet,
+ final int[] gids, final int runtimeFlags, int zygotePolicyFlags,
+ final int mountExternal, final String requiredAbi, final String instructionSet,
final String invokeWith, final long startSeq) {
// If there is a precede instance of the process, wait for its death with a timeout.
// Use local reference since we are not using locks here
@@ -1959,8 +1956,10 @@
}
try {
final Process.ProcessStartResult startResult = startProcess(app.hostingRecord,
- entryPoint, app, app.startUid, gids, runtimeFlags, mountExternal,
- app.seInfo, requiredAbi, instructionSet, invokeWith, app.startTime);
+ entryPoint, app, app.startUid, gids, runtimeFlags, zygotePolicyFlags,
+ mountExternal, app.seInfo, requiredAbi, instructionSet, invokeWith,
+ app.startTime);
+
synchronized (mService) {
handleProcessStartedLocked(app, startResult, startSeq);
}
@@ -2113,9 +2112,9 @@
}
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
- ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
- String seInfo, String requiredAbi, String instructionSet, String invokeWith,
- long startTime) {
+ ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags,
+ int mountExternal, String seInfo, String requiredAbi, String instructionSet,
+ String invokeWith, long startTime) {
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
app.processName);
@@ -2163,14 +2162,15 @@
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
- /*useUsapPool=*/ false, isTopApp, app.mDisabledCompatChanges,
- pkgDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+ /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
+ app.mDisabledCompatChanges, pkgDataInfoMap,
+ new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
- app.info.dataDir, invokeWith, app.info.packageName, isTopApp,
- app.mDisabledCompatChanges, pkgDataInfoMap,
+ app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
+ isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
@@ -2181,14 +2181,14 @@
}
@GuardedBy("mService")
- final void startProcessLocked(ProcessRecord app, HostingRecord hostingRecord) {
- startProcessLocked(app, hostingRecord, null /* abiOverride */);
+ void startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, int zygotePolicyFlags) {
+ startProcessLocked(app, hostingRecord, zygotePolicyFlags, null /* abiOverride */);
}
@GuardedBy("mService")
final boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
- String abiOverride) {
- return startProcessLocked(app, hostingRecord,
+ int zygotePolicyFlags, String abiOverride) {
+ return startProcessLocked(app, hostingRecord, zygotePolicyFlags,
false /* disableHiddenApiChecks */, false /* disableTestApiChecks */,
false /* mountExtStorageFull */, abiOverride);
}
@@ -2196,8 +2196,9 @@
@GuardedBy("mService")
final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
- boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
- String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
+ int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
+ boolean keepIfLarge, String abiOverride, String entryPoint, String[] entryPointArgs,
+ Runnable crashHandler) {
long startTime = SystemClock.uptimeMillis();
ProcessRecord app;
if (!isolated) {
@@ -2308,7 +2309,8 @@
}
checkSlow(startTime, "startProcess: stepping in to startProcess");
- final boolean success = startProcessLocked(app, hostingRecord, abiOverride);
+ final boolean success =
+ startProcessLocked(app, hostingRecord, zygotePolicyFlags, abiOverride);
checkSlow(startTime, "startProcess: done starting proc!");
return success ? app : null;
}
@@ -2631,7 +2633,8 @@
mService.handleAppDiedLocked(app, willRestart, allowRestart);
if (willRestart) {
removeLruProcessLocked(app);
- mService.addAppLocked(app.info, null, false, null /* ABI override */);
+ mService.addAppLocked(app.info, null, false, null /* ABI override */,
+ ZYGOTE_POLICY_FLAG_EMPTY);
}
} else {
mRemovedProcesses.add(app);
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 3f8f6bf..e426574 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -436,8 +436,7 @@
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
// Initialize PROC_STATS
- // TODO (b/148402814): Change this directory to stats_pull.
- mBaseDir = new File(SystemServiceManager.ensureSystemDir(), "stats_companion");
+ mBaseDir = new File(SystemServiceManager.ensureSystemDir(), "stats_pull");
// Disables throttler on CPU time readers.
mCpuUidUserSysTimeReader = new KernelCpuUidUserSysTimeReader(false);
diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
index a8cf9f6..115b5c8 100644
--- a/services/core/java/com/android/server/utils/quota/QuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
@@ -552,6 +552,7 @@
mTriggerTimeElapsed = nextTriggerTimeElapsed;
}
} else {
+ cancelAlarm(this);
mTriggerTimeElapsed = 0;
}
}
diff --git a/tests/BootImageProfileTest/DISABLED_TEST_MAPPING b/tests/BootImageProfileTest/TEST_MAPPING
similarity index 100%
rename from tests/BootImageProfileTest/DISABLED_TEST_MAPPING
rename to tests/BootImageProfileTest/TEST_MAPPING