Merge "Temporary decouple snapshot controller from transition." into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index 69fe85e..430a1e2 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -35,6 +35,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Date;
/**
@@ -264,7 +265,7 @@
return sb.toString();
}
- private static String policyIndexToString(int index) {
+ static String policyIndexToString(int index) {
switch (index) {
case REQUESTER_POLICY_INDEX:
return "requester";
@@ -400,4 +401,32 @@
proto.end(token);
}
+
+ /**
+ * Stores a snapshot of an alarm at any given time to be used for logging and diagnostics.
+ * This should intentionally avoid holding pointers to objects like {@link Alarm#operation}.
+ */
+ static class Snapshot {
+ final int mType;
+ final String mTag;
+ final long[] mPolicyWhenElapsed;
+
+ Snapshot(Alarm a) {
+ mType = a.type;
+ mTag = a.statsTag;
+ mPolicyWhenElapsed = Arrays.copyOf(a.mPolicyWhenElapsed, NUM_POLICIES);
+ }
+
+ void dump(IndentingPrintWriter pw, long nowElapsed) {
+ pw.print("type", typeToString(mType));
+ pw.print("tag", mTag);
+ pw.println();
+ pw.print("policyWhenElapsed:");
+ for (int i = 0; i < NUM_POLICIES; i++) {
+ pw.print(" " + policyIndexToString(i) + "=");
+ TimeUtils.formatDuration(mPolicyWhenElapsed[i], nowElapsed, pw);
+ }
+ pw.println();
+ }
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index c76a43f..3772960 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -618,13 +618,13 @@
static final int REMOVE_REASON_LISTENER_BINDER_DIED = 5;
static final int REMOVE_REASON_LISTENER_CACHED = 6;
- final String mTag;
+ final Alarm.Snapshot mAlarmSnapshot;
final long mWhenRemovedElapsed;
final long mWhenRemovedRtc;
final int mRemoveReason;
RemovedAlarm(Alarm a, int removeReason, long nowRtc, long nowElapsed) {
- mTag = a.statsTag;
+ mAlarmSnapshot = new Alarm.Snapshot(a);
mRemoveReason = removeReason;
mWhenRemovedRtc = nowRtc;
mWhenRemovedElapsed = nowElapsed;
@@ -656,13 +656,21 @@
}
void dump(IndentingPrintWriter pw, long nowElapsed, SimpleDateFormat sdf) {
- pw.print("[tag", mTag);
- pw.print("reason", removeReasonToString(mRemoveReason));
+ pw.increaseIndent();
+
+ pw.print("Reason", removeReasonToString(mRemoveReason));
pw.print("elapsed=");
TimeUtils.formatDuration(mWhenRemovedElapsed, nowElapsed, pw);
pw.print(" rtc=");
pw.print(sdf.format(new Date(mWhenRemovedRtc)));
- pw.println("]");
+ pw.println();
+
+ pw.println("Snapshot:");
+ pw.increaseIndent();
+ mAlarmSnapshot.dump(pw, nowElapsed);
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
}
}
@@ -3088,7 +3096,9 @@
+ " does not belong to the calling uid " + callingUid);
}
synchronized (mLock) {
- removeLocked(callingPackage, REMOVE_REASON_ALARM_CANCELLED);
+ removeAlarmsInternalLocked(
+ a -> (a.matches(callingPackage) && a.creatorUid == callingUid),
+ REMOVE_REASON_ALARM_CANCELLED);
}
}
@@ -3503,15 +3513,16 @@
}
if (mRemovalHistory.size() > 0) {
- pw.println("Removal history: ");
+ pw.println("Removal history:");
pw.increaseIndent();
for (int i = 0; i < mRemovalHistory.size(); i++) {
UserHandle.formatUid(pw, mRemovalHistory.keyAt(i));
pw.println(":");
pw.increaseIndent();
final RemovedAlarm[] historyForUid = mRemovalHistory.valueAt(i).toArray();
- for (final RemovedAlarm removedAlarm : historyForUid) {
- removedAlarm.dump(pw, nowELAPSED, sdf);
+ for (int index = historyForUid.length - 1; index >= 0; index--) {
+ pw.print("#" + (historyForUid.length - index) + ": ");
+ historyForUid[index].dump(pw, nowELAPSED, sdf);
}
pw.decreaseIndent();
}
@@ -4285,10 +4296,25 @@
}
}
- boolean lookForPackageLocked(String packageName) {
- final ArrayList<Alarm> allAlarms = mAlarmStore.asList();
- for (final Alarm alarm : allAlarms) {
- if (alarm.matches(packageName)) {
+ @GuardedBy("mLock")
+ boolean lookForPackageLocked(String packageName, int uid) {
+ // This is called extremely rarely, e.g. when the user opens the force-stop page in settings
+ // so the loops using an iterator should be fine.
+ for (final Alarm alarm : mAlarmStore.asList()) {
+ if (alarm.matches(packageName) && alarm.creatorUid == uid) {
+ return true;
+ }
+ }
+ final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.get(uid);
+ if (alarmsForUid != null) {
+ for (final Alarm alarm : alarmsForUid) {
+ if (alarm.matches(packageName)) {
+ return true;
+ }
+ }
+ }
+ for (final Alarm alarm : mPendingNonWakeupAlarms) {
+ if (alarm.matches(packageName) && alarm.creatorUid == uid) {
return true;
}
}
@@ -5269,7 +5295,7 @@
case Intent.ACTION_QUERY_PACKAGE_RESTART:
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
for (String packageName : pkgList) {
- if (lookForPackageLocked(packageName)) {
+ if (lookForPackageLocked(packageName, uid)) {
setResultCode(Activity.RESULT_OK);
return;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 89eb1a9..4477e94 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1259,10 +1259,14 @@
final BackgroundStartPrivileges bsp =
activityManagerInternal.getBackgroundStartPrivileges(uid);
- final boolean balAllowed = bsp.allowsBackgroundActivityStarts();
if (DEBUG) {
- Slog.d(TAG, "Job " + job.toShortString() + " bal state: " + bsp);
+ Slog.d(TAG, "Job " + job.toShortString() + " bsp state: " + bsp);
}
+ // Intentionally use the background activity start BSP here instead of
+ // the full BAL check since the former is transient and better indicates that the
+ // user recently interacted with the app, while the latter includes
+ // permanent exceptions that don't warrant bypassing normal concurrency policy.
+ final boolean balAllowed = bsp.allowsBackgroundActivityStarts();
cachedPrivilegedState.put(uid,
balAllowed ? PRIVILEGED_STATE_BAL : PRIVILEGED_STATE_NONE);
return balAllowed;
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 08810b5..ea14640 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -31,7 +31,6 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
-import android.app.BackgroundStartPrivileges;
import android.app.IUidObserver;
import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
@@ -1095,6 +1094,10 @@
synchronized (mLock) {
mUidToPackageCache.remove(uid);
}
+ } else {
+ synchronized (mJobSchedulerStub.mPersistCache) {
+ mJobSchedulerStub.mPersistCache.remove(pkgUid);
+ }
}
} else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
if (DEBUG) {
@@ -1809,7 +1812,9 @@
private boolean cancelJobsForUid(int uid, boolean includeSourceApp,
boolean namespaceOnly, @Nullable String namespace,
@JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
- if (uid == Process.SYSTEM_UID) {
+ // Non-null system namespace means the cancelling is limited to the namespace
+ // and won't cause issues for the system at large.
+ if (uid == Process.SYSTEM_UID && (!namespaceOnly || namespace == null)) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
return false;
}
@@ -3782,7 +3787,8 @@
return canPersist;
}
- private int validateJob(@NonNull JobInfo job, int callingUid, int sourceUserId,
+ private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid,
+ int sourceUserId,
@Nullable String sourcePkgName, @Nullable JobWorkItem jobWorkItem) {
final boolean rejectNegativeNetworkEstimates = CompatChanges.isChangeEnabled(
JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES, callingUid);
@@ -3815,6 +3821,8 @@
}
// We aim to check the permission of both the source and calling app so that apps
// don't attempt to bypass the permission by using other apps to do the work.
+ boolean isInStateToScheduleUiJobSource = false;
+ final String callingPkgName = job.getService().getPackageName();
if (sourceUid != -1) {
// Check the permission of the source app.
final int sourceResult =
@@ -3822,8 +3830,13 @@
if (sourceResult != JobScheduler.RESULT_SUCCESS) {
return sourceResult;
}
+ final int sourcePid =
+ callingUid == sourceUid && callingPkgName.equals(sourcePkgName)
+ ? callingPid : -1;
+ isInStateToScheduleUiJobSource = isInStateToScheduleUserInitiatedJobs(
+ sourceUid, sourcePid, sourcePkgName);
}
- final String callingPkgName = job.getService().getPackageName();
+ boolean isInStateToScheduleUiJobCalling = false;
if (callingUid != sourceUid || !callingPkgName.equals(sourcePkgName)) {
// Source app is different from calling app. Make sure the calling app also has
// the permission.
@@ -3832,25 +3845,17 @@
if (callingResult != JobScheduler.RESULT_SUCCESS) {
return callingResult;
}
+ // Avoid rechecking the state if the source app is able to schedule the job.
+ if (!isInStateToScheduleUiJobSource) {
+ isInStateToScheduleUiJobCalling = isInStateToScheduleUserInitiatedJobs(
+ callingUid, callingPid, callingPkgName);
+ }
}
- final int uid = sourceUid != -1 ? sourceUid : callingUid;
- final int procState = mActivityManagerInternal.getUidProcessState(uid);
- if (DEBUG) {
- Slog.d(TAG, "Uid " + uid + " proc state="
- + ActivityManager.procStateToString(procState));
- }
- if (procState != ActivityManager.PROCESS_STATE_TOP) {
- final BackgroundStartPrivileges bsp =
- mActivityManagerInternal.getBackgroundStartPrivileges(uid);
- if (DEBUG) {
- Slog.d(TAG, "Uid " + uid + ": " + bsp);
- }
- if (!bsp.allowsBackgroundActivityStarts()) {
- Slog.e(TAG,
- "Uid " + uid + " not in a state to schedule user-initiated jobs");
- return JobScheduler.RESULT_FAILURE;
- }
+ if (!isInStateToScheduleUiJobSource && !isInStateToScheduleUiJobCalling) {
+ Slog.e(TAG, "Uid(s) " + sourceUid + "/" + callingUid
+ + " not in a state to schedule user-initiated jobs");
+ return JobScheduler.RESULT_FAILURE;
}
}
if (jobWorkItem != null) {
@@ -3896,6 +3901,24 @@
return JobScheduler.RESULT_SUCCESS;
}
+ private boolean isInStateToScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+ final int procState = mActivityManagerInternal.getUidProcessState(uid);
+ if (DEBUG) {
+ Slog.d(TAG, "Uid " + uid + " proc state="
+ + ActivityManager.procStateToString(procState));
+ }
+ if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ return true;
+ }
+ final boolean canScheduleUiJobsInBg =
+ mActivityManagerInternal.canScheduleUserInitiatedJobs(uid, pid, pkgName);
+ if (DEBUG) {
+ Slog.d(TAG, "Uid " + uid
+ + " AM.canScheduleUserInitiatedJobs= " + canScheduleUiJobsInBg);
+ }
+ return canScheduleUiJobsInBg;
+ }
+
// IJobScheduler implementation
@Override
public int schedule(String namespace, JobInfo job) throws RemoteException {
@@ -3908,7 +3931,7 @@
enforceValidJobRequest(uid, pid, job);
- final int result = validateJob(job, uid, -1, null, null);
+ final int result = validateJob(job, uid, pid, -1, null, null);
if (result != JobScheduler.RESULT_SUCCESS) {
return result;
}
@@ -3941,7 +3964,7 @@
throw new NullPointerException("work is null");
}
- final int result = validateJob(job, uid, -1, null, work);
+ final int result = validateJob(job, uid, pid, -1, null, work);
if (result != JobScheduler.RESULT_SUCCESS) {
return result;
}
@@ -3963,6 +3986,7 @@
public int scheduleAsPackage(String namespace, JobInfo job, String packageName, int userId,
String tag) throws RemoteException {
final int callerUid = Binder.getCallingUid();
+ final int callerPid = Binder.getCallingPid();
if (DEBUG) {
Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
+ " on behalf of " + packageName + "/");
@@ -3979,7 +4003,7 @@
+ " not permitted to schedule jobs for other apps");
}
- int result = validateJob(job, callerUid, userId, packageName, null);
+ int result = validateJob(job, callerUid, callerPid, userId, packageName, null);
if (result != JobScheduler.RESULT_SUCCESS) {
return result;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 4c339ac..7388fff 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -465,22 +465,36 @@
mExecutionStartTimeElapsed - job.enqueueTime,
job.getJob().isUserInitiated(),
job.shouldTreatAsUserInitiatedJob());
+ final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ final String componentPackage = job.getServiceComponent().getPackageName();
+ String traceTag = "*job*<" + job.getSourceUid() + ">" + sourcePackage;
+ if (!sourcePackage.equals(componentPackage)) {
+ traceTag += ":" + componentPackage;
+ }
+ traceTag += "/" + job.getServiceComponent().getShortClassName();
+ if (!componentPackage.equals(job.serviceProcessName)) {
+ traceTag += "$" + job.serviceProcessName;
+ }
+ if (job.getNamespace() != null) {
+ traceTag += "@" + job.getNamespace();
+ }
+ traceTag += "#" + job.getJobId();
+
// Use the context's ID to distinguish traces since there'll only be one job
// running per context.
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
- job.getTag(), getId());
+ traceTag, getId());
}
try {
mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
- final String jobPackage = job.getSourcePackageName();
final int jobUserId = job.getSourceUserId();
UsageStatsManagerInternal usageStats =
LocalServices.getService(UsageStatsManagerInternal.class);
- usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
+ usageStats.setLastJobRunTime(sourcePackage, jobUserId, mExecutionStartTimeElapsed);
mAvailable = false;
mStoppedReason = null;
mStoppedTime = 0;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index a6d064c..4d8b3e1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -38,6 +38,7 @@
import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX_CAKES;
@@ -77,6 +78,7 @@
import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE;
import static android.app.tare.EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED;
+import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP;
import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP;
import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT;
import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX;
@@ -145,6 +147,7 @@
};
private long mMinSatiatedBalanceExempted;
+ private long mMinSatiatedBalanceHeadlessSystemApp;
private long mMinSatiatedBalanceOther;
private long mMaxSatiatedBalance;
private long mInitialSatiatedConsumptionLimit;
@@ -179,6 +182,9 @@
if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
+ if (mIrs.isHeadlessSystemApp(pkgName)) {
+ return mMinSatiatedBalanceHeadlessSystemApp;
+ }
// TODO: take other exemptions into account
return mMinSatiatedBalanceOther;
}
@@ -242,9 +248,14 @@
mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
+ mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties,
+ KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
+ mMinSatiatedBalanceOther);
mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
- KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
- mMinSatiatedBalanceOther);
+ KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
+ DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+ mMinSatiatedBalanceHeadlessSystemApp);
mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index ffb2c03..1c915bc 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -205,6 +205,11 @@
@GuardedBy("mLock")
private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>();
+ /** The package name of the wellbeing app. */
+ @GuardedBy("mLock")
+ @Nullable
+ private String mWellbeingPackage;
+
private volatile boolean mHasBattery = true;
@EconomyManager.EnabledMode
private volatile int mEnabledMode;
@@ -496,6 +501,20 @@
}
}
+ boolean isHeadlessSystemApp(@NonNull String pkgName) {
+ if (pkgName == null) {
+ Slog.wtfStack(TAG, "isHeadlessSystemApp called with null package");
+ return false;
+ }
+ synchronized (mLock) {
+ // The wellbeing app is pre-set on the device, not expected to be interacted with
+ // much by the user, but can be expected to do work in the background on behalf of
+ // the user. As such, it's a pseudo-headless system app, so treat it as a headless
+ // system app.
+ return pkgName.equals(mWellbeingPackage);
+ }
+ }
+
boolean isPackageExempted(final int userId, @NonNull String pkgName) {
synchronized (mLock) {
return mExemptedApps.contains(pkgName);
@@ -1097,6 +1116,9 @@
}
synchronized (mLock) {
registerListeners();
+ // As of Android UDC, users can't change the wellbeing package, so load it once
+ // as soon as possible and don't bother trying to update it afterwards.
+ mWellbeingPackage = mPackageManager.getWellbeingPackageName();
mCurrentBatteryLevel = getCurrentBatteryLevel();
// Get the current battery presence, if available. This would succeed if TARE is
// toggled long after boot.
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index c2a6e43..526e876 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -43,6 +43,7 @@
import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES;
@@ -90,6 +91,7 @@
import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
import static android.app.tare.EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
+import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_INSTANT;
@@ -158,6 +160,7 @@
};
private long mMinSatiatedBalanceExempted;
+ private long mMinSatiatedBalanceHeadlessSystemApp;
private long mMinSatiatedBalanceOther;
private long mMinSatiatedBalanceIncrementalAppUpdater;
private long mMaxSatiatedBalance;
@@ -194,6 +197,8 @@
final long baseBalance;
if (mIrs.isPackageExempted(userId, pkgName)) {
baseBalance = mMinSatiatedBalanceExempted;
+ } else if (mIrs.isHeadlessSystemApp(pkgName)) {
+ baseBalance = mMinSatiatedBalanceHeadlessSystemApp;
} else {
baseBalance = mMinSatiatedBalanceOther;
}
@@ -276,9 +281,14 @@
mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
+ mMinSatiatedBalanceHeadlessSystemApp = getConstantAsCake(mParser, properties,
+ KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
+ mMinSatiatedBalanceOther);
mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
- KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
- mMinSatiatedBalanceOther);
+ KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
+ DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+ mMinSatiatedBalanceHeadlessSystemApp);
mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties,
KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
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 ab0a8ad..55e6815 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -489,14 +489,6 @@
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- /**
- * Whether we should allow apps into the
- * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not.
- * If false, any attempts to put an app into the bucket will put the app into the
- * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE} bucket instead.
- */
- private boolean mAllowRestrictedBucket;
-
private volatile boolean mAppIdleEnabled;
private volatile boolean mIsCharging;
private boolean mSystemServicesReady = false;
@@ -1058,13 +1050,6 @@
Slog.d(TAG, "Bringing down to RESTRICTED due to timeout");
}
}
- if (newBucket == STANDBY_BUCKET_RESTRICTED && !mAllowRestrictedBucket) {
- newBucket = STANDBY_BUCKET_RARE;
- // Leave the reason alone.
- if (DEBUG) {
- Slog.d(TAG, "Bringing up from RESTRICTED to RARE due to off switch");
- }
- }
if (newBucket > minBucket) {
newBucket = minBucket;
// Leave the reason alone.
@@ -1689,7 +1674,7 @@
final int reason = (REASON_MAIN_MASK & mainReason) | (REASON_SUB_MASK & restrictReason);
final long nowElapsed = mInjector.elapsedRealtime();
- final int bucket = mAllowRestrictedBucket ? STANDBY_BUCKET_RESTRICTED : STANDBY_BUCKET_RARE;
+ final int bucket = STANDBY_BUCKET_RESTRICTED;
setAppStandbyBucket(packageName, userId, bucket, reason, nowElapsed, false);
}
@@ -1793,9 +1778,6 @@
Slog.e(TAG, "Tried to set bucket of uninstalled app: " + packageName);
return;
}
- if (newBucket == STANDBY_BUCKET_RESTRICTED && !mAllowRestrictedBucket) {
- newBucket = STANDBY_BUCKET_RARE;
- }
AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName,
userId, elapsedRealtime);
boolean predicted = (reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED;
@@ -1921,7 +1903,6 @@
+ " due to min timeout");
}
} else if (newBucket == STANDBY_BUCKET_RARE
- && mAllowRestrictedBucket
&& getBucketForLocked(packageName, userId, elapsedRealtime)
== STANDBY_BUCKET_RESTRICTED) {
// Prediction doesn't think the app will be used anytime soon and
@@ -2529,8 +2510,6 @@
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
- pw.print(" mAllowRestrictedBucket=");
- pw.print(mAllowRestrictedBucket);
pw.print(" mIsCharging=");
pw.print(mIsCharging);
pw.println();
@@ -2711,12 +2690,6 @@
}
}
- boolean isRestrictedBucketEnabled() {
- return Global.getInt(mContext.getContentResolver(),
- Global.ENABLE_RESTRICTED_BUCKET,
- Global.DEFAULT_ENABLE_RESTRICTED_BUCKET) == 1;
- }
-
File getDataSystemDirectory() {
return Environment.getDataSystemDirectory();
}
@@ -3079,11 +3052,6 @@
// APP_STANDBY_ENABLED is a SystemApi that some apps may be watching, so best to
// leave it in Settings.
cr.registerContentObserver(Global.getUriFor(Global.APP_STANDBY_ENABLED), false, this);
- // Leave ENABLE_RESTRICTED_BUCKET as a user-controlled setting which will stay in
- // Settings.
- // TODO: make setting user-specific
- cr.registerContentObserver(Global.getUriFor(Global.ENABLE_RESTRICTED_BUCKET),
- false, this);
// ADAPTIVE_BATTERY_MANAGEMENT_ENABLED is a user setting, so it has to stay in Settings.
cr.registerContentObserver(Global.getUriFor(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED),
false, this);
@@ -3284,10 +3252,6 @@
Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED));
}
- synchronized (mAppIdleLock) {
- mAllowRestrictedBucket = mInjector.isRestrictedBucketEnabled();
- }
-
setAppIdleEnabled(mInjector.isAppIdleEnabled());
}
diff --git a/api/Android.bp b/api/Android.bp
index 73dbd28..24b3004 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -41,23 +41,6 @@
}
python_binary_host {
- name: "api_versions_trimmer",
- srcs: ["api_versions_trimmer.py"],
-}
-
-python_test_host {
- name: "api_versions_trimmer_unittests",
- main: "api_versions_trimmer_unittests.py",
- srcs: [
- "api_versions_trimmer_unittests.py",
- "api_versions_trimmer.py",
- ],
- test_options: {
- unit_test: true,
- },
-}
-
-python_binary_host {
name: "merge_annotation_zips",
srcs: ["merge_annotation_zips.py"],
}
diff --git a/api/api.go b/api/api.go
index 9876abb..09c2383 100644
--- a/api/api.go
+++ b/api/api.go
@@ -194,55 +194,6 @@
}
}
-func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
- // For the filtered api versions, we prune all APIs except art module's APIs. because
- // 1) ART apis are available by default to all modules, while other module-to-module deps are
- // explicit and probably receive more scrutiny anyway
- // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap
- // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have
- // per-module lint databases that excludes just that module's APIs. Alas, that's more
- // difficult to achieve.
- modules = remove(modules, art)
-
- for _, i := range []struct{
- name string
- out string
- in string
- }{
- {
- // We shouldn't need public-filtered or system-filtered.
- // public-filtered is currently used to lint things that
- // use the module sdk or the system server sdk, but those
- // should be switched over to module-filtered and
- // system-server-filtered, and then public-filtered can
- // be removed.
- name: "api-versions-xml-public-filtered",
- out: "api-versions-public-filtered.xml",
- in: ":api_versions_public{.api_versions.xml}",
- }, {
- name: "api-versions-xml-module-lib-filtered",
- out: "api-versions-module-lib-filtered.xml",
- in: ":api_versions_module_lib{.api_versions.xml}",
- }, {
- name: "api-versions-xml-system-server-filtered",
- out: "api-versions-system-server-filtered.xml",
- in: ":api_versions_system_server{.api_versions.xml}",
- },
- } {
- props := genruleProps{}
- props.Name = proptools.StringPtr(i.name)
- props.Out = []string{i.out}
- // Note: order matters: first parameter is the full api-versions.xml
- // after that the stubs files in any order
- // stubs files are all modules that export API surfaces EXCEPT ART
- props.Srcs = append([]string{i.in}, createSrcs(modules, ".stubs{.jar}")...)
- props.Tools = []string{"api_versions_trimmer"}
- props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
- props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
- ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
- }
-}
-
func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) {
props := libraryProps{}
props.Name = proptools.StringPtr("all-modules-public-stubs")
@@ -395,8 +346,6 @@
createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
- createFilteredApiVersions(ctx, bootclasspath)
-
createPublicStubsSourceFilegroup(ctx, bootclasspath)
}
diff --git a/api/api_versions_trimmer.py b/api/api_versions_trimmer.py
deleted file mode 100755
index 9afd95a..0000000
--- a/api/api_versions_trimmer.py
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 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.
-
-"""Script to remove mainline APIs from the api-versions.xml."""
-
-import argparse
-import re
-import xml.etree.ElementTree as ET
-import zipfile
-
-
-def read_classes(stubs):
- """Read classes from the stubs file.
-
- Args:
- stubs: argument can be a path to a file (a string), a file-like object or a
- path-like object
-
- Returns:
- a set of the classes found in the file (set of strings)
- """
- classes = set()
- with zipfile.ZipFile(stubs) as z:
- for info in z.infolist():
- if (not info.is_dir()
- and info.filename.endswith(".class")
- and not info.filename.startswith("META-INF")):
- # drop ".class" extension
- classes.add(info.filename[:-6])
- return classes
-
-
-def filter_method_tag(method, classes_to_remove):
- """Updates the signature of this method by calling filter_method_signature.
-
- Updates the method passed into this function.
-
- Args:
- method: xml element that represents a method
- classes_to_remove: set of classes you to remove
- """
- filtered = filter_method_signature(method.get("name"), classes_to_remove)
- method.set("name", filtered)
-
-
-def filter_method_signature(signature, classes_to_remove):
- """Removes mentions of certain classes from this method signature.
-
- Replaces any existing classes that need to be removed, with java/lang/Object
-
- Args:
- signature: string that is a java representation of a method signature
- classes_to_remove: set of classes you to remove
- """
- regex = re.compile("L.*?;")
- start = signature.find("(")
- matches = set(regex.findall(signature[start:]))
- for m in matches:
- # m[1:-1] to drop the leading `L` and `;` ending
- if m[1:-1] in classes_to_remove:
- signature = signature.replace(m, "Ljava/lang/Object;")
- return signature
-
-
-def filter_lint_database(database, classes_to_remove, output):
- """Reads a lint database and writes a filtered version without some classes.
-
- Reads database from api-versions.xml and removes any references to classes
- in the second argument. Writes the result (another xml with the same format
- of the database) to output.
-
- Args:
- database: path to xml with lint database to read
- classes_to_remove: iterable (ideally a set or similar for quick
- lookups) that enumerates the classes that should be removed
- output: path to write the filtered database
- """
- xml = ET.parse(database)
- root = xml.getroot()
- for c in xml.findall("class"):
- cname = c.get("name")
- if cname in classes_to_remove:
- root.remove(c)
- else:
- # find the <extends /> tag inside this class to see if the parent
- # has been removed from the known classes (attribute called name)
- super_classes = c.findall("extends")
- for super_class in super_classes:
- super_class_name = super_class.get("name")
- if super_class_name in classes_to_remove:
- super_class.set("name", "java/lang/Object")
- interfaces = c.findall("implements")
- for interface in interfaces:
- interface_name = interface.get("name")
- if interface_name in classes_to_remove:
- c.remove(interface)
- for method in c.findall("method"):
- filter_method_tag(method, classes_to_remove)
- xml.write(output)
-
-
-def main():
- """Run the program."""
- parser = argparse.ArgumentParser(
- description=
- ("Read a lint database (api-versions.xml) and many stubs jar files. "
- "Produce another database file that doesn't include the classes present "
- "in the stubs file(s)."))
- parser.add_argument("output", help="Destination of the result (xml file).")
- parser.add_argument(
- "api_versions",
- help="The lint database (api-versions.xml file) to read data from"
- )
- parser.add_argument("stubs", nargs="+", help="The stubs jar file(s)")
- parsed = parser.parse_args()
- classes = set()
- for stub in parsed.stubs:
- classes.update(read_classes(stub))
- filter_lint_database(parsed.api_versions, classes, parsed.output)
-
-
-if __name__ == "__main__":
- main()
diff --git a/api/api_versions_trimmer_unittests.py b/api/api_versions_trimmer_unittests.py
deleted file mode 100644
index d2e5b7d..0000000
--- a/api/api_versions_trimmer_unittests.py
+++ /dev/null
@@ -1,307 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 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.
-
-import io
-import re
-import unittest
-import xml.etree.ElementTree as ET
-import zipfile
-
-import api_versions_trimmer
-
-
-def create_in_memory_zip_file(files):
- f = io.BytesIO()
- with zipfile.ZipFile(f, "w") as z:
- for fname in files:
- with z.open(fname, mode="w") as class_file:
- class_file.write(b"")
- return f
-
-
-def indent(elem, level=0):
- i = "\n" + level * " "
- j = "\n" + (level - 1) * " "
- if len(elem):
- if not elem.text or not elem.text.strip():
- elem.text = i + " "
- if not elem.tail or not elem.tail.strip():
- elem.tail = i
- for subelem in elem:
- indent(subelem, level + 1)
- if not elem.tail or not elem.tail.strip():
- elem.tail = j
- else:
- if level and (not elem.tail or not elem.tail.strip()):
- elem.tail = j
- return elem
-
-
-def pretty_print(s):
- tree = ET.parse(io.StringIO(s))
- el = indent(tree.getroot())
- res = ET.tostring(el).decode("utf-8")
- # remove empty lines inside the result because this still breaks some
- # comparisons
- return re.sub(r"\n\s*\n", "\n", res, re.MULTILINE)
-
-
-class ApiVersionsTrimmerUnittests(unittest.TestCase):
-
- def setUp(self):
- # so it prints diffs in long strings (xml files)
- self.maxDiff = None
-
- def test_read_classes(self):
- f = create_in_memory_zip_file(
- ["a/b/C.class",
- "a/b/D.class",
- ]
- )
- res = api_versions_trimmer.read_classes(f)
- self.assertEqual({"a/b/C", "a/b/D"}, res)
-
- def test_read_classes_ignore_dex(self):
- f = create_in_memory_zip_file(
- ["a/b/C.class",
- "a/b/D.class",
- "a/b/E.dex",
- "f.dex",
- ]
- )
- res = api_versions_trimmer.read_classes(f)
- self.assertEqual({"a/b/C", "a/b/D"}, res)
-
- def test_read_classes_ignore_manifest(self):
- f = create_in_memory_zip_file(
- ["a/b/C.class",
- "a/b/D.class",
- "META-INFO/G.class"
- ]
- )
- res = api_versions_trimmer.read_classes(f)
- self.assertEqual({"a/b/C", "a/b/D"}, res)
-
- def test_filter_method_signature(self):
- xml = """
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
- """
- method = ET.fromstring(xml)
- classes_to_remove = {"android/accessibilityservice/GestureDescription"}
- expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
- api_versions_trimmer.filter_method_tag(method, classes_to_remove)
- self.assertEqual(expected, method.get("name"))
-
- def test_filter_method_signature_with_L_in_method(self):
- xml = """
- <method name="dispatchLeftGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
- """
- method = ET.fromstring(xml)
- classes_to_remove = {"android/accessibilityservice/GestureDescription"}
- expected = "dispatchLeftGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
- api_versions_trimmer.filter_method_tag(method, classes_to_remove)
- self.assertEqual(expected, method.get("name"))
-
- def test_filter_method_signature_with_L_in_class(self):
- xml = """
- <method name="dispatchGesture(Landroid/accessibilityservice/LeftGestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
- """
- method = ET.fromstring(xml)
- classes_to_remove = {"android/accessibilityservice/LeftGestureDescription"}
- expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
- api_versions_trimmer.filter_method_tag(method, classes_to_remove)
- self.assertEqual(expected, method.get("name"))
-
- def test_filter_method_signature_with_inner_class(self):
- xml = """
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription$Inner;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/>
- """
- method = ET.fromstring(xml)
- classes_to_remove = {"android/accessibilityservice/GestureDescription$Inner"}
- expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z"
- api_versions_trimmer.filter_method_tag(method, classes_to_remove)
- self.assertEqual(expected, method.get("name"))
-
- def _run_filter_db_test(self, database_str, expected):
- """Performs the pattern of testing the filter_lint_database method.
-
- Filters instances of the class "a/b/C" (hard-coded) from the database string
- and compares the result with the expected result (performs formatting of
- the xml of both inputs)
-
- Args:
- database_str: string, the contents of the lint database (api-versions.xml)
- expected: string, the expected result after filtering the original
- database
- """
- database = io.StringIO(database_str)
- classes_to_remove = {"a/b/C"}
- output = io.BytesIO()
- api_versions_trimmer.filter_lint_database(
- database,
- classes_to_remove,
- output
- )
- expected = pretty_print(expected)
- res = pretty_print(output.getvalue().decode("utf-8"))
- self.assertEqual(expected, res)
-
- def test_filter_lint_database_updates_method_signature_params(self):
- self._run_filter_db_test(
- database_str="""
- <api version="2">
- <!-- will be removed -->
- <class name="a/b/C" since="1">
- <extends name="java/lang/Object"/>
- </class>
-
- <class name="a/b/E" since="1">
- <!-- extends will be modified -->
- <extends name="a/b/C"/>
- <!-- first parameter will be modified -->
- <method name="dispatchGesture(La/b/C;Landroid/os/Handler;)Z" since="24"/>
- <!-- second should remain untouched -->
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """,
- expected="""
- <api version="2">
- <class name="a/b/E" since="1">
- <extends name="java/lang/Object"/>
- <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """)
-
- def test_filter_lint_database_updates_method_signature_return(self):
- self._run_filter_db_test(
- database_str="""
- <api version="2">
- <!-- will be removed -->
- <class name="a/b/C" since="1">
- <extends name="java/lang/Object"/>
- </class>
-
- <class name="a/b/E" since="1">
- <!-- extends will be modified -->
- <extends name="a/b/C"/>
- <!-- return type should be changed -->
- <method name="gestureIdToString(I)La/b/C;" since="24"/>
- </class>
- </api>
- """,
- expected="""
- <api version="2">
- <class name="a/b/E" since="1">
-
- <extends name="java/lang/Object"/>
-
- <method name="gestureIdToString(I)Ljava/lang/Object;" since="24"/>
- </class>
- </api>
- """)
-
- def test_filter_lint_database_removes_implements(self):
- self._run_filter_db_test(
- database_str="""
- <api version="2">
- <!-- will be removed -->
- <class name="a/b/C" since="1">
- <extends name="java/lang/Object"/>
- </class>
-
- <class name="a/b/D" since="1">
- <extends name="java/lang/Object"/>
- <implements name="a/b/C"/>
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """,
- expected="""
- <api version="2">
-
- <class name="a/b/D" since="1">
- <extends name="java/lang/Object"/>
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """)
-
- def test_filter_lint_database_updates_extends(self):
- self._run_filter_db_test(
- database_str="""
- <api version="2">
- <!-- will be removed -->
- <class name="a/b/C" since="1">
- <extends name="java/lang/Object"/>
- </class>
-
- <class name="a/b/E" since="1">
- <!-- extends will be modified -->
- <extends name="a/b/C"/>
- <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """,
- expected="""
- <api version="2">
- <class name="a/b/E" since="1">
- <extends name="java/lang/Object"/>
- <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/>
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """)
-
- def test_filter_lint_database_removes_class(self):
- self._run_filter_db_test(
- database_str="""
- <api version="2">
- <!-- will be removed -->
- <class name="a/b/C" since="1">
- <extends name="java/lang/Object"/>
- </class>
-
- <class name="a/b/D" since="1">
- <extends name="java/lang/Object"/>
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """,
- expected="""
- <api version="2">
-
- <class name="a/b/D" since="1">
- <extends name="java/lang/Object"/>
- <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe
-sultCallback;Landroid/os/Handler;)Z" since="24"/>
- </class>
- </api>
- """)
-
-
-if __name__ == "__main__":
- unittest.main(verbosity=2)
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 00d9a4b..c4e8b0e 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -458,6 +458,7 @@
EGLConfig BootAnimation::getEglConfig(const EGLDisplay& display) {
const EGLint attribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
index 957ebfb..a7560b2 100644
--- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
@@ -40,7 +40,7 @@
public String longHelp() {
return shortHelp() + "\n"
+ "\n"
- + "usage: svc power stayon [true|false|usb|ac|wireless]\n"
+ + "usage: svc power stayon [true|false|usb|ac|wireless|dock]\n"
+ " Set the 'keep awake while plugged in' setting.\n"
+ " svc power reboot [reason]\n"
+ " Perform a runtime shutdown and reboot device with specified reason.\n"
@@ -66,9 +66,10 @@
if ("stayon".equals(args[1]) && args.length == 3) {
int val;
if ("true".equals(args[2])) {
- val = BatteryManager.BATTERY_PLUGGED_AC |
- BatteryManager.BATTERY_PLUGGED_USB |
- BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ val = BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS
+ | BatteryManager.BATTERY_PLUGGED_DOCK;
}
else if ("false".equals(args[2])) {
val = 0;
@@ -78,6 +79,8 @@
val = BatteryManager.BATTERY_PLUGGED_AC;
} else if ("wireless".equals(args[2])) {
val = BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ } else if ("dock".equals(args[2])) {
+ val = BatteryManager.BATTERY_PLUGGED_DOCK;
} else {
break fail;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 06e3160..b91144b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -83,6 +83,8 @@
field public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY";
field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
+ field public static final String CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS = "android.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS";
+ field public static final String CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS = "android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS";
field public static final String CREDENTIAL_MANAGER_SET_ORIGIN = "android.permission.CREDENTIAL_MANAGER_SET_ORIGIN";
field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
@@ -556,6 +558,7 @@
field public static final int canTakeScreenshot = 16844303; // 0x101060f
field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
field public static final int cantSaveState = 16844142; // 0x101056e
+ field public static final int capability;
field @Deprecated public static final int capitalize = 16843113; // 0x1010169
field public static final int category = 16843752; // 0x10103e8
field public static final int centerBright = 16842956; // 0x10100cc
@@ -1448,6 +1451,7 @@
field public static final int sessionService = 16843837; // 0x101043d
field public static final int settingsActivity = 16843301; // 0x1010225
field public static final int settingsSliceUri = 16844179; // 0x1010593
+ field public static final int settingsSubtitle;
field public static final int setupActivity = 16843766; // 0x10103f6
field public static final int shadowColor = 16843105; // 0x1010161
field public static final int shadowDx = 16843106; // 0x1010162
@@ -13666,14 +13670,17 @@
method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
+ method public void getCredential(@NonNull android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
+ method public void prepareGetCredential(@NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.PrepareGetCredentialResponse,android.credentials.GetCredentialException>);
method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
}
public final class CredentialOption implements android.os.Parcelable {
- ctor public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
+ ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
+ method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders();
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialRetrievalData();
method @NonNull public String getType();
@@ -13683,6 +13690,14 @@
field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
}
+ public static final class CredentialOption.Builder {
+ ctor public CredentialOption.Builder(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle);
+ method @NonNull public android.credentials.CredentialOption.Builder addAllowedProvider(@NonNull android.content.ComponentName);
+ method @NonNull public android.credentials.CredentialOption build();
+ method @NonNull public android.credentials.CredentialOption.Builder setAllowedProviders(@NonNull java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.credentials.CredentialOption.Builder setIsSystemProviderRequired(boolean);
+ }
+
public class GetCredentialException extends java.lang.Exception {
ctor public GetCredentialException(@NonNull String, @Nullable String);
ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
@@ -13722,6 +13737,16 @@
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialResponse> CREATOR;
}
+ public final class PrepareGetCredentialResponse {
+ method @NonNull public android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle getPendingGetCredentialHandle();
+ method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasAuthenticationResults();
+ method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasCredentialResults(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS) public boolean hasRemoteResults();
+ }
+
+ public static final class PrepareGetCredentialResponse.PendingGetCredentialHandle {
+ }
+
public final class RegisterCredentialDescriptionRequest implements android.os.Parcelable {
ctor public RegisterCredentialDescriptionRequest(@NonNull android.credentials.CredentialDescription);
ctor public RegisterCredentialDescriptionRequest(@NonNull java.util.Set<android.credentials.CredentialDescription>);
@@ -40707,7 +40732,7 @@
method public abstract void onBeginGetCredential(@NonNull android.service.credentials.BeginGetCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginGetCredentialResponse,android.credentials.GetCredentialException>);
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onClearCredentialState(@NonNull android.service.credentials.ClearCredentialStateRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
- field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+ field @Deprecated public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
field public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
field public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE";
field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
@@ -40717,6 +40742,7 @@
field public static final String EXTRA_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.GET_CREDENTIAL_REQUEST";
field public static final String EXTRA_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.GET_CREDENTIAL_RESPONSE";
field public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService";
+ field public static final String SERVICE_META_DATA = "android.credentials.provider";
}
public final class GetCredentialRequest implements android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b7d97ea..7107bf7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1112,6 +1112,7 @@
method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
+ method @Nullable public CharSequence getSettingsSubtitle();
method @NonNull public boolean hasCapability(@NonNull String);
method public boolean isEnabled();
method public boolean isSystemProvider();
@@ -1124,6 +1125,7 @@
method @NonNull public android.credentials.CredentialProviderInfo.Builder addCapabilities(@NonNull java.util.List<java.lang.String>);
method @NonNull public android.credentials.CredentialProviderInfo build();
method @NonNull public android.credentials.CredentialProviderInfo.Builder setEnabled(boolean);
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder setSettingsSubtitle(@Nullable CharSequence);
method @NonNull public android.credentials.CredentialProviderInfo.Builder setSystemProvider(boolean);
}
@@ -2249,7 +2251,7 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser();
- method public int getDisplayIdAssignedToUser();
+ method public int getMainDisplayIdAssignedToUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index d0ce701..12026aa 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -72,6 +72,20 @@
private static long sBackgroundPauseDelay = 1000;
/**
+ * A cache of the values in a list. Used so that when calling the list, we have a copy
+ * of it in case the list is modified while iterating. The array can be reused to avoid
+ * allocation on every notification.
+ */
+ private Object[] mCachedList;
+
+ /**
+ * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
+ * complex to keep track of since we notify listeners at different times depending on
+ * startDelay and whether start() was called before end().
+ */
+ boolean mStartListenersCalled = false;
+
+ /**
* Sets the duration for delaying pausing animators when apps go into the background.
* Used by AnimationHandler when requested to pause animators.
*
@@ -158,16 +172,11 @@
* @see AnimatorPauseListener
*/
public void pause() {
- if (isStarted() && !mPaused) {
+ // We only want to pause started Animators or animators that setCurrentPlayTime()
+ // have been called on. mStartListenerCalled will be true if seek has happened.
+ if ((isStarted() || mStartListenersCalled) && !mPaused) {
mPaused = true;
- if (mPauseListeners != null) {
- ArrayList<AnimatorPauseListener> tmpListeners =
- (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationPause(this);
- }
- }
+ notifyPauseListeners(AnimatorCaller.ON_PAUSE);
}
}
@@ -184,14 +193,7 @@
public void resume() {
if (mPaused) {
mPaused = false;
- if (mPauseListeners != null) {
- ArrayList<AnimatorPauseListener> tmpListeners =
- (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationResume(this);
- }
- }
+ notifyPauseListeners(AnimatorCaller.ON_RESUME);
}
}
@@ -450,6 +452,8 @@
if (mPauseListeners != null) {
anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
}
+ anim.mCachedList = null;
+ anim.mStartListenersCalled = false;
return anim;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
@@ -591,6 +595,86 @@
}
/**
+ * Calls notification for each AnimatorListener.
+ *
+ * @param notification The notification method to call on each listener.
+ * @param isReverse When this is used with start/end, this is the isReverse parameter. For
+ * other calls, this is ignored.
+ */
+ void notifyListeners(
+ AnimatorCaller<AnimatorListener, Animator> notification,
+ boolean isReverse
+ ) {
+ callOnList(mListeners, notification, this, isReverse);
+ }
+
+ /**
+ * Call pause/resume on each AnimatorPauseListener.
+ *
+ * @param notification Either ON_PAUSE or ON_RESUME to call onPause or onResume on each
+ * listener.
+ */
+ void notifyPauseListeners(AnimatorCaller<AnimatorPauseListener, Animator> notification) {
+ callOnList(mPauseListeners, notification, this, false);
+ }
+
+ void notifyStartListeners(boolean isReversing) {
+ boolean startListenersCalled = mStartListenersCalled;
+ mStartListenersCalled = true;
+ if (mListeners != null && !startListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_START, isReversing);
+ }
+ }
+
+ void notifyEndListeners(boolean isReversing) {
+ boolean startListenersCalled = mStartListenersCalled;
+ mStartListenersCalled = false;
+ if (mListeners != null && startListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_END, isReversing);
+ }
+ }
+
+ /**
+ * Calls <code>call</code> for every item in <code>list</code> with <code>animator</code> and
+ * <code>isReverse</code> as parameters.
+ *
+ * @param list The list of items to make calls on.
+ * @param call The method to call for each item in list.
+ * @param animator The animator parameter of call.
+ * @param isReverse The isReverse parameter of call.
+ * @param <T> The item type of list
+ * @param <A> The Animator type of animator.
+ */
+ <T, A> void callOnList(
+ ArrayList<T> list,
+ AnimatorCaller<T, A> call,
+ A animator,
+ boolean isReverse
+ ) {
+ int size = list == null ? 0 : list.size();
+ if (size > 0) {
+ // Try to reuse mCacheList to store the items of list.
+ Object[] array;
+ if (mCachedList == null || mCachedList.length < size) {
+ array = new Object[size];
+ } else {
+ array = mCachedList;
+ // Clear it in case there is some reentrancy
+ mCachedList = null;
+ }
+ list.toArray(array);
+ for (int i = 0; i < size; i++) {
+ //noinspection unchecked
+ T item = (T) array[i];
+ call.call(item, animator, isReverse);
+ array[i] = null;
+ }
+ // Store it for the next call so we can reuse this array, if needed.
+ mCachedList = array;
+ }
+ }
+
+ /**
* <p>An animation listener receives notifications from an animation.
* Notifications indicate animation related events, such as the end or the
* repetition of the animation.</p>
@@ -748,4 +832,29 @@
return clone;
}
}
+
+ /**
+ * Internally used by {@link #callOnList(ArrayList, AnimatorCaller, Object, boolean)} to
+ * make a call on all children of a list. This can be for start, stop, pause, cancel, update,
+ * etc notifications.
+ *
+ * @param <T> The type of listener to make the call on
+ * @param <A> The type of animator that is passed as a parameter
+ */
+ interface AnimatorCaller<T, A> {
+ void call(T listener, A animator, boolean isReverse);
+
+ AnimatorCaller<AnimatorListener, Animator> ON_START = AnimatorListener::onAnimationStart;
+ AnimatorCaller<AnimatorListener, Animator> ON_END = AnimatorListener::onAnimationEnd;
+ AnimatorCaller<AnimatorListener, Animator> ON_CANCEL =
+ (listener, animator, isReverse) -> listener.onAnimationCancel(animator);
+ AnimatorCaller<AnimatorListener, Animator> ON_REPEAT =
+ (listener, animator, isReverse) -> listener.onAnimationRepeat(animator);
+ AnimatorCaller<AnimatorPauseListener, Animator> ON_PAUSE =
+ (listener, animator, isReverse) -> listener.onAnimationPause(animator);
+ AnimatorCaller<AnimatorPauseListener, Animator> ON_RESUME =
+ (listener, animator, isReverse) -> listener.onAnimationResume(animator);
+ AnimatorCaller<ValueAnimator.AnimatorUpdateListener, ValueAnimator> ON_UPDATE =
+ (listener, animator, isReverse) -> listener.onAnimationUpdate(animator);
+ }
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 257adfe..60659dc 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -32,6 +32,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
+import java.util.function.Consumer;
/**
* This class plays a set of {@link Animator} objects in the specified order. Animations
@@ -188,11 +189,6 @@
*/
private long[] mChildStartAndStopTimes;
- /**
- * Tracks whether we've notified listeners of the onAnimationStart() event.
- */
- private boolean mStartListenersCalled;
-
// This is to work around a bug in b/34736819. This needs to be removed once app team
// fixes their side.
private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -423,25 +419,29 @@
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
- if (isStarted()) {
- ArrayList<AnimatorListener> tmpListeners = null;
- if (mListeners != null) {
- tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
- int size = tmpListeners.size();
- for (int i = 0; i < size; i++) {
- tmpListeners.get(i).onAnimationCancel(this);
- }
- }
- ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet);
- int setSize = playingSet.size();
- for (int i = 0; i < setSize; i++) {
- playingSet.get(i).mAnimation.cancel();
- }
+ if (isStarted() || mStartListenersCalled) {
+ notifyListeners(AnimatorCaller.ON_CANCEL, false);
+ callOnPlayingSet(Animator::cancel);
mPlayingSet.clear();
endAnimation();
}
}
+ /**
+ * Calls consumer on every Animator of mPlayingSet.
+ *
+ * @param consumer The method to call on every Animator of mPlayingSet.
+ */
+ private void callOnPlayingSet(Consumer<Animator> consumer) {
+ final ArrayList<Node> list = mPlayingSet;
+ final int size = list.size();
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < size; i++) {
+ final Animator animator = list.get(i).mAnimation;
+ consumer.accept(animator);
+ }
+ }
+
// Force all the animations to end when the duration scale is 0.
private void forceToEnd() {
if (mEndCanBeCalled) {
@@ -481,13 +481,13 @@
return;
}
if (isStarted()) {
+ mStarted = false; // don't allow reentrancy
// Iterate the animations that haven't finished or haven't started, and end them.
if (mReversing) {
// Between start() and first frame, mLastEventId would be unset (i.e. -1)
mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
- while (mLastEventId > 0) {
- mLastEventId = mLastEventId - 1;
- AnimationEvent event = mEvents.get(mLastEventId);
+ for (int eventId = mLastEventId - 1; eventId >= 0; eventId--) {
+ AnimationEvent event = mEvents.get(eventId);
Animator anim = event.mNode.mAnimation;
if (mNodeMap.get(anim).mEnded) {
continue;
@@ -503,11 +503,10 @@
}
}
} else {
- while (mLastEventId < mEvents.size() - 1) {
+ for (int eventId = mLastEventId + 1; eventId < mEvents.size(); eventId++) {
// Avoid potential reentrant loop caused by child animators manipulating
// AnimatorSet's lifecycle (i.e. not a recommended approach).
- mLastEventId = mLastEventId + 1;
- AnimationEvent event = mEvents.get(mLastEventId);
+ AnimationEvent event = mEvents.get(eventId);
Animator anim = event.mNode.mAnimation;
if (mNodeMap.get(anim).mEnded) {
continue;
@@ -522,7 +521,6 @@
}
}
}
- mPlayingSet.clear();
}
endAnimation();
}
@@ -662,6 +660,7 @@
super.pause();
if (!previouslyPaused && mPaused) {
mPauseTime = -1;
+ callOnPlayingSet(Animator::pause);
}
}
@@ -676,6 +675,7 @@
if (mPauseTime >= 0) {
addAnimationCallback(0);
}
+ callOnPlayingSet(Animator::resume);
}
}
@@ -716,6 +716,10 @@
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
+ if (inReverse == mReversing && selfPulse == mSelfPulse && mStarted) {
+ // It is already started
+ return;
+ }
mStarted = true;
mSelfPulse = selfPulse;
mPaused = false;
@@ -749,32 +753,6 @@
}
}
- private void notifyStartListeners(boolean inReverse) {
- if (mListeners != null && !mStartListenersCalled) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- AnimatorListener listener = tmpListeners.get(i);
- listener.onAnimationStart(this, inReverse);
- }
- }
- mStartListenersCalled = true;
- }
-
- private void notifyEndListeners(boolean inReverse) {
- if (mListeners != null && mStartListenersCalled) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- AnimatorListener listener = tmpListeners.get(i);
- listener.onAnimationEnd(this, inReverse);
- }
- }
- mStartListenersCalled = false;
- }
-
// Returns true if set is empty or contains nothing but animator sets with no start delay.
private static boolean isEmptySet(AnimatorSet set) {
if (set.getStartDelay() > 0) {
@@ -941,12 +919,18 @@
lastPlayTime - node.mStartTime,
notify
);
+ if (notify) {
+ mPlayingSet.remove(node);
+ }
} else if (start <= currentPlayTime && currentPlayTime <= end) {
animator.animateSkipToEnds(
currentPlayTime - node.mStartTime,
lastPlayTime - node.mStartTime,
notify
);
+ if (notify && !mPlayingSet.contains(node)) {
+ mPlayingSet.add(node);
+ }
}
}
}
@@ -974,12 +958,18 @@
lastPlayTime - node.mStartTime,
notify
);
+ if (notify) {
+ mPlayingSet.remove(node);
+ }
} else if (start <= currentPlayTime && currentPlayTime <= end) {
animator.animateSkipToEnds(
currentPlayTime - node.mStartTime,
lastPlayTime - node.mStartTime,
notify
);
+ if (notify && !mPlayingSet.contains(node)) {
+ mPlayingSet.add(node);
+ }
}
}
}
@@ -1120,8 +1110,8 @@
mSeekState.setPlayTime(0, mReversing);
}
}
- animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
mSeekState.setPlayTime(playTime, mReversing);
+ animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
}
/**
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 7009725..5d69f8b 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -199,13 +199,6 @@
private boolean mStarted = false;
/**
- * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
- * complex to keep track of since we notify listeners at different times depending on
- * startDelay and whether start() was called before end().
- */
- private boolean mStartListenersCalled = false;
-
- /**
* Flag that denotes whether the animation is set up and ready to go. Used to
* set up animation that has not yet been started.
*/
@@ -1108,30 +1101,6 @@
}
}
- private void notifyStartListeners(boolean isReversing) {
- if (mListeners != null && !mStartListenersCalled) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this, isReversing);
- }
- }
- mStartListenersCalled = true;
- }
-
- private void notifyEndListeners(boolean isReversing) {
- if (mListeners != null && mStartListenersCalled) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this, isReversing);
- }
- }
- mStartListenersCalled = false;
- }
-
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1149,6 +1118,10 @@
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
+ if (playBackwards == mResumed && mSelfPulse == !mSuppressSelfPulseRequested && mStarted) {
+ // already started
+ return;
+ }
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
@@ -1219,23 +1192,14 @@
// Only cancel if the animation is actually running or has been started and is about
// to run
// Only notify listeners if the animator has actually started
- if ((mStarted || mRunning) && mListeners != null) {
+ if ((mStarted || mRunning || mStartListenersCalled) && mListeners != null) {
if (!mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
notifyStartListeners(mReversing);
}
- int listenersSize = mListeners.size();
- if (listenersSize > 0) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- for (int i = 0; i < listenersSize; i++) {
- AnimatorListener listener = tmpListeners.get(i);
- listener.onAnimationCancel(this);
- }
- }
+ notifyListeners(AnimatorCaller.ON_CANCEL, false);
}
endAnimation();
-
}
@Override
@@ -1338,11 +1302,11 @@
// If it's not yet running, then start listeners weren't called. Call them now.
notifyStartListeners(mReversing);
}
- mRunning = false;
- mStarted = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
+ mRunning = false;
+ mStarted = false;
notifyEndListeners(mReversing);
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
@@ -1435,12 +1399,7 @@
done = true;
} else if (newIteration && !lastIterationFinished) {
// Time to repeat
- if (mListeners != null) {
- int numListeners = mListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- mListeners.get(i).onAnimationRepeat(this);
- }
- }
+ notifyListeners(AnimatorCaller.ON_REPEAT, false);
} else if (lastIterationFinished) {
done = true;
}
@@ -1493,13 +1452,8 @@
iteration = Math.min(iteration, mRepeatCount);
lastIteration = Math.min(lastIteration, mRepeatCount);
- if (iteration != lastIteration) {
- if (mListeners != null) {
- int numListeners = mListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- mListeners.get(i).onAnimationRepeat(this);
- }
- }
+ if (notify && iteration != lastIteration) {
+ notifyListeners(AnimatorCaller.ON_REPEAT, false);
}
}
@@ -1697,11 +1651,8 @@
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
- if (mUpdateListeners != null) {
- int numListeners = mUpdateListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- mUpdateListeners.get(i).onAnimationUpdate(this);
- }
+ if (mSeekFraction >= 0 || mStartListenersCalled) {
+ callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false);
}
}
@@ -1718,7 +1669,6 @@
anim.mRunning = false;
anim.mPaused = false;
anim.mResumed = false;
- anim.mStartListenersCalled = false;
anim.mStartTime = -1;
anim.mStartTimeCommitted = false;
anim.mAnimationEndRequested = false;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 981f140..929c07b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -234,7 +234,7 @@
* Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
* Will be called when a Uid has become frozen or unfrozen.
*/
- final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks =
+ private final ArrayMap<UidFrozenStateChangedCallback, Executor> mFrozenStateChangedCallbacks =
new ArrayMap<>();
private final IUidFrozenStateChangedCallback mFrozenStateChangedCallback =
@@ -284,6 +284,8 @@
public @interface UidFrozenState {}
/**
+ * Notify the client that the frozen states of an array of UIDs have changed.
+ *
* @param uids The UIDs for which the frozen state has changed
* @param frozenStates Frozen state for each UID index, Will be set to
* {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN}
@@ -316,9 +318,11 @@
public void registerUidFrozenStateChangedCallback(
@NonNull Executor executor,
@NonNull UidFrozenStateChangedCallback callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
synchronized (mFrozenStateChangedCallbacks) {
if (mFrozenStateChangedCallbacks.containsKey(callback)) {
- throw new IllegalArgumentException("Callback already registered: " + callback);
+ throw new IllegalStateException("Callback already registered: " + callback);
}
mFrozenStateChangedCallbacks.put(callback, executor);
if (mFrozenStateChangedCallbacks.size() > 1) {
@@ -344,6 +348,7 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void unregisterUidFrozenStateChangedCallback(
@NonNull UidFrozenStateChangedCallback callback) {
+ Preconditions.checkNotNull(callback, "callback cannot be null");
synchronized (mFrozenStateChangedCallbacks) {
mFrozenStateChangedCallbacks.remove(callback);
if (mFrozenStateChangedCallbacks.isEmpty()) {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b96f8c9..0293bb5 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -474,6 +474,12 @@
public abstract BackgroundStartPrivileges getBackgroundStartPrivileges(int uid);
public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
+ /**
+ * Returns whether the app is in a state where it is allowed to schedule a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+ */
+ public abstract boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName);
+
/** @see com.android.server.am.ActivityManagerService#monitor */
public abstract void monitor();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 2879062..403acad 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -879,6 +879,8 @@
/** Logs API state change to associate with an FGS, used for FGS Type Metrics */
void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index ac92811..4dc7253 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1151,7 +1151,7 @@
}
UserManager userManager = mInstrContext.getSystemService(UserManager.class);
- int userDisplayId = userManager.getDisplayIdAssignedToUser();
+ int userDisplayId = userManager.getMainDisplayIdAssignedToUser();
if (VERBOSE) {
Log.v(TAG, "setDisplayIfNeeded(" + event + "): eventDisplayId=" + eventDisplayId
+ ", user=" + mInstrContext.getUser() + ", userDisplayId=" + userDisplayId);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 502ef0d..99de724 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3269,6 +3269,43 @@
/**
* @hide
*/
+ public static boolean areIconsDifferent(Notification first, Notification second) {
+ return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon())
+ || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon());
+ }
+
+ /**
+ * Note that we aren't actually comparing the contents of the bitmaps here; this is only a
+ * cursory inspection. We will not return false negatives, but false positives are likely.
+ */
+ private static boolean areIconsMaybeDifferent(Icon a, Icon b) {
+ if (a == b) {
+ return false;
+ }
+ if (a == null || b == null) {
+ return true;
+ }
+ if (a.sameAs(b)) {
+ return false;
+ }
+ final int aType = a.getType();
+ if (aType != b.getType()) {
+ return true;
+ }
+ if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
+ final Bitmap aBitmap = a.getBitmap();
+ final Bitmap bBitmap = b.getBitmap();
+ return aBitmap.getWidth() != bBitmap.getWidth()
+ || aBitmap.getHeight() != bBitmap.getHeight()
+ || aBitmap.getConfig() != bBitmap.getConfig()
+ || aBitmap.getGenerationId() != bBitmap.getGenerationId();
+ }
+ return true;
+ }
+
+ /**
+ * @hide
+ */
public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) {
if (first.getStyle() == null) {
return second.getStyle() != null;
@@ -7643,8 +7680,6 @@
/**
* @hide
- * Note that we aren't actually comparing the contents of the bitmaps here, so this
- * is only doing a cursory inspection. Bitmaps of equal size will appear the same.
*/
@Override
public boolean areNotificationsVisiblyDifferent(Style other) {
@@ -7652,32 +7687,7 @@
return true;
}
BigPictureStyle otherS = (BigPictureStyle) other;
- return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture());
- }
-
- private static boolean areIconsObviouslyDifferent(Icon a, Icon b) {
- if (a == b) {
- return false;
- }
- if (a == null || b == null) {
- return true;
- }
- if (a.sameAs(b)) {
- return false;
- }
- final int aType = a.getType();
- if (aType != b.getType()) {
- return true;
- }
- if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) {
- final Bitmap aBitmap = a.getBitmap();
- final Bitmap bBitmap = b.getBitmap();
- return aBitmap.getWidth() != bBitmap.getWidth()
- || aBitmap.getHeight() != bBitmap.getHeight()
- || aBitmap.getConfig() != bBitmap.getConfig()
- || aBitmap.getGenerationId() != bBitmap.getGenerationId();
- }
- return true;
+ return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture());
}
}
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 0bdc222..1df8602 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -42,10 +42,10 @@
},
{
"file_patterns": ["(/|^)AppOpsManager.java"],
- "name": "CtsPermission2TestCases",
+ "name": "CtsPermissionPolicyTestCases",
"options": [
{
- "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+ "include-filter": "android.permissionpolicy.cts.RuntimePermissionProperties"
}
]
},
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 303ada0..404f94a 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -188,6 +188,14 @@
public int launchIntoPipHostTaskId;
/**
+ * The task id of the parent Task of the launch-into-pip Activity, i.e., if task have more than
+ * one activity it will create new task for this activity, this id is the origin task id and
+ * the pip activity will be reparent to origin task when it exit pip mode.
+ * @hide
+ */
+ public int lastParentTaskIdBeforePip;
+
+ /**
* The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of
* (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS),
* {@code null} otherwise.
@@ -512,6 +520,7 @@
pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR);
shouldDockBigOverlays = source.readBoolean();
launchIntoPipHostTaskId = source.readInt();
+ lastParentTaskIdBeforePip = source.readInt();
displayCutoutInsets = source.readTypedObject(Rect.CREATOR);
topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
isResizeable = source.readBoolean();
@@ -558,6 +567,7 @@
dest.writeTypedObject(pictureInPictureParams, flags);
dest.writeBoolean(shouldDockBigOverlays);
dest.writeInt(launchIntoPipHostTaskId);
+ dest.writeInt(lastParentTaskIdBeforePip);
dest.writeTypedObject(displayCutoutInsets, flags);
dest.writeTypedObject(topActivityInfo, flags);
dest.writeBoolean(isResizeable);
@@ -598,6 +608,7 @@
+ " pictureInPictureParams=" + pictureInPictureParams
+ " shouldDockBigOverlays=" + shouldDockBigOverlays
+ " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId
+ + " lastParentTaskIdBeforePip=" + lastParentTaskIdBeforePip
+ " displayCutoutSafeInsets=" + displayCutoutInsets
+ " topActivityInfo=" + topActivityInfo
+ " launchCookies=" + launchCookies
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 90681cb..00e6408 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -603,6 +603,10 @@
mVirtualAudioDevice.close();
mVirtualAudioDevice = null;
}
+ if (mVirtualCameraDevice != null) {
+ mVirtualCameraDevice.close();
+ mVirtualCameraDevice = null;
+ }
}
/**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index d3502c5..d2dbd64 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -309,14 +309,30 @@
try {
if (checkGetTypePermission(attributionSource, uri)
== PermissionChecker.PERMISSION_GRANTED) {
- final String type = mInterface.getType(uri);
+ String type;
+ if (checkPermission(Manifest.permission.GET_ANY_PROVIDER_TYPE,
+ attributionSource) == PermissionChecker.PERMISSION_GRANTED) {
+ /*
+ For calling packages having the special permission for any type,
+ the calling identity should be cleared before calling getType.
+ */
+ final CallingIdentity origId = getContentProvider().clearCallingIdentity();
+ try {
+ type = mInterface.getType(uri);
+ } finally {
+ getContentProvider().restoreCallingIdentity(origId);
+ }
+ } else {
+ type = mInterface.getType(uri);
+ }
+
if (type != null) {
logGetTypeData(Binder.getCallingUid(), uri, type, true);
}
return type;
} else {
final int callingUid = Binder.getCallingUid();
- final long origId = Binder.clearCallingIdentity();
+ final CallingIdentity origId = getContentProvider().clearCallingIdentity();
try {
final String type = getTypeAnonymous(uri);
if (type != null) {
@@ -324,7 +340,7 @@
}
return type;
} finally {
- Binder.restoreCallingIdentity(origId);
+ getContentProvider().restoreCallingIdentity(origId);
}
}
} catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b9c671a..21e2a13 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10134,25 +10134,9 @@
/**
* Register an application dex module with the package manager.
- * The package manager will keep track of the given module for future optimizations.
*
- * Dex module optimizations will disable the classpath checking at runtime. The client bares
- * the responsibility to ensure that the static assumptions on classes in the optimized code
- * hold at runtime (e.g. there's no duplicate classes in the classpath).
- *
- * Note that the package manager already keeps track of dex modules loaded with
- * {@link dalvik.system.DexClassLoader} and {@link dalvik.system.PathClassLoader}.
- * This can be called for an eager registration.
- *
- * The call might take a while and the results will be posted on the main thread, using
- * the given callback.
- *
- * If the module is intended to be shared with other apps, make sure that the file
- * permissions allow for it.
- * If at registration time the permissions allow for others to read it, the module would
- * be marked as a shared module which might undergo a different optimization strategy.
- * (usually shared modules will generated larger optimizations artifacts,
- * taking more disk space).
+ * This call no longer does anything. If a callback is given it is called with a false success
+ * value.
*
* @param dexModulePath the absolute path of the dex module.
* @param callback if not null, {@link DexModuleRegisterCallback#onDexModuleRegistered} will
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 493a4ff..5579d22 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -16,8 +16,6 @@
package android.credentials;
-import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
-
import static java.util.Objects.requireNonNull;
import android.annotation.CallbackExecutor;
@@ -167,37 +165,83 @@
}
/**
- * Gets a {@link GetPendingCredentialResponse} that can launch the credential retrieval UI flow
- * to request a user credential for your app.
+ * Launches the remaining flows to retrieve an app credential from the user, after the
+ * completed prefetch work corresponding to the given {@code pendingGetCredentialHandle}.
+ *
+ * <p>The execution can potentially launch UI flows to collect user consent to using a
+ * credential, display a picker when multiple credentials exist, etc.
+ *
+ * <p>Use this API to complete the full credential retrieval operation after you initiated a
+ * request through the {@link #prepareGetCredential(
+ * GetCredentialRequest, CancellationSignal, Executor, OutcomeReceiver)} API.
+ *
+ * @param pendingGetCredentialHandle the handle representing the pending operation to resume
+ * @param activity the activity used to launch any UI needed
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ */
+ public void getCredential(
+ @NonNull PrepareGetCredentialResponse.PendingGetCredentialHandle
+ pendingGetCredentialHandle,
+ @NonNull Activity activity,
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+ requireNonNull(pendingGetCredentialHandle, "pendingGetCredentialHandle must not be null");
+ requireNonNull(activity, "activity must not be null");
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "getCredential already canceled");
+ return;
+ }
+
+ pendingGetCredentialHandle.show(activity, cancellationSignal, executor, callback);
+ }
+
+ /**
+ * Prepare for a get-credential operation. Returns a {@link PrepareGetCredentialResponse} that
+ * can launch the credential retrieval UI flow to request a user credential for your app.
+ *
+ * <p>This API doesn't invoke any UI. It only performs the preparation work so that you can
+ * later launch the remaining get-credential operation (involves UIs) through the {@link
+ * #getCredential(PrepareGetCredentialResponse.PendingGetCredentialHandle, Activity,
+ * CancellationSignal, Executor, OutcomeReceiver)} API which incurs less latency compared to
+ * the {@link #getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
+ * OutcomeReceiver)} API that executes the whole operation in one call.
*
* @param request the request specifying type(s) of credentials to get from the user
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
- *
- * @hide
*/
- public void getPendingCredential(
+ public void prepareGetCredential(
@NonNull GetCredentialRequest request,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
- GetPendingCredentialResponse, GetCredentialException> callback) {
+ PrepareGetCredentialResponse, GetCredentialException> callback) {
requireNonNull(request, "request must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "getPendingCredential already canceled");
+ Log.w(TAG, "prepareGetCredential already canceled");
return;
}
ICancellationSignal cancelRemote = null;
+ GetCredentialTransportPendingUseCase getCredentialTransport =
+ new GetCredentialTransportPendingUseCase();
try {
cancelRemote =
- mService.executeGetPendingCredential(
+ mService.executePrepareGetCredential(
request,
- new GetPendingCredentialTransport(executor, callback),
+ new PrepareGetCredentialTransport(
+ executor, callback, getCredentialTransport),
+ getCredentialTransport,
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -484,23 +528,27 @@
}
}
- private static class GetPendingCredentialTransport extends IGetPendingCredentialCallback.Stub {
+ private static class PrepareGetCredentialTransport extends IPrepareGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
private final Executor mExecutor;
private final OutcomeReceiver<
- GetPendingCredentialResponse, GetCredentialException> mCallback;
+ PrepareGetCredentialResponse, GetCredentialException> mCallback;
+ private final GetCredentialTransportPendingUseCase mGetCredentialTransport;
- private GetPendingCredentialTransport(
+ private PrepareGetCredentialTransport(
Executor executor,
- OutcomeReceiver<GetPendingCredentialResponse, GetCredentialException> callback) {
+ OutcomeReceiver<PrepareGetCredentialResponse, GetCredentialException> callback,
+ GetCredentialTransportPendingUseCase getCredentialTransport) {
mExecutor = executor;
mCallback = callback;
+ mGetCredentialTransport = getCredentialTransport;
}
@Override
- public void onResponse(GetPendingCredentialResponse response) {
- mExecutor.execute(() -> mCallback.onResult(response));
+ public void onResponse(PrepareGetCredentialResponseInternal response) {
+ mExecutor.execute(() -> mCallback.onResult(
+ new PrepareGetCredentialResponse(response, mGetCredentialTransport)));
}
@Override
@@ -510,6 +558,51 @@
}
}
+ /** @hide */
+ protected static class GetCredentialTransportPendingUseCase
+ extends IGetCredentialCallback.Stub {
+ @Nullable private PrepareGetCredentialResponse.GetPendingCredentialInternalCallback
+ mCallback = null;
+
+ private GetCredentialTransportPendingUseCase() {}
+
+ public void setCallback(
+ PrepareGetCredentialResponse.GetPendingCredentialInternalCallback callback) {
+ if (mCallback == null) {
+ mCallback = callback;
+ } else {
+ throw new IllegalStateException("callback has already been set once");
+ }
+ }
+
+ @Override
+ public void onPendingIntent(PendingIntent pendingIntent) {
+ if (mCallback != null) {
+ mCallback.onPendingIntent(pendingIntent);
+ } else {
+ Log.d(TAG, "Unexpected onPendingIntent call before the show invocation");
+ }
+ }
+
+ @Override
+ public void onResponse(GetCredentialResponse response) {
+ if (mCallback != null) {
+ mCallback.onResponse(response);
+ } else {
+ Log.d(TAG, "Unexpected onResponse call before the show invocation");
+ }
+ }
+
+ @Override
+ public void onError(String errorType, String message) {
+ if (mCallback != null) {
+ mCallback.onError(errorType, message);
+ } else {
+ Log.d(TAG, "Unexpected onError call before the show invocation");
+ }
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
@@ -535,7 +628,8 @@
TAG,
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
e);
- // TODO: propagate the error.
+ mExecutor.execute(() -> mCallback.onError(
+ new GetCredentialException(GetCredentialException.TYPE_UNKNOWN)));
}
}
@@ -577,7 +671,8 @@
TAG,
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
e);
- // TODO: propagate the error.
+ mExecutor.execute(() -> mCallback.onError(
+ new CreateCredentialException(CreateCredentialException.TYPE_UNKNOWN)));
}
}
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index 9a3b46d..da6656a 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -16,16 +16,25 @@
package android.credentials;
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
+
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
+
+import androidx.annotation.RequiresPermission;
import com.android.internal.util.AnnotationValidations;
import com.android.internal.util.Preconditions;
+import java.util.Set;
+
/**
* Information about a specific type of credential to be requested during a {@link
* CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
@@ -66,6 +75,14 @@
private final boolean mIsSystemProviderRequired;
/**
+ * A list of {@link ComponentName}s corresponding to the providers that this option must be
+ * queried against.
+ */
+ @NonNull
+ private final ArraySet<ComponentName> mAllowedProviders;
+
+
+ /**
* Returns the requested credential type.
*/
@NonNull
@@ -105,12 +122,22 @@
return mIsSystemProviderRequired;
}
+ /**
+ * Returns the set of {@link ComponentName} corresponding to providers that must receive
+ * this option.
+ */
+ @NonNull
+ public Set<ComponentName> getAllowedProviders() {
+ return mAllowedProviders;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
dest.writeBundle(mCredentialRetrievalData);
dest.writeBundle(mCandidateQueryData);
dest.writeBoolean(mIsSystemProviderRequired);
+ dest.writeArraySet(mAllowedProviders);
}
@Override
@@ -125,6 +152,7 @@
+ ", requestData=" + mCredentialRetrievalData
+ ", candidateQueryData=" + mCandidateQueryData
+ ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ + ", allowedProviders=" + mAllowedProviders
+ "}";
}
@@ -139,17 +167,50 @@
* provider
* @throws IllegalArgumentException If type is empty.
*/
- public CredentialOption(
+ private CredentialOption(
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
@NonNull Bundle candidateQueryData,
- boolean isSystemProviderRequired) {
+ boolean isSystemProviderRequired,
+ @NonNull ArraySet<ComponentName> allowedProviders) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
"requestData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
mIsSystemProviderRequired = isSystemProviderRequired;
+ mAllowedProviders = requireNonNull(allowedProviders, "providerFilterSer must"
+ + "not be empty");
+ }
+
+ /**
+ * Constructs a {@link CredentialOption}.
+ *
+ * @param type the requested credential type
+ * @param credentialRetrievalData the request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial credential candidate query stage
+ * @param isSystemProviderRequired whether the request must only be fulfilled by a system
+ * provider
+ * @throws IllegalArgumentException If type is empty, or null.
+ * @throws NullPointerException If {@code credentialRetrievalData}, or
+ * {@code candidateQueryData} is null.
+ *
+ * @deprecated replaced by Builder
+ */
+ @Deprecated
+ public CredentialOption(
+ @NonNull String type,
+ @NonNull Bundle credentialRetrievalData,
+ @NonNull Bundle candidateQueryData,
+ boolean isSystemProviderRequired) {
+ this(
+ type,
+ credentialRetrievalData,
+ candidateQueryData,
+ isSystemProviderRequired,
+ new ArraySet<>()
+ );
}
private CredentialOption(@NonNull Parcel in) {
@@ -165,6 +226,8 @@
mCandidateQueryData = candidateQueryData;
AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
mIsSystemProviderRequired = isSystemProviderRequired;
+ mAllowedProviders = (ArraySet<ComponentName>) in.readArraySet(null);
+ AnnotationValidations.validate(NonNull.class, null, mAllowedProviders);
}
@NonNull
@@ -179,4 +242,108 @@
return new CredentialOption(in);
}
};
+
+ /** A builder for {@link CredentialOption}. */
+ public static final class Builder {
+
+ @NonNull
+ private String mType;
+
+ @NonNull
+ private Bundle mCredentialRetrievalData;
+
+ @NonNull
+ private Bundle mCandidateQueryData;
+
+ private boolean mIsSystemProviderRequired = false;
+
+ @NonNull
+ private ArraySet<ComponentName> mAllowedProviders = new ArraySet<>();
+
+ /**
+ * @param type the type of the credential option
+ * @param credentialRetrievalData the full request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial credential candidate query stage.
+ * @throws IllegalArgumentException If {@code type} is null, or empty
+ * @throws NullPointerException If {@code credentialRetrievalData}, or
+ * {@code candidateQueryData} is null
+ */
+ public Builder(@NonNull String type, @NonNull Bundle credentialRetrievalData,
+ @NonNull Bundle candidateQueryData) {
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+ + "null, or empty");
+ mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
+ "credentialRetrievalData must not be null");
+ mCandidateQueryData = requireNonNull(candidateQueryData,
+ "candidateQueryData must not be null");
+ }
+
+ /**
+ * Sets a true/false value corresponding to whether this option must be serviced by
+ * system credentials providers only.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public Builder setIsSystemProviderRequired(boolean isSystemProviderRequired) {
+ mIsSystemProviderRequired = isSystemProviderRequired;
+ return this;
+ }
+
+ /**
+ * Adds a provider {@link ComponentName} to be queried while gathering credentials from
+ * credential providers on the device.
+ *
+ * If no candidate providers are specified, all user configured and system credential
+ * providers will be queried in the candidate query phase.
+ *
+ * If an invalid component name is provided, or a service corresponding to the
+ * component name does not exist on the device, that component name is ignored.
+ * If all component names are invalid, or not present on the device, no providers
+ * are queried and no credentials are retrieved.
+ *
+ * @throws NullPointerException If {@code allowedProvider} is null
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
+ @NonNull
+ public Builder addAllowedProvider(@NonNull ComponentName allowedProvider) {
+ mAllowedProviders.add(requireNonNull(allowedProvider,
+ "allowedProvider must not be null"));
+ return this;
+ }
+
+ /**
+ * Sets a set of provider {@link ComponentName} to be queried while gathering credentials
+ * from credential providers on the device.
+ *
+ * If no candidate providers are specified, all user configured and system credential
+ * providers will be queried in the candidate query phase.
+ *
+ * If an invalid component name is provided, or a service corresponding to the
+ * component name does not exist on the device, that component name is ignored.
+ * If all component names are invalid, or not present on the device, no providers
+ * are queried and no credentials are retrieved.
+ *
+ * @throws NullPointerException If {@code allowedProviders} is null, or any of its
+ * elements are null.
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
+ @NonNull
+ public Builder setAllowedProviders(@NonNull Set<ComponentName> allowedProviders) {
+ Preconditions.checkCollectionElementsNotNull(
+ allowedProviders,
+ /*valueName=*/ "allowedProviders");
+ mAllowedProviders = new ArraySet<>(allowedProviders);
+ return this;
+ }
+
+ /**
+ * Builds a {@link CredentialOption}.
+ */
+ @NonNull
+ public CredentialOption build() {
+ return new CredentialOption(mType, mCredentialRetrievalData, mCandidateQueryData,
+ mIsSystemProviderRequired, mAllowedProviders);
+ }
+ }
}
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
index 7276770..c224f01 100644
--- a/core/java/android/credentials/CredentialProviderInfo.java
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -29,7 +29,9 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* {@link ServiceInfo} and meta-data about a credential provider.
@@ -39,8 +41,9 @@
@TestApi
public final class CredentialProviderInfo implements Parcelable {
@NonNull private final ServiceInfo mServiceInfo;
- @NonNull private final List<String> mCapabilities = new ArrayList<>();
+ @NonNull private final Set<String> mCapabilities = new HashSet<>();
@Nullable private final CharSequence mOverrideLabel;
+ @Nullable private CharSequence mSettingsSubtitle = null;
private final boolean mIsSystemProvider;
private final boolean mIsEnabled;
@@ -53,6 +56,7 @@
mServiceInfo = builder.mServiceInfo;
mCapabilities.addAll(builder.mCapabilities);
mIsSystemProvider = builder.mIsSystemProvider;
+ mSettingsSubtitle = builder.mSettingsSubtitle;
mIsEnabled = builder.mIsEnabled;
mOverrideLabel = builder.mOverrideLabel;
}
@@ -92,7 +96,11 @@
/** Returns a list of capabilities this provider service can support. */
@NonNull
public List<String> getCapabilities() {
- return Collections.unmodifiableList(mCapabilities);
+ List<String> capabilities = new ArrayList<>();
+ for (String capability : mCapabilities) {
+ capabilities.add(capability);
+ }
+ return Collections.unmodifiableList(capabilities);
}
/** Returns whether the provider is enabled by the user. */
@@ -100,6 +108,12 @@
return mIsEnabled;
}
+ /** Returns the settings subtitle. */
+ @Nullable
+ public CharSequence getSettingsSubtitle() {
+ return mSettingsSubtitle;
+ }
+
/** Returns the component name for the service. */
@NonNull
public ComponentName getComponentName() {
@@ -110,9 +124,12 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mServiceInfo, flags);
dest.writeBoolean(mIsSystemProvider);
- dest.writeStringList(mCapabilities);
dest.writeBoolean(mIsEnabled);
TextUtils.writeToParcel(mOverrideLabel, dest, flags);
+ TextUtils.writeToParcel(mSettingsSubtitle, dest, flags);
+
+ List<String> capabilities = getCapabilities();
+ dest.writeStringList(capabilities);
}
@Override
@@ -135,6 +152,9 @@
+ "overrideLabel="
+ mOverrideLabel
+ ", "
+ + "settingsSubtitle="
+ + mSettingsSubtitle
+ + ", "
+ "capabilities="
+ String.join(",", mCapabilities)
+ "}";
@@ -143,9 +163,13 @@
private CredentialProviderInfo(@NonNull Parcel in) {
mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR);
mIsSystemProvider = in.readBoolean();
- in.readStringList(mCapabilities);
mIsEnabled = in.readBoolean();
mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+
+ List<String> capabilities = new ArrayList<>();
+ in.readStringList(capabilities);
+ mCapabilities.addAll(capabilities);
}
public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
@@ -165,8 +189,9 @@
public static final class Builder {
@NonNull private ServiceInfo mServiceInfo;
- @NonNull private List<String> mCapabilities = new ArrayList<>();
+ @NonNull private Set<String> mCapabilities = new HashSet<>();
private boolean mIsSystemProvider = false;
+ @Nullable private CharSequence mSettingsSubtitle = null;
private boolean mIsEnabled = false;
@Nullable private CharSequence mOverrideLabel = null;
@@ -195,12 +220,28 @@
return this;
}
+ /** Sets the settings subtitle. */
+ public @NonNull Builder setSettingsSubtitle(@Nullable CharSequence settingsSubtitle) {
+ mSettingsSubtitle = settingsSubtitle;
+ return this;
+ }
+
/** Sets a list of capabilities this provider service can support. */
public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
mCapabilities.addAll(capabilities);
return this;
}
+ /**
+ * Sets a list of capabilities this provider service can support.
+ *
+ * @hide
+ */
+ public @NonNull Builder addCapabilities(@NonNull Set<String> capabilities) {
+ mCapabilities.addAll(capabilities);
+ return this;
+ }
+
/** Sets whether it is enabled by the user. */
public @NonNull Builder setEnabled(boolean isEnabled) {
mIsEnabled = isEnabled;
diff --git a/core/java/android/credentials/GetPendingCredentialResponse.java b/core/java/android/credentials/GetPendingCredentialResponse.java
deleted file mode 100644
index 9005d9d..0000000
--- a/core/java/android/credentials/GetPendingCredentialResponse.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.os.CancellationSignal;
-import android.os.OutcomeReceiver;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.concurrent.Executor;
-
-
-/**
- * A response object that prefetches user app credentials and provides metadata about them. It can
- * then be used to issue the full credential retrieval flow via the
- * {@link #show(Activity, CancellationSignal, Executor, OutcomeReceiver)} method to perform the
- * necessary flows such as consent collection and officially retrieve a credential.
- *
- * @hide
- */
-public final class GetPendingCredentialResponse implements Parcelable {
- private final boolean mHasCredentialResults;
- private final boolean mHasAuthenticationResults;
- private final boolean mHasRemoteResults;
-
- /** Returns true if the user has any candidate credentials, and false otherwise. */
- public boolean hasCredentialResults() {
- return mHasCredentialResults;
- }
-
- /**
- * Returns true if the user has any candidate authentication actions (locked credential
- * supplier), and false otherwise.
- */
- public boolean hasAuthenticationResults() {
- return mHasAuthenticationResults;
- }
-
- /**
- * Returns true if the user has any candidate remote credential results, and false otherwise.
- */
- public boolean hasRemoteResults() {
- return mHasRemoteResults;
- }
-
- /**
- * Launches the necessary flows such as consent collection and credential selection to
- * officially retrieve a credential among the pending credential candidates.
- *
- * @param activity the activity used to launch any UI needed
- * @param cancellationSignal an optional signal that allows for cancelling this call
- * @param executor the callback will take place on this {@link Executor}
- * @param callback the callback invoked when the request succeeds or fails
- */
- public void show(@NonNull Activity activity, @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
- // TODO(b/273308895): implement
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeBoolean(mHasCredentialResults);
- dest.writeBoolean(mHasAuthenticationResults);
- dest.writeBoolean(mHasRemoteResults);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public String toString() {
- return "GetCredentialResponse {" + "credential=" + mHasCredentialResults + "}";
- }
-
- /**
- * Constructs a {@link GetPendingCredentialResponse}.
- *
- * @param hasCredentialResults whether the user has any candidate credentials
- * @param hasAuthenticationResults whether the user has any candidate authentication actions
- * @param hasRemoteResults whether the user has any candidate remote options
- */
- public GetPendingCredentialResponse(boolean hasCredentialResults,
- boolean hasAuthenticationResults, boolean hasRemoteResults) {
- mHasCredentialResults = hasCredentialResults;
- mHasAuthenticationResults = hasAuthenticationResults;
- mHasRemoteResults = hasRemoteResults;
- }
-
- private GetPendingCredentialResponse(@NonNull Parcel in) {
- mHasCredentialResults = in.readBoolean();
- mHasAuthenticationResults = in.readBoolean();
- mHasRemoteResults = in.readBoolean();
- }
-
- public static final @NonNull Creator<GetPendingCredentialResponse> CREATOR = new Creator<>() {
- @Override
- public GetPendingCredentialResponse[] newArray(int size) {
- return new GetPendingCredentialResponse[size];
- }
-
- @Override
- public GetPendingCredentialResponse createFromParcel(@NonNull Parcel in) {
- return new GetPendingCredentialResponse(in);
- }
- };
-}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index af8e7b4..5fde96b 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -27,7 +27,7 @@
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IGetPendingCredentialCallback;
+import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
import android.os.ICancellationSignal;
@@ -41,7 +41,7 @@
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal executeGetPendingCredential(in GetCredentialRequest request, in IGetPendingCredentialCallback callback, String callingPackage);
+ @nullable ICancellationSignal executePrepareGetCredential(in GetCredentialRequest request, in IPrepareGetCredentialCallback prepareGetCredentialCallback, in IGetCredentialCallback getCredentialCallback, String callingPackage);
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
diff --git a/core/java/android/credentials/IGetPendingCredentialCallback.aidl b/core/java/android/credentials/IPrepareGetCredentialCallback.aidl
similarity index 76%
rename from core/java/android/credentials/IGetPendingCredentialCallback.aidl
rename to core/java/android/credentials/IPrepareGetCredentialCallback.aidl
index 4ab0f99..c918aec 100644
--- a/core/java/android/credentials/IGetPendingCredentialCallback.aidl
+++ b/core/java/android/credentials/IPrepareGetCredentialCallback.aidl
@@ -17,14 +17,14 @@
package android.credentials;
import android.app.PendingIntent;
-import android.credentials.GetPendingCredentialResponse;
+import android.credentials.PrepareGetCredentialResponseInternal;
/**
- * Listener for a executeGetPendingCredential request.
+ * Listener for a executePrepareGetCredential request.
*
* @hide
*/
-interface IGetPendingCredentialCallback {
- oneway void onResponse(in GetPendingCredentialResponse response);
+interface IPrepareGetCredentialCallback {
+ oneway void onResponse(in PrepareGetCredentialResponseInternal response);
oneway void onError(String errorType, String message);
}
\ No newline at end of file
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
new file mode 100644
index 0000000..81e9068
--- /dev/null
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.IntentSender;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * A response object that prefetches user app credentials and provides metadata about them. It can
+ * then be used to issue the full credential retrieval flow via the
+ * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection
+ * and credential selection, to officially retrieve a credential.
+ */
+public final class PrepareGetCredentialResponse {
+
+ /**
+ * A handle that represents a pending get-credential operation. Pass this handle to {@link
+ * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal,
+ * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a
+ * credential.
+ */
+ public static final class PendingGetCredentialHandle {
+ @NonNull
+ private final CredentialManager.GetCredentialTransportPendingUseCase
+ mGetCredentialTransport;
+ /**
+ * The pending intent to be launched to finalize the user credential. If null, the callback
+ * will fail with {@link GetCredentialException#TYPE_NO_CREDENTIAL}.
+ */
+ @Nullable
+ private final PendingIntent mPendingIntent;
+
+ /** @hide */
+ PendingGetCredentialHandle(
+ @NonNull CredentialManager.GetCredentialTransportPendingUseCase transport,
+ @Nullable PendingIntent pendingIntent) {
+ mGetCredentialTransport = transport;
+ mPendingIntent = pendingIntent;
+ }
+
+ /** @hide */
+ void show(@NonNull Activity activity, @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+ if (mPendingIntent == null) {
+ executor.execute(() -> callback.onError(
+ new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL)));
+ return;
+ }
+
+ mGetCredentialTransport.setCallback(new GetPendingCredentialInternalCallback() {
+ @Override
+ public void onPendingIntent(PendingIntent pendingIntent) {
+ try {
+ activity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "startIntentSender() failed for intent for show()", e);
+ executor.execute(() -> callback.onError(
+ new GetCredentialException(GetCredentialException.TYPE_UNKNOWN)));
+ }
+ }
+
+ @Override
+ public void onResponse(GetCredentialResponse response) {
+ executor.execute(() -> callback.onResult(response));
+ }
+
+ @Override
+ public void onError(String errorType, String message) {
+ executor.execute(
+ () -> callback.onError(new GetCredentialException(errorType, message)));
+ }
+ });
+
+ try {
+ activity.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "startIntentSender() failed for intent for show()", e);
+ executor.execute(() -> callback.onError(
+ new GetCredentialException(GetCredentialException.TYPE_UNKNOWN)));
+ }
+ }
+ }
+ private static final String TAG = "CredentialManager";
+
+ @NonNull private final PrepareGetCredentialResponseInternal mResponseInternal;
+
+ @NonNull private final PendingGetCredentialHandle mPendingGetCredentialHandle;
+
+ /**
+ * Returns true if the user has any candidate credentials for the given {@code credentialType},
+ * and false otherwise.
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
+ public boolean hasCredentialResults(@NonNull String credentialType) {
+ return mResponseInternal.hasCredentialResults(credentialType);
+ }
+
+ /**
+ * Returns true if the user has any candidate authentication actions (locked credential
+ * supplier), and false otherwise.
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
+ public boolean hasAuthenticationResults() {
+ return mResponseInternal.hasAuthenticationResults();
+ }
+
+ /**
+ * Returns true if the user has any candidate remote credential results, and false otherwise.
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
+ public boolean hasRemoteResults() {
+ return mResponseInternal.hasRemoteResults();
+ }
+
+ /**
+ * Returns a handle that represents this pending get-credential operation. Pass this handle to
+ * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity,
+ * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially
+ * retrieve a credential.
+ */
+ @NonNull
+ public PendingGetCredentialHandle getPendingGetCredentialHandle() {
+ return mPendingGetCredentialHandle;
+ }
+
+ /**
+ * Constructs a {@link PrepareGetCredentialResponse}.
+ *
+ * @param responseInternal whether caller has the permission to query the credential
+ * result metadata
+ * @param getCredentialTransport the transport for the operation to finalaze a credential
+ * @hide
+ */
+ protected PrepareGetCredentialResponse(
+ @NonNull PrepareGetCredentialResponseInternal responseInternal,
+ @NonNull CredentialManager.GetCredentialTransportPendingUseCase
+ getCredentialTransport) {
+ mResponseInternal = responseInternal;
+ mPendingGetCredentialHandle = new PendingGetCredentialHandle(
+ getCredentialTransport, responseInternal.getPendingIntent());
+ }
+
+ /** @hide */
+ protected interface GetPendingCredentialInternalCallback {
+ void onPendingIntent(@NonNull PendingIntent pendingIntent);
+
+ void onResponse(@NonNull GetCredentialResponse response);
+
+ void onError(@NonNull String errorType, @Nullable String message);
+ }
+}
diff --git a/core/java/android/credentials/GetPendingCredentialResponse.aidl b/core/java/android/credentials/PrepareGetCredentialResponseInternal.aidl
similarity index 92%
rename from core/java/android/credentials/GetPendingCredentialResponse.aidl
rename to core/java/android/credentials/PrepareGetCredentialResponseInternal.aidl
index 1cdd637..217dac8 100644
--- a/core/java/android/credentials/GetPendingCredentialResponse.aidl
+++ b/core/java/android/credentials/PrepareGetCredentialResponseInternal.aidl
@@ -16,4 +16,4 @@
package android.credentials;
-parcelable GetPendingCredentialResponse;
\ No newline at end of file
+parcelable PrepareGetCredentialResponseInternal;
\ No newline at end of file
diff --git a/core/java/android/credentials/PrepareGetCredentialResponseInternal.java b/core/java/android/credentials/PrepareGetCredentialResponseInternal.java
new file mode 100644
index 0000000..9afd44f
--- /dev/null
+++ b/core/java/android/credentials/PrepareGetCredentialResponseInternal.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static android.Manifest.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+
+/**
+ * An internal response object that prefetches user app credentials and provides metadata about
+ * them.
+ *
+ * @hide
+ */
+public final class PrepareGetCredentialResponseInternal implements Parcelable {
+ private static final String TAG = "CredentialManager";
+
+ private final boolean mHasQueryApiPermission;
+ @Nullable
+ private final ArraySet<String> mCredentialResultTypes;
+ private final boolean mHasAuthenticationResults;
+ private final boolean mHasRemoteResults;
+ /**
+ * The pending intent to be launched to finalize the user credential. If null, the callback
+ * will fail with {@link GetCredentialException#TYPE_NO_CREDENTIAL}.
+ */
+ @Nullable
+ private final PendingIntent mPendingIntent;
+
+ @Nullable
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /**
+ * Returns true if the user has any candidate credentials for the given {@code credentialType},
+ * and false otherwise.
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
+ public boolean hasCredentialResults(@NonNull String credentialType) {
+ if (!mHasQueryApiPermission) {
+ throw new SecurityException(
+ "caller doesn't have the permission to query credential results");
+ }
+ if (mCredentialResultTypes == null) {
+ return false;
+ }
+ return mCredentialResultTypes.contains(credentialType);
+ }
+
+ /**
+ * Returns true if the user has any candidate authentication actions (locked credential
+ * supplier), and false otherwise.
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
+ public boolean hasAuthenticationResults() {
+ if (!mHasQueryApiPermission) {
+ throw new SecurityException(
+ "caller doesn't have the permission to query authentication results");
+ }
+ return mHasAuthenticationResults;
+ }
+
+ /**
+ * Returns true if the user has any candidate remote credential results, and false otherwise.
+ */
+ @RequiresPermission(CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS)
+ public boolean hasRemoteResults() {
+ if (!mHasQueryApiPermission) {
+ throw new SecurityException(
+ "caller doesn't have the permission to query remote results");
+ }
+ return mHasRemoteResults;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mHasQueryApiPermission);
+ dest.writeArraySet(mCredentialResultTypes);
+ dest.writeBoolean(mHasAuthenticationResults);
+ dest.writeBoolean(mHasRemoteResults);
+ dest.writeTypedObject(mPendingIntent, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Constructs a {@link PrepareGetCredentialResponseInternal}.
+ *
+ * @param hasQueryApiPermission whether caller has the permission to query the credential
+ * result metadata
+ * @param credentialResultTypes a set of credential types that each has candidate credentials
+ * found, or null if the caller doesn't have the permission to
+ * this information
+ * @param hasAuthenticationResults whether the user has any candidate authentication actions, or
+ * false if the caller doesn't have the permission to this
+ * information
+ * @param hasRemoteResults whether the user has any candidate remote options, or false
+ * if the caller doesn't have the permission to this information
+ * @param pendingIntent the pending intent to be launched during
+ * {@link #show(Activity, CancellationSignal, Executor,
+ * OutcomeReceiver)}} to
+ * finalize the user credential
+ * @hide
+ */
+ public PrepareGetCredentialResponseInternal(boolean hasQueryApiPermission,
+ @Nullable Set<String> credentialResultTypes,
+ boolean hasAuthenticationResults, boolean hasRemoteResults,
+ @Nullable PendingIntent pendingIntent) {
+ mHasQueryApiPermission = hasQueryApiPermission;
+ mCredentialResultTypes = new ArraySet<>(credentialResultTypes);
+ mHasAuthenticationResults = hasAuthenticationResults;
+ mHasRemoteResults = hasRemoteResults;
+ mPendingIntent = pendingIntent;
+ }
+
+ private PrepareGetCredentialResponseInternal(@NonNull Parcel in) {
+ mHasQueryApiPermission = in.readBoolean();
+ mCredentialResultTypes = (ArraySet<String>) in.readArraySet(null);
+ mHasAuthenticationResults = in.readBoolean();
+ mHasRemoteResults = in.readBoolean();
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ }
+
+ public static final @NonNull Creator<PrepareGetCredentialResponseInternal> CREATOR =
+ new Creator<>() {
+ @Override
+ public PrepareGetCredentialResponseInternal[] newArray(int size) {
+ return new PrepareGetCredentialResponseInternal[size];
+ }
+
+ @Override
+ public PrepareGetCredentialResponseInternal createFromParcel(@NonNull Parcel in) {
+ return new PrepareGetCredentialResponseInternal(in);
+ }
+ };
+}
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index e9df553..1bc6099 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -661,7 +661,7 @@
*
* <p>Repeating burst requests are a simple way for an application to
* maintain a preview or other continuous stream of frames where each
- * request is different in a predicatable way, without having to continually
+ * request is different in a predictable way, without having to continually
* submit requests through {@link #captureBurst}.</p>
*
* <p>To stop the repeating capture, call {@link #stopRepeating}. Any
@@ -902,7 +902,7 @@
* {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}
* capability in {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}. When this method
* is supported, applications can use it to improve the latency of closing camera or recreating
- * capture session without losing the in progresss capture request outputs.</p>
+ * capture session without losing the in progress capture request outputs.</p>
*
* <p>Offline processing mode and the corresponding {@link CameraOfflineSession} differ from
* a regular online camera capture session in several ways. Successful offline switches will
@@ -1001,7 +1001,7 @@
*
* <p>Note that for common usage scenarios like creating a new session or closing the camera
* device, it is faster to call respective APIs directly (see below for more details) without
- * calling into this method. This API is only useful when application wants to uncofigure the
+ * calling into this method. This API is only useful when application wants to unconfigure the
* camera but keep the device open for later use.</p>
*
* <p>Creating a new capture session with {@link CameraDevice#createCaptureSession}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dfb9cf6..0e4c3c0 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -651,7 +651,7 @@
* @param metadataClass The subclass of CameraMetadata that you want to get the keys for.
* @param keyClass The class of the metadata key, e.g. CaptureRequest.Key.class
* @param filterTags An array of tags to be used for filtering
- * @param includeSynthetic Include public syntethic tag by default.
+ * @param includeSynthetic Include public synthetic tag by default.
*
* @return List of keys supported by this CameraDevice for metadataClass.
*
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 42aa608..5feda78 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -141,7 +141,7 @@
* parameters. All automatic control is disabled (auto-exposure, auto-white
* balance, auto-focus), and post-processing parameters are set to preview
* quality. The manual capture parameters (exposure, sensitivity, and so on)
- * are set to reasonable defaults, but should be overriden by the
+ * are set to reasonable defaults, but should be overridden by the
* application depending on the intended use case.
* This template is guaranteed to be supported on camera devices that support the
* {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR}
@@ -680,7 +680,7 @@
* <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution GPU processing with preview.</td> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution in-app processing with preview.</td> </tr>
- * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processsing.</td> </tr>
+ * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processing.</td> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td> <td>Video recording with maximum-size video snapshot</td> </tr>
* <tr> <td>{@code YUV }</td><td id="rb">{@code 640x480}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Standard video recording plus maximum-resolution in-app processing.</td> </tr>
* <tr> <td>{@code YUV }</td><td id="rb">{@code 640x480}</td> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Preview plus two-input maximum-resolution in-app processing.</td> </tr>
@@ -722,7 +722,7 @@
* <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution GPU processing with preview.</td> </tr>
* <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution in-app processing with preview.</td> </tr>
- * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processsing.</td> </tr>
+ * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processing.</td> </tr>
* </table><br>
* </p>
*
@@ -1137,7 +1137,7 @@
* <tr><th colspan="13">Additional guaranteed combinations for ULTRA_HIGH_RESOLUTION sensors (YUV / PRIV inputs are guaranteed only if YUV / PRIVATE reprocessing are supported)</th></tr>
* <tr> <th colspan="3" id="rb">Input</th> <th colspan="3" id="rb">Target 1</th> <th colspan="3" id="rb">Target 2</th> <th colspan="3" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr>
* <tr> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th><th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th></tr>
- * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td colspan="3" id="rb"></td> <td>RAW remosaic reprocessing with seperate preview</td> </tr>
+ * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td colspan="3" id="rb"></td> <td>RAW remosaic reprocessing with separate preview</td> </tr>
* <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code JPEG / YUV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td>Ultra high res RAW -> JPEG / YUV with seperate preview</td> </tr>
* <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code YUV / PRIV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code JPEG }</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td> Ultra high res PRIV / YUV -> YUV / JPEG reprocessing with seperate preview</td> </tr>
* </table><br>
@@ -1260,7 +1260,7 @@
* settings by calling {@link CaptureRequest.Builder#setPhysicalCameraKey}.</p>
*
* <p>Individual physical camera settings will only be honored for camera session
- * that was initialiazed with corresponding physical camera id output configuration
+ * that was initialized with corresponding physical camera id output configuration
* {@link OutputConfiguration#setPhysicalCameraId} and the same output targets are
* also attached in the request by {@link CaptureRequest.Builder#addTarget}.</p>
*
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 696873f..144b1de 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -256,7 +256,7 @@
/**
* Similar to getCameraIdList(). However, getCamerIdListNoLazy() necessarily communicates with
- * cameraserver in order to get the list of camera ids. This is to faciliate testing since some
+ * cameraserver in order to get the list of camera ids. This is to facilitate testing since some
* camera ids may go 'offline' without callbacks from cameraserver because of changes in
* SYSTEM_CAMERA permissions (though this is not a changeable permission, tests may call
* adopt(drop)ShellPermissionIdentity() and effectively change their permissions). This call
@@ -560,7 +560,7 @@
}
// Query the characteristics of all physical sub-cameras, and combine the multi-resolution
- // stream configurations. Alternatively, for ultra-high resolution camera, direclty use
+ // stream configurations. Alternatively, for ultra-high resolution camera, directly use
// its multi-resolution stream configurations. Note that framework derived formats such as
// HEIC and DEPTH_JPEG aren't supported as multi-resolution input or output formats.
Set<String> physicalCameraIds = info.getPhysicalCameraIds();
@@ -835,7 +835,7 @@
* Opening the same camera ID twice in the same application will similarly cause the
* {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback
* being fired for the {@link CameraDevice} from the first open call and all ongoing tasks
- * being droppped.</p>
+ * being dropped.</p>
*
* <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will
* be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up
@@ -1759,7 +1759,7 @@
private final Set<Set<String>> mConcurrentCameraIdCombinations =
new ArraySet<Set<String>>();
- // Registered availablility callbacks and their executors
+ // Registered availability callbacks and their executors
private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap =
new ArrayMap<AvailabilityCallback, Executor>();
@@ -2846,7 +2846,7 @@
// Tell listeners that the cameras and torch modes are unavailable and schedule a
// reconnection to camera service. When camera service is reconnected, the camera
// and torch statuses will be updated.
- // Iterate from the end to the beginning befcause onStatusChangedLocked removes
+ // Iterate from the end to the beginning because onStatusChangedLocked removes
// entries from the ArrayMap.
for (int i = mDeviceStatus.size() - 1; i >= 0; i--) {
String cameraId = mDeviceStatus.keyAt(i);
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index ea99847..a7e28e2 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -159,7 +159,7 @@
* Optionally, if {@code filterTags} is not {@code null}, then filter out any keys
* whose native {@code tag} is not in {@code filterTags}. The {@code filterTags} array will be
* sorted as a side effect.
- * {@code includeSynthetic} Includes public syntenthic fields by default.
+ * {@code includeSynthetic} Includes public synthetic fields by default.
* </p>
*/
/*package*/ @SuppressWarnings("unchecked")
@@ -2308,7 +2308,7 @@
/**
* <p>An external flash has been turned on.</p>
* <p>It informs the camera device that an external flash has been turned on, and that
- * metering (and continuous focus if active) should be quickly recaculated to account
+ * metering (and continuous focus if active) should be quickly recalculated to account
* for the external flash. Otherwise, this mode acts like ON.</p>
* <p>When the external flash is turned off, AE mode should be changed to one of the
* other available AE modes.</p>
diff --git a/core/java/android/hardware/camera2/CameraOfflineSession.java b/core/java/android/hardware/camera2/CameraOfflineSession.java
index 312559c..c219886 100644
--- a/core/java/android/hardware/camera2/CameraOfflineSession.java
+++ b/core/java/android/hardware/camera2/CameraOfflineSession.java
@@ -152,7 +152,7 @@
*
* <p>Closing a session is idempotent; closing more than once has no effect.</p>
*
- * @throws IllegalStateException if the offline sesion is not ready.
+ * @throws IllegalStateException if the offline session is not ready.
*/
@Override
public abstract void close();
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index cc484ea..1ae2fe1 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -482,7 +482,7 @@
}
private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample
- private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel
+ private static final int BYTES_PER_RGB_PIX = 3; // bytes per pixel
// TIFF tag values needed to map between public API and TIFF spec
private static final int TAG_ORIENTATION_UNKNOWN = 9;
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 34d016a..7c54a9b 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -36,4 +36,5 @@
int surfaceGroupId;
String physicalCameraId;
List<CameraOutputConfig> sharedSurfaceConfigs;
+ boolean isMultiResolutionOutput;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 2fa8b87..cfade55 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -258,6 +258,9 @@
OutputConfiguration cameraOutput = new OutputConfiguration(output.surfaceGroupId,
outputSurface);
+ if (output.isMultiResolutionOutput) {
+ cameraOutput.setMultiResolutionOutput();
+ }
if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) {
cameraOutput.enableSurfaceSharing();
for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) {
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 86c453b..0a4a1f0 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -607,7 +607,7 @@
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM),
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) },
- "Maximum-resolution two-input in-app processsing"),
+ "Maximum-resolution two-input in-app processing"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW),
@@ -891,7 +891,7 @@
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
- "Standard stil image capture"),
+ "Standard still image capture"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p),
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)},
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 857f62d..21540bf 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -165,7 +165,7 @@
* device runs in fixed frame rate. The timestamp is roughly in the same time base as
* {@link android.os.SystemClock#uptimeMillis}.</li>
* <li> For an output surface of MediaRecorder, MediaCodec, or ImageReader with {@link
- * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usge flag, the timestamp base is
+ * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usage flag, the timestamp base is
* {@link #TIMESTAMP_BASE_MONOTONIC}, which is roughly the same time base as
* {@link android.os.SystemClock#uptimeMillis}.</li>
* <li> For all other cases, the timestamp base is {@link #TIMESTAMP_BASE_SENSOR}, the same
@@ -418,7 +418,7 @@
* call, or no non-negative group ID has been set.
* @hide
*/
- void setMultiResolutionOutput() {
+ public void setMultiResolutionOutput() {
if (mIsShared) {
throw new IllegalStateException("Multi-resolution output flag must not be set for " +
"configuration with surface sharing");
@@ -654,7 +654,7 @@
mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
} else {
mSurfaceType = SURFACE_TYPE_UNKNOWN;
- throw new IllegalArgumentException("Unknow surface source class type");
+ throw new IllegalArgumentException("Unknown surface source class type");
}
if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) {
@@ -715,7 +715,7 @@
* The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView,
* MediaRecorder, MediaCodec, or implementation defined ImageReader.</p>
*
- * <p>This function must not be called from OuptutConfigurations created by {@link
+ * <p>This function must not be called from OutputConfigurations created by {@link
* #createInstancesForMultiResolutionOutput}.</p>
*
* @throws IllegalStateException If this OutputConfiguration is created via {@link
@@ -934,7 +934,7 @@
*
* <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
* OutputConfiguration. The only notable exception is the surface associated with
- * the OutputConfigration see {@link #getSurface} which was passed as part of the constructor
+ * the OutputConfiguration see {@link #getSurface} which was passed as part of the constructor
* or was added first in the deferred case
* {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
*
@@ -962,7 +962,7 @@
* for scenarios where the immediate consumer target isn't sufficient to indicate the stream's
* usage.</p>
*
- * <p>The main difference beteween stream use case and capture intent is that the former
+ * <p>The main difference between stream use case and capture intent is that the former
* enables the camera device to optimize camera hardware and software pipelines based on user
* scenarios for each stream, whereas the latter is mainly a hint to camera to decide
* optimal 3A strategy that's applicable to the whole session. The camera device carries out
@@ -1123,7 +1123,7 @@
* CameraCharacteristics#SENSOR_READOUT_TIMESTAMP} is
* {@link CameraMetadata#SENSOR_READOUT_TIMESTAMP_HARDWARE}.</p>
*
- * <p>As long as readout timestamp is supported, if the timestamp base isi
+ * <p>As long as readout timestamp is supported, if the timestamp base is
* {@link #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}, or if the timestamp base is DEFAULT for a
* SurfaceView output, the image timestamps for the output are always readout time regardless
* of whether this function is called.</p>
@@ -1420,9 +1420,9 @@
*/
@Override
public int hashCode() {
- // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise
- // the deferred output configuration will be lost in the camera streammap after the deferred
- // surface is set.
+ // Need ensure that the hashcode remains unchanged after adding a deferred surface.
+ // Otherwise the deferred output configuration will be lost in the camera stream map
+ // after the deferred surface is set.
if (mIsDeferredConfig) {
return HashCodeHelpers.hashCode(
mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
@@ -1446,7 +1446,7 @@
private static final String TAG = "OutputConfiguration";
// A surfaceGroupId counter used for MultiResolutionImageReader. Its value is
- // incremented everytime {@link createInstancesForMultiResolutionOutput} is called.
+ // incremented every time {@link createInstancesForMultiResolutionOutput} is called.
private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0;
private ArrayList<Surface> mSurfaces;
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 5a48176..aabe149 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1790,7 +1790,7 @@
*
* <p>{@code ValidOutputFormatsForInput([in:%s(%d), out:%s(%d), ... %s(%d)],
* ... [in:%s(%d), out:%s(%d), ... %s(%d)])}, where {@code [in:%s(%d), out:%s(%d), ... %s(%d)]}
- * represents an input fomat and its valid output formats.</p>
+ * represents an input format and its valid output formats.</p>
*
* <p>{@code HighSpeedVideoConfigurations([w:%d, h:%d, min_fps:%d, max_fps:%d],
* ... [w:%d, h:%d, min_fps:%d, max_fps:%d])}, where
diff --git a/core/java/android/hardware/display/HdrConversionMode.java b/core/java/android/hardware/display/HdrConversionMode.java
index 49e5eff..5fccb5e 100644
--- a/core/java/android/hardware/display/HdrConversionMode.java
+++ b/core/java/android/hardware/display/HdrConversionMode.java
@@ -29,9 +29,6 @@
/**
* Describes the HDR conversion mode for a device.
*
- * This class is used when user changes the HDR conversion mode of the device via
- * {@link DisplayManager#setHdrConversionMode(HdrConversionMode)}.
- * <p>
* HDR conversion mode has a conversionMode and preferredHdrOutputType. </p><p>
* The conversionMode can be one of:
* {@link HdrConversionMode#HDR_CONVERSION_UNSUPPORTED} : HDR conversion is unsupported. In this
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 490e55b..03d6d91 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -161,9 +161,8 @@
}
/**
- * Returns the recording session associated with this VirtualDisplay. Only used for
+ * Returns the recording session associated with this {@link VirtualDisplay}. Only used for
* recording via {@link MediaProjection}.
- *
* @hide
*/
@Nullable
@@ -438,7 +437,7 @@
*
* <p>For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on
* a 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded up
- * down to a divisor of the physical display. If unset or zero, the virtual display will be
+ * to a divisor of the physical display. If unset or zero, the virtual display will be
* refreshed at the physical display refresh rate.
*
* @see Display#getRefreshRate()
diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java
index 089b159..0ba0c5a 100644
--- a/core/java/android/nfc/tech/IsoDep.java
+++ b/core/java/android/nfc/tech/IsoDep.java
@@ -88,6 +88,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void setTimeout(int timeout) {
try {
@@ -106,6 +107,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public int getTimeout() {
try {
@@ -167,6 +169,7 @@
* @return response bytes received, will not be null
* @throws TagLostException if the tag leaves the field
* @throws IOException if there is an I/O failure, or this operation is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public byte[] transceive(byte[] data) throws IOException {
return transceive(data, true);
@@ -193,6 +196,7 @@
* support.
*
* @return whether the NFC adapter on this device supports extended length APDUs.
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public boolean isExtendedLengthApduSupported() {
try {
diff --git a/core/java/android/nfc/tech/MifareClassic.java b/core/java/android/nfc/tech/MifareClassic.java
index 080e058..26f54e6 100644
--- a/core/java/android/nfc/tech/MifareClassic.java
+++ b/core/java/android/nfc/tech/MifareClassic.java
@@ -597,6 +597,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void setTimeout(int timeout) {
try {
@@ -615,6 +616,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public int getTimeout() {
try {
diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/core/java/android/nfc/tech/MifareUltralight.java
index dec2c65..c0416a3 100644
--- a/core/java/android/nfc/tech/MifareUltralight.java
+++ b/core/java/android/nfc/tech/MifareUltralight.java
@@ -236,6 +236,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void setTimeout(int timeout) {
try {
@@ -255,6 +256,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public int getTimeout() {
try {
diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java
index 39c355a..7d83f15 100644
--- a/core/java/android/nfc/tech/Ndef.java
+++ b/core/java/android/nfc/tech/Ndef.java
@@ -261,6 +261,7 @@
* @throws TagLostException if the tag leaves the field
* @throws IOException if there is an I/O failure, or the operation is canceled
* @throws FormatException if the NDEF Message on the tag is malformed
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public NdefMessage getNdefMessage() throws IOException, FormatException {
checkConnected();
@@ -301,6 +302,7 @@
* @throws TagLostException if the tag leaves the field
* @throws IOException if there is an I/O failure, or the operation is canceled
* @throws FormatException if the NDEF Message to write is malformed
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
checkConnected();
@@ -339,6 +341,7 @@
* <p>Does not cause any RF activity and does not block.
*
* @return true if it is possible to make this tag read-only
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public boolean canMakeReadOnly() {
INfcTag tagService = mTag.getTagService();
@@ -370,6 +373,7 @@
* @return true on success, false if it is not possible to make this tag read-only
* @throws TagLostException if the tag leaves the field
* @throws IOException if there is an I/O failure, or the operation is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public boolean makeReadOnly() throws IOException {
checkConnected();
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java
index 4175cd0..f19d302 100644
--- a/core/java/android/nfc/tech/NdefFormatable.java
+++ b/core/java/android/nfc/tech/NdefFormatable.java
@@ -111,6 +111,7 @@
* @throws TagLostException if the tag leaves the field
* @throws IOException if there is an I/O failure, or the operation is canceled
* @throws FormatException if the NDEF Message to write is malformed
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void formatReadOnly(NdefMessage firstMessage) throws IOException, FormatException {
format(firstMessage, true);
diff --git a/core/java/android/nfc/tech/NfcA.java b/core/java/android/nfc/tech/NfcA.java
index 88730f9..7e66483 100644
--- a/core/java/android/nfc/tech/NfcA.java
+++ b/core/java/android/nfc/tech/NfcA.java
@@ -141,6 +141,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void setTimeout(int timeout) {
try {
@@ -159,6 +160,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public int getTimeout() {
try {
diff --git a/core/java/android/nfc/tech/NfcF.java b/core/java/android/nfc/tech/NfcF.java
index 4487121..2ccd388 100644
--- a/core/java/android/nfc/tech/NfcF.java
+++ b/core/java/android/nfc/tech/NfcF.java
@@ -145,6 +145,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param timeout timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void setTimeout(int timeout) {
try {
@@ -163,6 +164,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @return timeout value in milliseconds
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public int getTimeout() {
try {
diff --git a/core/java/android/nfc/tech/TagTechnology.java b/core/java/android/nfc/tech/TagTechnology.java
index 0e2c7c1..839fe42 100644
--- a/core/java/android/nfc/tech/TagTechnology.java
+++ b/core/java/android/nfc/tech/TagTechnology.java
@@ -176,6 +176,7 @@
* @see #close()
* @throws TagLostException if the tag leaves the field
* @throws IOException if there is an I/O failure, or connect is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void connect() throws IOException;
@@ -193,6 +194,7 @@
* @see #close()
* @throws TagLostException if the tag leaves the field
* @throws IOException if there is an I/O failure, or connect is canceled
+ * @throws SecurityException if the tag object is reused after the tag has left the field
* @hide
*/
public void reconnect() throws IOException;
@@ -205,6 +207,7 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @see #connect()
+ * @throws SecurityException if the tag object is reused after the tag has left the field
*/
public void close() throws IOException;
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index b478a379..af09a06 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1295,19 +1295,31 @@
* value, such as 256MB or 32GB. This avoids showing weird values like
* "29.5GB" in UI.
*
+ * Some storage devices are still using GiB (powers of 1024) over
+ * GB (powers of 1000) measurements and this method takes it into account.
+ *
+ * Round ranges:
+ * ...
+ * [256 GiB + 1; 512 GiB] -> 512 GB
+ * [512 GiB + 1; 1 TiB] -> 1 TB
+ * [1 TiB + 1; 2 TiB] -> 2 TB
+ * etc
+ *
* @hide
*/
public static long roundStorageSize(long size) {
long val = 1;
- long pow = 1;
- while ((val * pow) < size) {
+ long kiloPow = 1;
+ long kibiPow = 1;
+ while ((val * kibiPow) < size) {
val <<= 1;
if (val > 512) {
val = 1;
- pow *= 1000;
+ kibiPow *= 1024;
+ kiloPow *= 1000;
}
}
- return val * pow;
+ return val * kiloPow;
}
private static long toBytes(long value, String unit) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index fcebb45..8e1d2d6 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -134,7 +134,7 @@
boolean isUserForeground(int userId);
boolean isUserVisible(int userId);
int[] getVisibleUsers();
- int getDisplayIdAssignedToUser();
+ int getMainDisplayIdAssignedToUser();
boolean isUserNameSet(int userId);
boolean hasRestrictedProfiles(int userId);
boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userId, in IntentSender target, int flags);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 1673ade..e784c26 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4499,17 +4499,28 @@
public void writeToParcel(Parcel out) {
Parcel source = mSource;
if (source != null) {
- out.appendFrom(source, mPosition, mLength);
- } else {
- out.writeValue(mObject);
+ synchronized (source) {
+ if (mSource != null) {
+ out.appendFrom(source, mPosition, mLength);
+ return;
+ }
+ }
}
+
+ out.writeValue(mObject);
}
public boolean hasFileDescriptors() {
Parcel source = mSource;
- return (source != null)
- ? source.hasFileDescriptors(mPosition, mLength)
- : Parcel.hasFileDescriptors(mObject);
+ if (source != null) {
+ synchronized (source) {
+ if (mSource != null) {
+ return source.hasFileDescriptors(mPosition, mLength);
+ }
+ }
+ }
+
+ return Parcel.hasFileDescriptors(mObject);
}
@Override
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
index 221e89a..310ceb3 100644
--- a/core/java/android/os/PermissionEnforcer.java
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -18,9 +18,11 @@
import android.annotation.NonNull;
import android.annotation.SystemService;
+import android.app.AppOpsManager;
import android.content.AttributionSource;
import android.content.Context;
import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
import android.permission.PermissionCheckerManager;
/**
@@ -40,6 +42,7 @@
public class PermissionEnforcer {
private final Context mContext;
+ private static final String ACCESS_DENIED = "Access denied, requires: ";
/** Protected constructor. Allows subclasses to instantiate an object
* without using a Context.
@@ -59,11 +62,42 @@
mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */);
}
+ @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck")
+ @PermissionCheckerManager.PermissionResult
+ protected int checkPermission(@NonNull String permission, int pid, int uid) {
+ if (mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return PermissionCheckerManager.PERMISSION_GRANTED;
+ }
+ return PermissionCheckerManager.PERMISSION_HARD_DENIED;
+ }
+
+ private boolean anyAppOps(@NonNull String[] permissions) {
+ for (String permission : permissions) {
+ if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void enforcePermission(@NonNull String permission, @NonNull
AttributionSource source) throws SecurityException {
int result = checkPermission(permission, source);
if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
- throw new SecurityException("Access denied, requires: " + permission);
+ throw new SecurityException(ACCESS_DENIED + permission);
+ }
+ }
+
+ public void enforcePermission(@NonNull String permission, int pid, int uid)
+ throws SecurityException {
+ if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ AttributionSource source = new AttributionSource(uid, null, null);
+ enforcePermission(permission, source);
+ return;
+ }
+ int result = checkPermission(permission, pid, uid);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException(ACCESS_DENIED + permission);
}
}
@@ -72,7 +106,23 @@
for (String permission : permissions) {
int result = checkPermission(permission, source);
if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
- throw new SecurityException("Access denied, requires: allOf={"
+ throw new SecurityException(ACCESS_DENIED + "allOf={"
+ + String.join(", ", permissions) + "}");
+ }
+ }
+ }
+
+ public void enforcePermissionAllOf(@NonNull String[] permissions,
+ int pid, int uid) throws SecurityException {
+ if (anyAppOps(permissions)) {
+ AttributionSource source = new AttributionSource(uid, null, null);
+ enforcePermissionAllOf(permissions, source);
+ return;
+ }
+ for (String permission : permissions) {
+ int result = checkPermission(permission, pid, uid);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException(ACCESS_DENIED + "allOf={"
+ String.join(", ", permissions) + "}");
}
}
@@ -86,7 +136,24 @@
return;
}
}
- throw new SecurityException("Access denied, requires: anyOf={"
+ throw new SecurityException(ACCESS_DENIED + "anyOf={"
+ + String.join(", ", permissions) + "}");
+ }
+
+ public void enforcePermissionAnyOf(@NonNull String[] permissions,
+ int pid, int uid) throws SecurityException {
+ if (anyAppOps(permissions)) {
+ AttributionSource source = new AttributionSource(uid, null, null);
+ enforcePermissionAnyOf(permissions, source);
+ return;
+ }
+ for (String permission : permissions) {
+ int result = checkPermission(permission, pid, uid);
+ if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException(ACCESS_DENIED + "anyOf={"
+ String.join(", ", permissions) + "}");
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 86e678d..b3604da 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3056,14 +3056,14 @@
}
/**
- * See {@link com.android.server.pm.UserManagerInternal#getDisplayAssignedToUser(int)}.
+ * See {@link com.android.server.pm.UserManagerInternal#getMainDisplayAssignedToUser(int)}.
*
* @hide
*/
@TestApi
- public int getDisplayIdAssignedToUser() {
+ public int getMainDisplayIdAssignedToUser() {
try {
- return mService.getDisplayIdAssignedToUser();
+ return mService.getMainDisplayIdAssignedToUser();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index b117a9a..6f2a915 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -141,12 +141,15 @@
private int mRingerMode;
private int mZenMode;
private boolean mPlaySample;
+ private final boolean mDeviceHasProductStrategies;
private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
private static final int MSG_STOP_SAMPLE = 2;
private static final int MSG_INIT_SAMPLE = 3;
+ private static final int MSG_UPDATE_SLIDER_MAYBE_LATER = 4;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
+ private static final int CHECK_UPDATE_SLIDER_LATER_MS = 500;
private static final long SET_STREAM_VOLUME_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
private static final long START_SAMPLE_DELAY_MS = TimeUnit.MILLISECONDS.toMillis(500);
private static final long DURATION_TO_START_DELAYING = TimeUnit.MILLISECONDS.toMillis(2000);
@@ -170,6 +173,7 @@
boolean playSample) {
mContext = context;
mAudioManager = context.getSystemService(AudioManager.class);
+ mDeviceHasProductStrategies = hasAudioProductStrategies();
mNotificationManager = context.getSystemService(NotificationManager.class);
mNotificationPolicy = mNotificationManager.getConsolidatedNotificationPolicy();
mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
@@ -186,7 +190,7 @@
}
mZenMode = mNotificationManager.getZenMode();
- if (hasAudioProductStrategies()) {
+ if (mDeviceHasProductStrategies) {
mVolumeGroupId = getVolumeGroupIdForLegacyStreamType(mStreamType);
mAttributes = getAudioAttributesForLegacyStreamType(
mStreamType);
@@ -213,6 +217,12 @@
mDefaultUri = defaultUri;
}
+ /**
+ * DO NOT CALL every time this is needed, use once in constructor,
+ * read mDeviceHasProductStrategies instead
+ * @return true if stream types are used for volume management, false if volume groups are
+ * used for volume management
+ */
private boolean hasAudioProductStrategies() {
return AudioManager.getAudioProductStrategies().size() > 0;
}
@@ -330,6 +340,9 @@
onInitSample();
}
break;
+ case MSG_UPDATE_SLIDER_MAYBE_LATER:
+ onUpdateSliderMaybeLater();
+ break;
default:
Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
}
@@ -353,6 +366,21 @@
: isDelay() ? START_SAMPLE_DELAY_MS : 0);
}
+ private void onUpdateSliderMaybeLater() {
+ if (isDelay()) {
+ postUpdateSliderMaybeLater();
+ return;
+ }
+ updateSlider();
+ }
+
+ private void postUpdateSliderMaybeLater() {
+ if (mHandler == null) return;
+ mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SLIDER_MAYBE_LATER),
+ CHECK_UPDATE_SLIDER_LATER_MS);
+ }
+
// After stop volume it needs to add a small delay when playing volume or set stream.
// It is because the call volume is from the earpiece and the alarm/ring/media
// is from the speaker. If play the alarm volume or set alarm stream right after stop
@@ -422,7 +450,7 @@
postStopSample();
mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
mReceiver.setListening(false);
- if (hasAudioProductStrategies()) {
+ if (mDeviceHasProductStrategies) {
unregisterVolumeGroupCb();
}
mSeekBar.setOnSeekBarChangeListener(null);
@@ -442,7 +470,7 @@
System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]),
false, mVolumeObserver);
mReceiver.setListening(true);
- if (hasAudioProductStrategies()) {
+ if (mDeviceHasProductStrategies) {
registerVolumeGroupCb();
}
}
@@ -466,6 +494,7 @@
mLastProgress = progress;
mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
mHandler.removeMessages(MSG_START_SAMPLE);
+ mHandler.removeMessages(MSG_UPDATE_SLIDER_MAYBE_LATER);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME),
isDelay() ? SET_STREAM_VOLUME_DELAY_MS : 0);
}
@@ -609,7 +638,7 @@
if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
- if (hasAudioProductStrategies() && !isDelay()) {
+ if (mDeviceHasProductStrategies && !isDelay()) {
updateVolumeSlider(streamType, streamValue);
}
} else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
@@ -621,9 +650,16 @@
}
} else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (hasAudioProductStrategies() && !isDelay()) {
- int streamVolume = mAudioManager.getStreamVolume(streamType);
- updateVolumeSlider(streamType, streamVolume);
+
+ if (mDeviceHasProductStrategies) {
+ if (isDelay()) {
+ // not the right time to update the sliders, try again later
+ postUpdateSliderMaybeLater();
+ } else {
+ int streamVolume = mAudioManager.getStreamVolume(streamType);
+ updateVolumeSlider(streamType, streamVolume);
+ }
+
} else {
int volumeGroup = getVolumeGroupIdForLegacyStreamType(streamType);
if (volumeGroup != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 123f480..89b768d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14777,23 +14777,6 @@
"adaptive_battery_management_enabled";
/**
- * Whether or not apps are allowed into the
- * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
- * Type: int (0 for false, 1 for true)
- * Default: {@value #DEFAULT_ENABLE_RESTRICTED_BUCKET}
- *
- * @hide
- */
- @Readable
- public static final String ENABLE_RESTRICTED_BUCKET = "enable_restricted_bucket";
-
- /**
- * @see #ENABLE_RESTRICTED_BUCKET
- * @hide
- */
- public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 1;
-
- /**
* Whether or not app auto restriction is enabled. When it is enabled, settings app will
* auto restrict the app if it has bad behavior (e.g. hold wakelock for long time).
*
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 3190c69..8069414 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -33,17 +33,28 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -58,6 +69,11 @@
public final class CredentialProviderInfoFactory {
private static final String TAG = "CredentialProviderInfoFactory";
+ private static final String TAG_CREDENTIAL_PROVIDER = "credential-provider";
+ private static final String TAG_CAPABILITIES = "capabilities";
+ private static final String TAG_CAPABILITY = "capability";
+ private static final String ATTR_NAME = "name";
+
/**
* Constructs an information instance of the credential provider.
*
@@ -118,8 +134,8 @@
}
/**
- * Constructs an information instance of the credential provider for testing purposes. Does
- * not run any verifications and passes parameters as is.
+ * Constructs an information instance of the credential provider for testing purposes. Does not
+ * run any verifications and passes parameters as is.
*/
@VisibleForTesting
public static CredentialProviderInfo createForTests(
@@ -134,7 +150,6 @@
.setSystemProvider(isSystemProvider)
.addCapabilities(capabilities)
.build();
-
}
private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -194,10 +209,8 @@
private static CredentialProviderInfo.Builder populateMetadata(
@NonNull Context context, ServiceInfo serviceInfo) {
requireNonNull(context, "context must not be null");
-
- final CredentialProviderInfo.Builder builder =
- new CredentialProviderInfo.Builder(serviceInfo);
final PackageManager pm = context.getPackageManager();
+ CredentialProviderInfo.Builder builder = new CredentialProviderInfo.Builder(serviceInfo);
// 1. Get the metadata for the service.
final Bundle metadata = serviceInfo.metaData;
@@ -206,46 +219,155 @@
return builder;
}
- // 2. Extract the capabilities from the bundle.
+ // 2. Get the resources for the application.
+ Resources resources = null;
try {
- Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
- if (metadata == null || resources == null) {
- Log.i(TAG, "populateMetadata - resources is null");
- return builder;
- }
-
- builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo));
+ resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, e.getMessage());
+ Log.e(TAG, "Failed to get app resources", e);
+ }
+
+ // 3. Stop if we are missing data.
+ if (metadata == null || resources == null) {
+ Log.i(TAG, "populateMetadata - resources is null");
+ return builder;
+ }
+
+ // 4. Extract the XML metadata.
+ try {
+ builder = extractXmlMetadata(context, builder, serviceInfo, pm, resources);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get XML metadata", e);
+ }
+
+ // 5. Extract the legacy metadata.
+ try {
+ builder.addCapabilities(
+ populateLegacyProviderCapabilities(resources, metadata, serviceInfo));
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get legacy metadata ", e);
}
return builder;
}
- private static List<String> populateProviderCapabilities(
- Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
- List<String> output = new ArrayList<>();
- String[] capabilities = new String[0];
-
- try {
- capabilities =
- resources.getStringArray(
- metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, "Failed to get capabilities: " + e.getMessage());
+ private static CredentialProviderInfo.Builder extractXmlMetadata(
+ @NonNull Context context,
+ @NonNull CredentialProviderInfo.Builder builder,
+ @NonNull ServiceInfo serviceInfo,
+ @NonNull PackageManager pm,
+ @NonNull Resources resources) {
+ final XmlResourceParser parser =
+ serviceInfo.loadXmlMetaData(pm, CredentialProviderService.SERVICE_META_DATA);
+ if (parser == null) {
+ return builder;
}
- if (capabilities == null || capabilities.length == 0) {
- Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
+ try {
+ int type = 0;
+ while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+ type = parser.next();
+ }
+
+ // This is matching a <credential-provider /> tag in the XML.
+ if (TAG_CREDENTIAL_PROVIDER.equals(parser.getName())) {
+ final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+ TypedArray afsAttributes = null;
+ try {
+ afsAttributes =
+ resources.obtainAttributes(
+ allAttributes,
+ com.android.internal.R.styleable.CredentialProvider);
+ builder.setSettingsSubtitle(
+ afsAttributes.getString(
+ R.styleable.CredentialProvider_settingsSubtitle));
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get XML attr", e);
+ } finally {
+ if (afsAttributes != null) {
+ afsAttributes.recycle();
+ }
+ }
+ builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources));
+ } else {
+ Log.e(TAG, "Meta-data does not start with credential-provider-service tag");
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Error parsing credential provider service meta-data", e);
+ }
+
+ return builder;
+ }
+
+ private static Set<String> parseXmlProviderOuterCapabilities(
+ XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
+ final Set<String> capabilities = new HashSet<>();
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (TAG_CAPABILITIES.equals(parser.getName())) {
+ capabilities.addAll(parseXmlProviderInnerCapabilities(parser, resources));
+ }
+ }
+
+ return capabilities;
+ }
+
+ private static List<String> parseXmlProviderInnerCapabilities(
+ XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException {
+ List<String> capabilities = new ArrayList<>();
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (TAG_CAPABILITY.equals(parser.getName())) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ if (name != null && !TextUtils.isEmpty(name)) {
+ capabilities.add(name);
+ }
+ }
+ }
+
+ return capabilities;
+ }
+
+ private static Set<String> populateLegacyProviderCapabilities(
+ Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
+ Set<String> output = new HashSet<>();
+ Set<String> capabilities = new HashSet<>();
+
+ try {
+ String[] discovered =
+ resources.getStringArray(
+ metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
+ if (discovered != null) {
+ capabilities.addAll(Arrays.asList(discovered));
+ }
+ } catch (Resources.NotFoundException | NullPointerException e) {
+ Log.e(TAG, "Failed to get capabilities: ", e);
+ }
+
+ if (capabilities.size() == 0) {
+ Log.e(TAG, "No capabilities found for provider:" + serviceInfo);
return output;
}
for (String capability : capabilities) {
- if (capability.isEmpty()) {
- Slog.e(TAG, "Skipping empty capability");
+ if (capability == null || capability.isEmpty()) {
+ Log.w(TAG, "Skipping empty/null capability");
continue;
}
- Slog.e(TAG, "Capabilities found for provider: " + capability);
+ Log.i(TAG, "Capabilities found for provider: " + capability);
output.add(capability);
}
return output;
@@ -361,7 +483,8 @@
try {
DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class);
- return dpm.getCredentialManagerPolicy();
+ PackagePolicy pp = dpm.getCredentialManagerPolicy();
+ return pp;
} catch (SecurityException e) {
// If the current user is not enrolled in DPM then this can throw a security error.
Log.e(TAG, "Failed to get device policy: " + e);
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index e88474d..6824159 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -156,8 +156,43 @@
private static final String TAG = "CredProviderService";
+ /**
+ * The list of capabilities exposed by a credential provider.
+ *
+ * @deprecated Replaced with {@link android.service.credentials#SERVICE_META_DATA}
+ */
+ @Deprecated
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+ /**
+ * Name under which a Credential Provider service component publishes information
+ * about itself. This meta-data must reference an XML resource containing
+ * an
+ * <code><{@link android.R.styleable#CredentialProvider credential-provider}></code>
+ * tag.
+ *
+ * For example (AndroidManifest.xml):
+ * <code>
+ * <meta-data
+ * android:name="android.credentials.provider"
+ * android:resource="@xml/provider"/>
+ * </code>
+ *
+ * For example (xml/provider.xml):
+ * <code>
+ * <credential-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:settingsSubtitle="@string/providerSubtitle">
+ * <capabilities>
+ * <capability>@string/passwords</capability>
+ * <capability>@string/passkeys</capability>
+ * </capabilities>
+ * <string name="passwords">android.credentials.TYPE_PASSWORD_CREDENTIAL</string>
+ * <string name="passkeys">android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL</string>
+ * </credential-provider>
+ * </code>
+ */
+ public static final String SERVICE_META_DATA = "android.credentials.provider";
+
/** @hide */
public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY =
"android.credentials.testsystemprovider";
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index a1c5593..ff6dffd 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -293,6 +293,9 @@
*
* <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive)
* and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid.
+ *
+ * <p> This value is unitless. The relation between this value and the real audio signal
+ * power measured in decibels depends on the hotword detection service implementation.
*/
private final int mBackgroundAudioPower;
private static int defaultBackgroundAudioPower() {
@@ -749,6 +752,9 @@
*
* <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive)
* and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid.
+ *
+ * <p> This value is unitless. The relation between this value and the real audio signal
+ * power measured in decibels depends on the hotword detection service implementation.
*/
@DataClass.Generated.Member
public int getBackgroundAudioPower() {
@@ -1090,6 +1096,9 @@
*
* <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive)
* and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid.
+ *
+ * <p> This value is unitless. The relation between this value and the real audio signal
+ * power measured in decibels depends on the hotword detection service implementation.
*/
@DataClass.Generated.Member
public @NonNull Builder setBackgroundAudioPower(int value) {
@@ -1165,7 +1174,7 @@
}
@DataClass.Generated(
- time = 1679081102676L,
+ time = 1679517179528L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\npublic static final int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate final int mBackgroundAudioPower\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static int defaultBackgroundAudioPower()\npublic static int getMaxBackgroundAudioPower()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 8d95c02..0b947fc 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -60,6 +60,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -97,6 +98,7 @@
import android.window.ClientWindowFrames;
import android.window.ScreenCapture;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.HandlerCaller;
import com.android.internal.view.BaseIWindow;
@@ -104,9 +106,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -167,11 +170,12 @@
private static final int MSG_REPORT_SHOWN = 10150;
private static final int MSG_UPDATE_DIMMING = 10200;
private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
- private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
- Float.NEGATIVE_INFINITY);
+ /** limit calls to {@link Engine#onComputeColors} to at most once per second */
private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
- private static final int PROCESS_LOCAL_COLORS_INTERVAL_MS = 1000;
+
+ /** limit calls to {@link Engine#processLocalColorsInternal} to at most once per 2 seconds */
+ private static final int PROCESS_LOCAL_COLORS_INTERVAL_MS = 2000;
private static final boolean ENABLE_WALLPAPER_DIMMING =
SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true);
@@ -180,6 +184,9 @@
private final ArrayMap<IBinder, IWallpaperEngineWrapper> mActiveEngines = new ArrayMap<>();
+ private Handler mBackgroundHandler;
+ private HandlerThread mBackgroundThread;
+
static final class WallpaperCommand {
String action;
int x;
@@ -198,14 +205,6 @@
*/
public class Engine {
IWallpaperEngineWrapper mIWallpaperEngine;
- final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
- final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
-
- // 2D matrix [x][y] to represent a page of a portion of a window
- EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
- Bitmap mLastScreenshot;
- int mLastWindowPage = -1;
- private boolean mResetWindowPages;
// Copies from mIWallpaperEngine.
HandlerCaller mCaller;
@@ -266,11 +265,34 @@
final Object mLock = new Object();
boolean mOffsetMessageEnqueued;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- float mPendingXOffset;
- float mPendingYOffset;
- float mPendingXOffsetStep;
- float mPendingYOffsetStep;
+ @GuardedBy("mLock")
+ private float mPendingXOffset;
+ @GuardedBy("mLock")
+ private float mPendingYOffset;
+ @GuardedBy("mLock")
+ private float mPendingXOffsetStep;
+ @GuardedBy("mLock")
+ private float mPendingYOffsetStep;
+
+ /**
+ * local color extraction related fields. When a user calls `addLocalColorAreas`
+ */
+ @GuardedBy("mLock")
+ private final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
+
+ @GuardedBy("mLock")
+ private final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
+ private long mLastProcessLocalColorsTimestamp;
+ private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false);
+ private int mPixelCopyCount = 0;
+ // 2D matrix [x][y] to represent a page of a portion of a window
+ @GuardedBy("mLock")
+ private EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
+ private Bitmap mLastScreenshot;
+ private boolean mResetWindowPages;
+
boolean mPendingSync;
MotionEvent mPendingMove;
boolean mIsInAmbientMode;
@@ -279,12 +301,8 @@
private long mLastColorInvalidation;
private final Runnable mNotifyColorsChanged = this::notifyColorsChanged;
- // used to throttle processLocalColors
- private long mLastProcessLocalColorsTimestamp;
- private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false);
private final Supplier<Long> mClockFunction;
private final Handler mHandler;
-
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
@@ -854,7 +872,7 @@
+ "was not established.");
}
mResetWindowPages = true;
- processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ processLocalColors();
} catch (RemoteException e) {
Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
}
@@ -1389,10 +1407,9 @@
mIsCreating = false;
mSurfaceCreated = true;
if (redrawNeeded) {
- resetWindowPages();
mSession.finishDrawing(mWindow, null /* postDrawTransaction */,
Integer.MAX_VALUE);
- processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ processLocalColors();
}
reposition();
reportEngineShown(shouldWaitForEngineShown());
@@ -1536,7 +1553,7 @@
if (!mDestroyed) {
mVisible = visible;
reportVisibility(false);
- if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
+ if (mReportedVisible) processLocalColors();
} else {
AnimationHandler.requestAnimatorsEnabled(visible, this);
}
@@ -1640,14 +1657,14 @@
}
// setup local color extraction data
- processLocalColors(xOffset, xOffsetStep);
+ processLocalColors();
}
/**
* Thread-safe util to call {@link #processLocalColorsInternal} with a minimum interval of
* {@link #PROCESS_LOCAL_COLORS_INTERVAL_MS} between two calls.
*/
- private void processLocalColors(float xOffset, float xOffsetStep) {
+ private void processLocalColors() {
if (mProcessLocalColorsPending.compareAndSet(false, true)) {
final long now = mClockFunction.get();
final long timeSinceLastColorProcess = now - mLastProcessLocalColorsTimestamp;
@@ -1657,80 +1674,98 @@
mHandler.postDelayed(() -> {
mLastProcessLocalColorsTimestamp = now + timeToWait;
mProcessLocalColorsPending.set(false);
- processLocalColorsInternal(xOffset, xOffsetStep);
+ processLocalColorsInternal();
}, timeToWait);
}
}
- private void processLocalColorsInternal(float xOffset, float xOffsetStep) {
- // implemented by the wallpaper
+ /**
+ * Default implementation of the local color extraction.
+ * This will take a screenshot of the whole wallpaper on the main thread.
+ * Then, in a background thread, for each launcher page, for each area that needs color
+ * extraction in this page, creates a sub-bitmap and call {@link WallpaperColors#fromBitmap}
+ * to extract the colors. Every time a launcher page has been processed, call
+ * {@link #notifyLocalColorsChanged} with the color and areas of this page.
+ */
+ private void processLocalColorsInternal() {
if (supportsLocalColorExtraction()) return;
- if (DEBUG) {
- Log.d(TAG, "processLocalColors " + xOffset + " of step "
- + xOffsetStep);
- }
- //below is the default implementation
- if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN
- || !mSurfaceHolder.getSurface().isValid()) return;
- int xCurrentPage;
+ float xOffset;
+ float xOffsetStep;
+ float wallpaperDimAmount;
+ int xPage;
int xPages;
- if (!validStep(xOffsetStep)) {
- if (DEBUG) {
- Log.w(TAG, "invalid offset step " + xOffsetStep);
- }
- xOffset = 0;
- xOffsetStep = 1;
- xCurrentPage = 0;
- xPages = 1;
- } else {
- xPages = Math.round(1 / xOffsetStep) + 1;
- xOffsetStep = (float) 1 / (float) xPages;
- float shrink = (float) (xPages - 1) / (float) xPages;
- xOffset *= shrink;
- xCurrentPage = Math.round(xOffset / xOffsetStep);
- }
- if (DEBUG) {
- Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage);
- Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
- }
-
- float finalXOffsetStep = xOffsetStep;
- float finalXOffset = xOffset;
-
- Trace.beginSection("WallpaperService#processLocalColors");
- resetWindowPages();
- int xPage = xCurrentPage;
+ Set<RectF> areas;
EngineWindowPage current;
- if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
- mWindowPages = new EngineWindowPage[xPages];
- initWindowPages(mWindowPages, finalXOffsetStep);
- }
- if (mLocalColorsToAdd.size() != 0) {
- for (RectF colorArea : mLocalColorsToAdd) {
- if (!isValid(colorArea)) continue;
- mLocalColorAreas.add(colorArea);
- int colorPage = getRectFPage(colorArea, finalXOffsetStep);
- EngineWindowPage currentPage = mWindowPages[colorPage];
- currentPage.setLastUpdateTime(0);
- currentPage.removeColor(colorArea);
- }
- mLocalColorsToAdd.clear();
- }
- if (xPage >= mWindowPages.length) {
+
+ synchronized (mLock) {
+ xOffset = mPendingXOffset;
+ xOffsetStep = mPendingXOffsetStep;
+ wallpaperDimAmount = mWallpaperDimAmount;
+
if (DEBUG) {
- Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
- Log.e(TAG, "error on page " + xPage + " out of " + xPages);
- Log.e(TAG,
- "error on xOffsetStep " + finalXOffsetStep
- + " xOffset " + finalXOffset);
+ Log.d(TAG, "processLocalColors " + xOffset + " of step "
+ + xOffsetStep);
}
- xPage = mWindowPages.length - 1;
+ if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN
+ || !mSurfaceHolder.getSurface().isValid()) return;
+ int xCurrentPage;
+ if (!validStep(xOffsetStep)) {
+ if (DEBUG) {
+ Log.w(TAG, "invalid offset step " + xOffsetStep);
+ }
+ xOffset = 0;
+ xOffsetStep = 1;
+ xCurrentPage = 0;
+ xPages = 1;
+ } else {
+ xPages = Math.round(1 / xOffsetStep) + 1;
+ xOffsetStep = (float) 1 / (float) xPages;
+ float shrink = (float) (xPages - 1) / (float) xPages;
+ xOffset *= shrink;
+ xCurrentPage = Math.round(xOffset / xOffsetStep);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage);
+ Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+ }
+
+ float finalXOffsetStep = xOffsetStep;
+ float finalXOffset = xOffset;
+
+ resetWindowPages();
+ xPage = xCurrentPage;
+ if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
+ mWindowPages = new EngineWindowPage[xPages];
+ initWindowPages(mWindowPages, finalXOffsetStep);
+ }
+ if (mLocalColorsToAdd.size() != 0) {
+ for (RectF colorArea : mLocalColorsToAdd) {
+ if (!isValid(colorArea)) continue;
+ mLocalColorAreas.add(colorArea);
+ int colorPage = getRectFPage(colorArea, finalXOffsetStep);
+ EngineWindowPage currentPage = mWindowPages[colorPage];
+ currentPage.setLastUpdateTime(0);
+ currentPage.removeColor(colorArea);
+ }
+ mLocalColorsToAdd.clear();
+ }
+ if (xPage >= mWindowPages.length) {
+ if (DEBUG) {
+ Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
+ Log.e(TAG, "error on page " + xPage + " out of " + xPages);
+ Log.e(TAG,
+ "error on xOffsetStep " + finalXOffsetStep
+ + " xOffset " + finalXOffset);
+ }
+ xPage = mWindowPages.length - 1;
+ }
+ current = mWindowPages[xPage];
+ areas = new HashSet<>(current.getAreas());
}
- current = mWindowPages[xPage];
- updatePage(current, xPage, xPages, finalXOffsetStep);
- Trace.endSection();
+ updatePage(current, areas, xPage, xPages, wallpaperDimAmount);
}
+ @GuardedBy("mLock")
private void initWindowPages(EngineWindowPage[] windowPages, float step) {
for (int i = 0; i < windowPages.length; i++) {
windowPages[i] = new EngineWindowPage();
@@ -1747,16 +1782,16 @@
}
}
- void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
- float xOffsetStep) {
+ void updatePage(EngineWindowPage currentPage, Set<RectF> areas, int pageIndx, int numPages,
+ float wallpaperDimAmount) {
+
// in case the clock is zero, we start with negative time
long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION;
long lapsed = current - currentPage.getLastUpdateTime();
// Always update the page when the last update time is <= 0
// This is important especially when the device first boots
- if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) {
- return;
- }
+ if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
+
Surface surface = mSurfaceHolder.getSurface();
if (!surface.isValid()) return;
boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
@@ -1769,43 +1804,59 @@
Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height);
return;
}
+ final String pixelCopySectionName = "WallpaperService#pixelCopy";
+ final int pixelCopyCount = mPixelCopyCount++;
+ Trace.beginAsyncSection(pixelCopySectionName, pixelCopyCount);
Bitmap screenShot = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
final Bitmap finalScreenShot = screenShot;
- Trace.beginSection("WallpaperService#pixelCopy");
- PixelCopy.request(surface, screenShot, (res) -> {
- Trace.endSection();
- if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
- if (res != PixelCopy.SUCCESS) {
- Bitmap lastBitmap = currentPage.getBitmap();
- // assign the last bitmap taken for now
- currentPage.setBitmap(mLastScreenshot);
- Bitmap lastScreenshot = mLastScreenshot;
- if (lastScreenshot != null && !lastScreenshot.isRecycled()
- && !Objects.equals(lastBitmap, lastScreenshot)) {
- updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
+ try {
+ // TODO(b/274427458) check if this can be done in the background.
+ PixelCopy.request(surface, screenShot, (res) -> {
+ Trace.endAsyncSection(pixelCopySectionName, pixelCopyCount);
+ if (DEBUG) {
+ Log.d(TAG, "result of pixel copy is: "
+ + (res == PixelCopy.SUCCESS ? "SUCCESS" : "FAILURE"));
}
- } else {
- mLastScreenshot = finalScreenShot;
- // going to hold this lock for a while
- currentPage.setBitmap(finalScreenShot);
- currentPage.setLastUpdateTime(current);
- updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
- }
- }, mHandler);
-
+ if (res != PixelCopy.SUCCESS) {
+ Bitmap lastBitmap = currentPage.getBitmap();
+ // assign the last bitmap taken for now
+ currentPage.setBitmap(mLastScreenshot);
+ Bitmap lastScreenshot = mLastScreenshot;
+ if (lastScreenshot != null && !Objects.equals(lastBitmap, lastScreenshot)) {
+ updatePageColors(
+ currentPage, areas, pageIndx, numPages, wallpaperDimAmount);
+ }
+ } else {
+ mLastScreenshot = finalScreenShot;
+ currentPage.setBitmap(finalScreenShot);
+ currentPage.setLastUpdateTime(current);
+ updatePageColors(
+ currentPage, areas, pageIndx, numPages, wallpaperDimAmount);
+ }
+ }, mBackgroundHandler);
+ } catch (IllegalArgumentException e) {
+ // this can potentially happen if the surface is invalidated right between the
+ // surface.isValid() check and the PixelCopy operation.
+ // in this case, stop: we'll compute colors on the next processLocalColors call.
+ Log.w(TAG, "Cancelling processLocalColors: exception caught during PixelCopy");
+ }
}
// locked by the passed page
- private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
- float xOffsetStep) {
+ private void updatePageColors(EngineWindowPage page, Set<RectF> areas,
+ int pageIndx, int numPages, float wallpaperDimAmount) {
if (page.getBitmap() == null) return;
+ if (!mBackgroundHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException(
+ "ProcessLocalColors should be called from the background thread");
+ }
Trace.beginSection("WallpaperService#updatePageColors");
if (DEBUG) {
Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
+ page.getAreas().size() + " and bitmap size of "
+ page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight());
}
- for (RectF area: page.getAreas()) {
+ for (RectF area: areas) {
if (area == null) continue;
RectF subArea = generateSubRect(area, pageIndx, numPages);
Bitmap b = page.getBitmap();
@@ -1815,12 +1866,12 @@
int height = Math.round(b.getHeight() * subArea.height());
Bitmap target;
try {
- target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height);
+ target = Bitmap.createBitmap(b, x, y, width, height);
} catch (Exception e) {
Log.e(TAG, "Error creating page local color bitmap", e);
continue;
}
- WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount);
+ WallpaperColors color = WallpaperColors.fromBitmap(target, wallpaperDimAmount);
target.recycle();
WallpaperColors currentColor = page.getColors(area);
@@ -1837,12 +1888,14 @@
+ " local color callback for area" + area + " for page " + pageIndx
+ " of " + numPages);
}
- try {
- mConnection.onLocalWallpaperColorsChanged(area, color,
- mDisplayContext.getDisplayId());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
- }
+ mHandler.post(() -> {
+ try {
+ mConnection.onLocalWallpaperColorsChanged(area, color,
+ mDisplayContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+ }
+ });
}
}
Trace.endSection();
@@ -1868,16 +1921,17 @@
return new RectF(left, in.top, right, in.bottom);
}
+ @GuardedBy("mLock")
private void resetWindowPages() {
if (supportsLocalColorExtraction()) return;
if (!mResetWindowPages) return;
mResetWindowPages = false;
- mLastWindowPage = -1;
for (int i = 0; i < mWindowPages.length; i++) {
mWindowPages[i].setLastUpdateTime(0L);
}
}
+ @GuardedBy("mLock")
private int getRectFPage(RectF area, float step) {
if (!isValid(area)) return 0;
if (!validStep(step)) return 0;
@@ -1898,12 +1952,12 @@
if (DEBUG) {
Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
}
- mHandler.post(() -> {
- mLocalColorsToAdd.addAll(regions);
- processLocalColors(mPendingXOffset, mPendingYOffset);
+ mBackgroundHandler.post(() -> {
+ synchronized (mLock) {
+ mLocalColorsToAdd.addAll(regions);
+ }
+ processLocalColors();
});
-
-
}
/**
@@ -1913,16 +1967,18 @@
*/
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
if (supportsLocalColorExtraction()) return;
- mHandler.post(() -> {
- float step = mPendingXOffsetStep;
- mLocalColorsToAdd.removeAll(regions);
- mLocalColorAreas.removeAll(regions);
- if (!validStep(step)) {
- return;
- }
- for (int i = 0; i < mWindowPages.length; i++) {
- for (int j = 0; j < regions.size(); j++) {
- mWindowPages[i].removeArea(regions.get(j));
+ mBackgroundHandler.post(() -> {
+ synchronized (mLock) {
+ float step = mPendingXOffsetStep;
+ mLocalColorsToAdd.removeAll(regions);
+ mLocalColorAreas.removeAll(regions);
+ if (!validStep(step)) {
+ return;
+ }
+ for (int i = 0; i < mWindowPages.length; i++) {
+ for (int j = 0; j < regions.size(); j++) {
+ mWindowPages[i].removeArea(regions.get(j));
+ }
}
}
});
@@ -1940,7 +1996,7 @@
}
private boolean validStep(float step) {
- return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.;
+ return !Float.isNaN(step) && step > 0f && step <= 1f;
}
void doCommand(WallpaperCommand cmd) {
@@ -2579,6 +2635,9 @@
@Override
public void onCreate() {
Trace.beginSection("WPMS.onCreate");
+ mBackgroundThread = new HandlerThread("DefaultWallpaperLocalColorExtractor");
+ mBackgroundThread.start();
+ mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
super.onCreate();
Trace.endSection();
}
@@ -2591,6 +2650,7 @@
engineWrapper.destroy();
}
mActiveEngines.clear();
+ mBackgroundThread.quitSafely();
Trace.endSection();
}
diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java
index cf045b8..cc33af3 100644
--- a/core/java/android/util/DataUnit.java
+++ b/core/java/android/util/DataUnit.java
@@ -36,9 +36,11 @@
KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } },
MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } },
GIGABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000; } },
+ TERABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000_000L; } },
KIBIBYTES { @Override public long toBytes(long v) { return v * 1_024; } },
MEBIBYTES { @Override public long toBytes(long v) { return v * 1_048_576; } },
- GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } };
+ GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } },
+ TEBIBYTES { @Override public long toBytes(long v) { return v * 1_099_511_627_776L; } };
public long toBytes(long v) {
throw new AbstractMethodError();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6201b3a..bc514b0 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -229,9 +229,9 @@
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
- DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "false");
+ DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "true");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
- DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
+ DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "true");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "false");
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index cc83dec..cd03d83 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -71,7 +71,7 @@
* Creates a new SparseArray containing no mappings.
*/
public SparseArray() {
- this(10);
+ this(0);
}
/**
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index c145b20..12a9900 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -51,7 +51,7 @@
* Creates a new SparseBooleanArray containing no mappings.
*/
public SparseBooleanArray() {
- this(10);
+ this(0);
}
/**
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
index ee2e3ce..4b0cbe4 100644
--- a/core/java/android/util/SparseDoubleArray.java
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -50,7 +50,7 @@
/** Creates a new SparseDoubleArray containing no mappings. */
public SparseDoubleArray() {
- this(10);
+ this(0);
}
/**
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index d4f6685..0e98c28 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -58,7 +58,7 @@
* Creates a new SparseIntArray containing no mappings.
*/
public SparseIntArray() {
- this(10);
+ this(0);
}
/**
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index b739e37..e86b647 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -51,7 +51,7 @@
* Creates a new SparseLongArray containing no mappings.
*/
public SparseLongArray() {
- this(10);
+ this(0);
}
/**
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index c66c70af..c1c1317 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -25,7 +25,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
@@ -72,11 +71,18 @@
* If {@link #getContentToRecord()} is {@link RecordContent#RECORD_CONTENT_TASK}, then
* represents the {@link android.window.WindowContainerToken} of the Task to record.
*/
- @VisibleForTesting
@Nullable
private IBinder mTokenToRecord = null;
/**
+ * When {@code true}, no mirroring should take place until the user has re-granted access to
+ * the consent token. When {@code false}, recording can begin immediately.
+ *
+ * <p>Only set on the server side to sanitize any input from the client process.
+ */
+ private boolean mWaitingToRecord = false;
+
+ /**
* Default instance, with recording the display.
*/
private ContentRecordingSession() {
@@ -109,9 +115,10 @@
}
/**
- * Returns {@code true} when both sessions are for the same display.
+ * Returns {@code true} when both sessions are on the same
+ * {@link android.hardware.display.VirtualDisplay}.
*/
- public static boolean isSameDisplay(ContentRecordingSession session,
+ public static boolean isProjectionOnSameDisplay(ContentRecordingSession session,
ContentRecordingSession incomingSession) {
return session != null && incomingSession != null
&& session.getDisplayId() == incomingSession.getDisplayId();
@@ -156,7 +163,8 @@
/* package-private */ ContentRecordingSession(
int displayId,
@RecordContent int contentToRecord,
- @VisibleForTesting @Nullable IBinder tokenToRecord) {
+ @Nullable IBinder tokenToRecord,
+ boolean waitingToRecord) {
this.mDisplayId = displayId;
this.mContentToRecord = contentToRecord;
@@ -169,8 +177,7 @@
}
this.mTokenToRecord = tokenToRecord;
- com.android.internal.util.AnnotationValidations.validate(
- VisibleForTesting.class, null, mTokenToRecord);
+ this.mWaitingToRecord = waitingToRecord;
// onConstructed(); // You can define this method to get a callback
}
@@ -200,11 +207,22 @@
* represents the {@link android.window.WindowContainerToken} of the Task to record.
*/
@DataClass.Generated.Member
- public @VisibleForTesting @Nullable IBinder getTokenToRecord() {
+ public @Nullable IBinder getTokenToRecord() {
return mTokenToRecord;
}
/**
+ * When {@code true}, no mirroring should take place until the user has re-granted access to
+ * the consent token. When {@code false}, recording can begin immediately.
+ *
+ * <p>Only set on the server side to sanitize any input from the client process.
+ */
+ @DataClass.Generated.Member
+ public boolean isWaitingToRecord() {
+ return mWaitingToRecord;
+ }
+
+ /**
* Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
* recorded content rendered to its surface.
*/
@@ -240,10 +258,20 @@
* represents the {@link android.window.WindowContainerToken} of the Task to record.
*/
@DataClass.Generated.Member
- public @NonNull ContentRecordingSession setTokenToRecord(@VisibleForTesting @NonNull IBinder value) {
+ public @NonNull ContentRecordingSession setTokenToRecord(@NonNull IBinder value) {
mTokenToRecord = value;
- com.android.internal.util.AnnotationValidations.validate(
- VisibleForTesting.class, null, mTokenToRecord);
+ return this;
+ }
+
+ /**
+ * When {@code true}, no mirroring should take place until the user has re-granted access to
+ * the consent token. When {@code false}, recording can begin immediately.
+ *
+ * <p>Only set on the server side to sanitize any input from the client process.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ContentRecordingSession setWaitingToRecord( boolean value) {
+ mWaitingToRecord = value;
return this;
}
@@ -256,7 +284,8 @@
return "ContentRecordingSession { " +
"displayId = " + mDisplayId + ", " +
"contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
- "tokenToRecord = " + mTokenToRecord +
+ "tokenToRecord = " + mTokenToRecord + ", " +
+ "waitingToRecord = " + mWaitingToRecord +
" }";
}
@@ -275,7 +304,8 @@
return true
&& mDisplayId == that.mDisplayId
&& mContentToRecord == that.mContentToRecord
- && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord);
+ && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord)
+ && mWaitingToRecord == that.mWaitingToRecord;
}
@Override
@@ -288,6 +318,7 @@
_hash = 31 * _hash + mDisplayId;
_hash = 31 * _hash + mContentToRecord;
_hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
+ _hash = 31 * _hash + Boolean.hashCode(mWaitingToRecord);
return _hash;
}
@@ -298,6 +329,7 @@
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
+ if (mWaitingToRecord) flg |= 0x8;
if (mTokenToRecord != null) flg |= 0x4;
dest.writeByte(flg);
dest.writeInt(mDisplayId);
@@ -317,6 +349,7 @@
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
+ boolean waitingToRecord = (flg & 0x8) != 0;
int displayId = in.readInt();
int contentToRecord = in.readInt();
IBinder tokenToRecord = (flg & 0x4) == 0 ? null : (IBinder) in.readStrongBinder();
@@ -333,8 +366,7 @@
}
this.mTokenToRecord = tokenToRecord;
- com.android.internal.util.AnnotationValidations.validate(
- VisibleForTesting.class, null, mTokenToRecord);
+ this.mWaitingToRecord = waitingToRecord;
// onConstructed(); // You can define this method to get a callback
}
@@ -362,7 +394,8 @@
private int mDisplayId;
private @RecordContent int mContentToRecord;
- private @VisibleForTesting @Nullable IBinder mTokenToRecord;
+ private @Nullable IBinder mTokenToRecord;
+ private boolean mWaitingToRecord;
private long mBuilderFieldsSet = 0L;
@@ -400,17 +433,31 @@
* represents the {@link android.window.WindowContainerToken} of the Task to record.
*/
@DataClass.Generated.Member
- public @NonNull Builder setTokenToRecord(@VisibleForTesting @NonNull IBinder value) {
+ public @NonNull Builder setTokenToRecord(@NonNull IBinder value) {
checkNotUsed();
mBuilderFieldsSet |= 0x4;
mTokenToRecord = value;
return this;
}
+ /**
+ * When {@code true}, no mirroring should take place until the user has re-granted access to
+ * the consent token. When {@code false}, recording can begin immediately.
+ *
+ * <p>Only set on the server side to sanitize any input from the client process.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setWaitingToRecord(boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mWaitingToRecord = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ContentRecordingSession build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x8; // Mark builder used
+ mBuilderFieldsSet |= 0x10; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mDisplayId = INVALID_DISPLAY;
@@ -421,15 +468,19 @@
if ((mBuilderFieldsSet & 0x4) == 0) {
mTokenToRecord = null;
}
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mWaitingToRecord = false;
+ }
ContentRecordingSession o = new ContentRecordingSession(
mDisplayId,
mContentToRecord,
- mTokenToRecord);
+ mTokenToRecord,
+ mWaitingToRecord);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x8) != 0) {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -437,10 +488,10 @@
}
@DataClass.Generated(
- time = 1645803878639L,
+ time = 1678817765846L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
- inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate @com.android.internal.annotations.VisibleForTesting @android.annotation.Nullable android.os.IBinder mTokenToRecord\npublic static android.view.ContentRecordingSession createDisplaySession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final int RECORD_CONTENT_DISPLAY\npublic static final int RECORD_CONTENT_TASK\nprivate int mDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate boolean mWaitingToRecord\npublic static android.view.ContentRecordingSession createDisplaySession(android.os.IBinder)\npublic static android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static boolean isValid(android.view.ContentRecordingSession)\npublic static boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 6b60442..77f3b1d 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -151,14 +151,16 @@
// Either we've already tried to initiate handwriting, or the ongoing MotionEvent
// sequence is considered to be tap, long-click or other gestures.
if (!mState.mShouldInitHandwriting || mState.mExceedHandwritingSlop) {
- return mState.mHasInitiatedHandwriting;
+ return mState.mHasInitiatedHandwriting
+ || mState.mHasPreparedHandwritingDelegation;
}
final long timeElapsed =
motionEvent.getEventTime() - mState.mStylusDownTimeInMillis;
if (timeElapsed > mHandwritingTimeoutInMillis) {
mState.mShouldInitHandwriting = false;
- return mState.mHasInitiatedHandwriting;
+ return mState.mHasInitiatedHandwriting
+ || mState.mHasPreparedHandwritingDelegation;
}
final int pointerIndex = motionEvent.findPointerIndex(mState.mStylusPointerId);
@@ -183,12 +185,13 @@
mImm.prepareStylusHandwritingDelegation(
candidateView, delegatePackageName);
candidateView.getHandwritingDelegatorCallback().run();
+ mState.mHasPreparedHandwritingDelegation = true;
} else {
requestFocusWithoutReveal(candidateView);
}
}
}
- return mState.mHasInitiatedHandwriting;
+ return mState.mHasInitiatedHandwriting || mState.mHasPreparedHandwritingDelegation;
}
return false;
}
@@ -568,6 +571,8 @@
* Whether handwriting mode has already been initiated for the current MotionEvent sequence.
*/
private boolean mHasInitiatedHandwriting;
+
+ private boolean mHasPreparedHandwritingDelegation;
/**
* Whether the current ongoing stylus MotionEvent sequence already exceeds the
* handwriting slop.
@@ -593,6 +598,7 @@
mShouldInitHandwriting = true;
mHasInitiatedHandwriting = false;
+ mHasPreparedHandwritingDelegation = false;
mExceedHandwritingSlop = false;
}
}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 9225cd9..61864d7 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1183,8 +1183,10 @@
*/
@NonNull
public LightsManager getLightsManager() {
- if (mLightsManager == null) {
- mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId);
+ synchronized (mMotionRanges) {
+ if (mLightsManager == null) {
+ mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId);
+ }
}
return mLightsManager;
}
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index b574ecf..66fd794 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -15,6 +15,10 @@
# Autofill
per-file ViewStructure.java = file:/core/java/android/service/autofill/OWNERS
+# Choreographer
+per-file Choreographer.java = file:platform/frameworks/native:/services/surfaceflinger/OWNERS
+per-file DisplayEventReceiver.java = file:platform/frameworks/native:/services/surfaceflinger/OWNERS
+
# Display
per-file Display*.java = file:/services/core/java/com/android/server/display/OWNERS
per-file Display*.aidl = file:/services/core/java/com/android/server/display/OWNERS
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 4895aed..0db52aa 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3896,10 +3896,12 @@
float currentBufferRatio, float desiredRatio) {
checkPreconditions(sc);
if (!Float.isFinite(currentBufferRatio) || currentBufferRatio < 1.0f) {
- throw new IllegalArgumentException("currentBufferRatio must be finite && >= 1.0f");
+ throw new IllegalArgumentException(
+ "currentBufferRatio must be finite && >= 1.0f; got " + currentBufferRatio);
}
if (!Float.isFinite(desiredRatio) || desiredRatio < 1.0f) {
- throw new IllegalArgumentException("desiredRatio must be finite && >= 1.0f");
+ throw new IllegalArgumentException(
+ "desiredRatio must be finite && >= 1.0f; got " + desiredRatio);
}
nativeSetExtendedRangeBrightness(mNativeObject, sc.mNativeObject, currentBufferRatio,
desiredRatio);
diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING
index ecb98f9..1e39716 100644
--- a/core/java/android/view/TEST_MAPPING
+++ b/core/java/android/view/TEST_MAPPING
@@ -42,6 +42,9 @@
],
"imports": [
{
+ "path": "cts/tests/surfacecontrol"
+ },
+ {
"path": "cts/tests/tests/uirendering"
}
]
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index aec3487..6cd8941 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -163,7 +163,6 @@
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.Checkable;
-import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ScrollBarDrawable;
import android.window.OnBackInvokedDispatcher;
@@ -10347,24 +10346,29 @@
}
/**
- * Check whether current activity / package is in denylist.If it's in the denylist,
- * then the views marked as not important for autofill are not eligible for autofill.
+ * Check whether current activity / package is in autofill denylist.
+ *
+ * Called by viewGroup#populateChildrenForAutofill() to determine whether to include view in
+ * assist structure
*/
final boolean isActivityDeniedForAutofillForUnimportantView() {
final AutofillManager afm = getAutofillManager();
- // keep behavior same with denylist feature not enabled
- if (afm == null) return true;
- return afm.isActivityDeniedForAutofillForUnimportantView();
+ if (afm == null) return false;
+ return afm.isActivityDeniedForAutofill();
}
/**
* Check whether current view matches autofillable heuristics
+ *
+ * Called by viewGroup#populateChildrenForAutofill() to determine whether to include view in
+ * assist structure
*/
final boolean isMatchingAutofillableHeuristics() {
final AutofillManager afm = getAutofillManager();
- // keep default behavior
if (afm == null) return false;
- return afm.isMatchingAutofillableHeuristicsForNotImportantViews(this);
+ // check the flag to see if trigger fill request on not important views is enabled
+ return afm.isTriggerFillRequestOnUnimportantViewEnabled()
+ ? afm.isAutofillable(this) : false;
}
private boolean isAutofillable() {
@@ -10380,39 +10384,26 @@
return false;
}
- // Experiment imeAction heuristic on important views. If the important view doesn't pass
- // heuristic check, also check augmented autofill in case augmented autofill is enabled
- // for the activity
- // TODO: refactor to have both important views and not important views use the same
- // heuristic check
- if (isImportantForAutofill()
- && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled()
- && this instanceof EditText
- && !afm.isPassingImeActionCheck((EditText) this)
- && !notifyAugmentedAutofillIfNeeded(afm)) {
- // TODO: add a log to indicate what has filtered out the view
+ // Check whether view is not part of an activity. If it's not, return false.
+ if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
return false;
}
- if (!isImportantForAutofill()) {
- // If view matches heuristics and is not denied, it will be treated same as view that's
- // important for autofill
- if (afm.isMatchingAutofillableHeuristicsForNotImportantViews(this)
- && !afm.isActivityDeniedForAutofillForUnimportantView()) {
- return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
- }
- // View is not important for "regular" autofill, so we must check if Augmented Autofill
- // is enabled for the activity
- if (!notifyAugmentedAutofillIfNeeded(afm)){
- return false;
- }
+ // If view is important and filter important view flag is turned on, or view is not
+ // important and trigger fill request on not important view flag is turned on, then use
+ // AutofillManager.isAutofillable() to decide whether view is autofillable instead.
+ if ((isImportantForAutofill() && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled())
+ || (!isImportantForAutofill()
+ && afm.isTriggerFillRequestOnUnimportantViewEnabled())) {
+ return afm.isAutofillable(this) ? true : notifyAugmentedAutofillIfNeeded(afm);
}
- return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
+ // If the previous condition is not met, fall back to the previous way to trigger fill
+ // request based on autofill importance instead.
+ return isImportantForAutofill() ? true : notifyAugmentedAutofillIfNeeded(afm);
}
- /** @hide **/
- public boolean notifyAugmentedAutofillIfNeeded(AutofillManager afm) {
+ private boolean notifyAugmentedAutofillIfNeeded(AutofillManager afm) {
final AutofillOptions options = mContext.getAutofillOptions();
if (options == null || !options.isAugmentedAutofillEnabled(mContext)) {
return false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d1f9fbd..152fa08 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11365,9 +11365,13 @@
if (mRemoved || !isHardwareEnabled()) {
t.apply();
} else {
+ // Copy and clear the passed in transaction for thread safety. The new transaction is
+ // accessed on the render thread.
+ var localTransaction = new Transaction();
+ localTransaction.merge(t);
mHasPendingTransactions = true;
registerRtFrameCallback(frame -> {
- mergeWithNextTransaction(t, frame);
+ mergeWithNextTransaction(localTransaction, frame);
});
}
return true;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9d82b79..b17d2b9 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2648,7 +2648,7 @@
/**
* Gets if the node's accessibility data is considered sensitive.
*
- * @return True if the node is editable, false otherwise.
+ * @return True if the node's data is considered sensitive, false otherwise.
* @see View#isAccessibilityDataSensitive()
*/
public boolean isAccessibilityDataSensitive() {
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 7da69e7..e267a7f 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -365,7 +365,10 @@
}
/**
- * Get denylist string from flag
+ * Get denylist string from flag.
+ *
+ * Note: This denylist works both on important view and not important views. The flag used here
+ * is legacy flag which will be replaced with soon.
*
* @hide
*/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 508c20a..781a4b6 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -687,11 +687,11 @@
// If a package is fully denied, then all views that marked as not
// important for autofill will not trigger fill request
- private boolean mIsPackageFullyDeniedForAutofillForUnimportantView = false;
+ private boolean mIsPackageFullyDeniedForAutofill = false;
// If a package is partially denied, autofill manager will check whether
// current activity is in deny set to decide whether to trigger fill request
- private boolean mIsPackagePartiallyDeniedForAutofillForUnimportantView = false;
+ private boolean mIsPackagePartiallyDeniedForAutofill = false;
// A deny set read from device config
private Set<String> mDeniedActivitiySet = new ArraySet<>();
@@ -876,15 +876,15 @@
final String packageName = mContext.getPackageName();
- mIsPackageFullyDeniedForAutofillForUnimportantView =
- isPackageFullyDeniedForAutofillForUnimportantView(denyListString, packageName);
+ mIsPackageFullyDeniedForAutofill =
+ isPackageFullyDeniedForAutofill(denyListString, packageName);
- if (!mIsPackageFullyDeniedForAutofillForUnimportantView) {
- mIsPackagePartiallyDeniedForAutofillForUnimportantView =
- isPackagePartiallyDeniedForAutofillForUnimportantView(denyListString, packageName);
+ if (!mIsPackageFullyDeniedForAutofill) {
+ mIsPackagePartiallyDeniedForAutofill =
+ isPackagePartiallyDeniedForAutofill(denyListString, packageName);
}
- if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
+ if (mIsPackagePartiallyDeniedForAutofill) {
setDeniedActivitySetWithDenyList(denyListString, packageName);
}
}
@@ -899,6 +899,15 @@
}
/**
+ * Whether to trigger fill request on not important views that passes heuristic check
+ *
+ * @hide
+ */
+ public boolean isTriggerFillRequestOnUnimportantViewEnabled() {
+ return mIsTriggerFillRequestOnUnimportantViewEnabled;
+ }
+
+ /**
* Whether view passes the imeAction check
*
* @hide
@@ -906,13 +915,13 @@
public boolean isPassingImeActionCheck(EditText editText) {
final int actionId = editText.getImeOptions();
if (mNonAutofillableImeActionIdSet.contains(String.valueOf(actionId))) {
- // TODO: add a log to indicate what has filtered out the view
+ Log.d(TAG, "view not autofillable - not passing ime action check");
return false;
}
return true;
}
- private boolean isPackageFullyDeniedForAutofillForUnimportantView(
+ private boolean isPackageFullyDeniedForAutofill(
@NonNull String denyListString, @NonNull String packageName) {
// If "PackageName:;" is in the string, then it means the package name is in denylist
// and there are no activities specified under it. That means the package is fully
@@ -920,7 +929,7 @@
return denyListString.indexOf(packageName + ":;") != -1;
}
- private boolean isPackagePartiallyDeniedForAutofillForUnimportantView(
+ private boolean isPackagePartiallyDeniedForAutofill(
@NonNull String denyListString, @NonNull String packageName) {
// This check happens after checking package is not fully denied. If "PackageName:" instead
// is in denylist, then it means there are specific activities to be denied. So the package
@@ -968,17 +977,16 @@
}
/**
- * Check whether autofill is denied for current activity or package. Used when a view is marked
- * as not important for autofill, if current activity or package is denied, then the view won't
- * trigger fill request.
+ * Check whether autofill is denied for current activity or package. If current activity or
+ * package is denied, then the view won't trigger fill request.
*
* @hide
*/
- public final boolean isActivityDeniedForAutofillForUnimportantView() {
- if (mIsPackageFullyDeniedForAutofillForUnimportantView) {
+ public boolean isActivityDeniedForAutofill() {
+ if (mIsPackageFullyDeniedForAutofill) {
return true;
}
- if (mIsPackagePartiallyDeniedForAutofillForUnimportantView) {
+ if (mIsPackagePartiallyDeniedForAutofill) {
final AutofillClient client = getClient();
if (client == null) {
return false;
@@ -992,27 +1000,36 @@
}
/**
- * Check whether view matches autofill-able heuristics
+ * Check heuristics and other rules to determine if view is autofillable
+ *
+ * Note: this function should be only called only when autofill for all apps is turned on. The
+ * calling method needs to check the corresponding flag to make sure that before calling into
+ * this function.
*
* @hide
*/
- public final boolean isMatchingAutofillableHeuristicsForNotImportantViews(@NonNull View view) {
- if (!mIsTriggerFillRequestOnUnimportantViewEnabled) {
+ public boolean isAutofillable(View view) {
+ if (isActivityDeniedForAutofill()) {
+ Log.d(TAG, "view is not autofillable - activity denied for autofill");
return false;
}
- // TODO: remove the autofill type check when this function is applied on both important and
- // not important views.
- // This check is needed here because once the view type check is lifted, addiditional
- // unimportant views will be added to the assist structure which may cuase system health
- // regression (viewGroup#populateChidlrenForAutofill() calls this function to decide whether
- // to include child view)
+ // Duplicate the autofill type check here because ViewGroup will call this function to
+ // decide whether to include view in assist structure.
+ // Also keep the autofill type check inside View#IsAutofillable() to serve as an early out
+ // or if other functions need to call it.
if (view.getAutofillType() == View.AUTOFILL_TYPE_NONE) return false;
if (view instanceof EditText) {
return isPassingImeActionCheck((EditText) view);
}
+ // Skip view type check if view is important for autofill or
+ // shouldEnableAutofillOnAllViewTypes flag is turned on
+ if (view.isImportantForAutofill() || mShouldEnableAutofillOnAllViewTypes) {
+ return true;
+ }
+
if (view instanceof CheckBox
|| view instanceof Spinner
|| view instanceof DatePicker
@@ -1021,10 +1038,9 @@
return true;
}
- return mShouldEnableAutofillOnAllViewTypes;
+ return false;
}
-
/**
* @hide
*/
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index e7207fa..c4d43bc 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -22,6 +22,7 @@
import android.annotation.TestApi;
import android.graphics.RectF;
import android.inputmethodservice.InputMethodService;
+import android.os.CancellationSignal;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.MotionEvent;
@@ -33,18 +34,20 @@
import java.util.function.IntConsumer;
/**
- * Base class for Stylus handwriting gesture.
- *
+ * Base class for stylus handwriting gestures.
+ * <p>
* During a stylus handwriting session, user can perform a stylus gesture operation like
* {@link SelectGesture}, {@link DeleteGesture}, {@link InsertGesture} on an
- * area of text. IME is responsible for listening to Stylus {@link MotionEvent} using
+ * area of text. IME is responsible for listening to stylus {@link MotionEvent}s using
* {@link InputMethodService#onStylusHandwritingMotionEvent} and interpret if it can translate to a
* gesture operation.
- * While creating Gesture operations {@link SelectGesture}, {@link DeleteGesture},
- * , {@code Granularity} helps pick the correct granular level of text like word level
+ * <p>
+ * While creating gesture operations {@link SelectGesture} and {@link DeleteGesture},
+ * {@code Granularity} helps pick the correct granular level of text like word level
* {@link #GRANULARITY_WORD}, or character level {@link #GRANULARITY_CHARACTER}.
*
* @see InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)
+ * @see InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture, CancellationSignal)
* @see InputMethodService#onStartStylusHandwriting()
*/
public abstract class HandwritingGesture {
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index 5353bc6..715fc9a 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -41,6 +41,9 @@
* are suitable for use. Credentials are not suitable if they have
* previously been rejected by the server for the current request.
*
+ * <p class="note"><b>Note:</b> The host application must call this method
+ * on the host application's UI Thread.
+ *
* @return whether the credentials are suitable for use
* @see WebView#getHttpAuthUsernamePassword
*/
@@ -50,6 +53,9 @@
/**
* Instructs the WebView to cancel the authentication request.
+ *
+ * <p class="note"><b>Note:</b> The host application must call this method
+ * on the host application's UI Thread.
*/
public void cancel() {
}
@@ -58,6 +64,9 @@
* Instructs the WebView to proceed with the authentication with the given
* credentials. Credentials for use with this method can be retrieved from
* the WebView's store using {@link WebView#getHttpAuthUsernamePassword}.
+ *
+ * <p class="note"><b>Note:</b> The host application must call this method
+ * on the host application's UI Thread.
*/
public void proceed(String username, String password) {
}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 7b6e1a3..55f09f1 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -455,6 +455,9 @@
* {@link HttpAuthHandler} to set the WebView's response to the request.
* The default behavior is to cancel the request.
*
+ * <p class="note"><b>Note:</b> The supplied HttpAuthHandler must be used on
+ * the UI thread.
+ *
* @param view the WebView that is initiating the callback
* @param handler the HttpAuthHandler used to set the WebView's response
* @param host the host requiring authentication
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index dce5432..088065d2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3193,47 +3193,66 @@
menuItemOrderPasteAsPlainText = 11;
}
+ final TypedArray a = mTextView.getContext().obtainStyledAttributes(new int[] {
+ // TODO: Make Undo/Redo be public attribute.
+ com.android.internal.R.attr.actionModeUndoDrawable,
+ com.android.internal.R.attr.actionModeRedoDrawable,
+ android.R.attr.actionModeCutDrawable,
+ android.R.attr.actionModeCopyDrawable,
+ android.R.attr.actionModePasteDrawable,
+ android.R.attr.actionModeSelectAllDrawable,
+ android.R.attr.actionModeShareDrawable,
+ });
+
menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
com.android.internal.R.string.undo)
.setAlphabeticShortcut('z')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+ .setIcon(a.getDrawable(0))
.setEnabled(mTextView.canUndo());
menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_REDO, menuItemOrderRedo,
com.android.internal.R.string.redo)
.setAlphabeticShortcut('z', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+ .setIcon(a.getDrawable(1))
.setEnabled(mTextView.canRedo());
menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_CUT, menuItemOrderCut,
com.android.internal.R.string.cut)
.setAlphabeticShortcut('x')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+ .setIcon(a.getDrawable(2))
.setEnabled(mTextView.canCut());
menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_COPY, menuItemOrderCopy,
com.android.internal.R.string.copy)
.setAlphabeticShortcut('c')
.setOnMenuItemClickListener(mOnContextMenuItemClickListener)
+ .setIcon(a.getDrawable(3))
.setEnabled(mTextView.canCopy());
menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE, menuItemOrderPaste,
com.android.internal.R.string.paste)
.setAlphabeticShortcut('v')
.setEnabled(mTextView.canPaste())
+ .setIcon(a.getDrawable(4))
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_PASTE_AS_PLAIN_TEXT,
menuItemOrderPasteAsPlainText,
com.android.internal.R.string.paste_as_plain_text)
.setAlphabeticShortcut('v', KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)
.setEnabled(mTextView.canPasteAsPlainText())
+ .setIcon(a.getDrawable(4))
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
menu.add(CONTEXT_MENU_GROUP_CLIPBOARD, TextView.ID_SELECT_ALL,
menuItemOrderSelectAll, com.android.internal.R.string.selectAll)
.setAlphabeticShortcut('a')
.setEnabled(mTextView.canSelectAllText())
+ .setIcon(a.getDrawable(5))
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_SHARE, menuItemOrderShare,
com.android.internal.R.string.share)
.setEnabled(mTextView.canShare())
+ .setIcon(a.getDrawable(6))
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill,
android.R.string.autofill)
@@ -3241,6 +3260,7 @@
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
mPreserveSelection = true;
+ a.recycle();
// No-op for the old context menu because it doesn't have icons.
adjustIconSpacing(menu);
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index 02dafd4..7f0a651 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -13,3 +13,5 @@
per-file SpellChecker.java = file:../view/inputmethod/OWNERS
per-file RemoteViews* = file:../appwidget/OWNERS
+
+per-file Toast.java = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 3478b0f..954f686 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -54,9 +54,6 @@
* @hide
*/
public final class WindowMetricsController {
- // TODO(b/151908239): Remove and always enable this if it is stable.
- private static final boolean LAZY_WINDOW_INSETS = android.os.SystemProperties.getBoolean(
- "persist.wm.debug.win_metrics_lazy_insets", true);
private final Context mContext;
public WindowMetricsController(@NonNull Context context) {
@@ -98,9 +95,7 @@
final IBinder token = Context.getToken(mContext);
final Supplier<WindowInsets> insetsSupplier = () -> getWindowInsetsFromServerForDisplay(
mContext.getDisplayId(), token, bounds, isScreenRound, windowingMode);
- return LAZY_WINDOW_INSETS
- ? new WindowMetrics(new Rect(bounds), insetsSupplier, density)
- : new WindowMetrics(new Rect(bounds), insetsSupplier.get(), density);
+ return new WindowMetrics(new Rect(bounds), insetsSupplier, density);
}
/**
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 0d02683..a1d571f 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -1,4 +1,6 @@
per-file *AppOp* = file:/core/java/android/permission/OWNERS
+per-file UnlaunchableAppActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
+per-file IntentForwarderActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS
per-file *Resolver* = file:/packages/SystemUI/OWNERS
per-file *Chooser* = file:/packages/SystemUI/OWNERS
per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 9fae211..2b08a55 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -74,6 +74,9 @@
public static final Flag OTP_REDACTION =
devFlag("persist.sysui.notification.otp_redaction");
+ /** Gating the removal of sorting-notifications-by-interruptiveness. */
+ public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
+ devFlag("persist.sysui.notification.no_sort_by_interruptiveness");
}
//// == End of flags. Everything below this line is the implementation. == ////
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
index afdbdc8..4a46d91 100644
--- a/core/java/com/android/internal/expresslog/Counter.java
+++ b/core/java/com/android/internal/expresslog/Counter.java
@@ -36,6 +36,16 @@
}
/**
+ * Increments Telemetry Express Counter metric by 1
+ * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+ * @param uid used as a dimension for the count metric
+ * @hide
+ */
+ public static void logIncrementWithUid(@NonNull String metricId, int uid) {
+ logIncrementWithUid(metricId, uid, 1);
+ }
+
+ /**
* Increments Telemetry Express Counter metric by arbitrary value
* @param metricId to log, no-op if metricId is not defined in the TeX catalog
* @param amount to increment counter
@@ -45,4 +55,17 @@
final long metricIdHash = Utils.hashString(metricId);
FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
}
+
+ /**
+ * Increments Telemetry Express Counter metric by arbitrary value
+ * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+ * @param uid used as a dimension for the count metric
+ * @param amount to increment counter
+ * @hide
+ */
+ public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
+ final long metricIdHash = Utils.hashString(metricId);
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
+ }
}
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
index 2f3b662..65fbb03 100644
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -16,10 +16,14 @@
package com.android.internal.expresslog;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.internal.util.FrameworkStatsLog;
+import java.util.Arrays;
+
/** Histogram encapsulates StatsD write API calls */
public final class Histogram {
@@ -28,7 +32,8 @@
/**
* Creates Histogram metric logging wrapper
- * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog
+ *
+ * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog
* @param binOptions to calculate bin index for samples
* @hide
*/
@@ -39,6 +44,7 @@
/**
* Logs increment sample count for automatically calculated bin
+ *
* @param sample value
* @hide
*/
@@ -52,6 +58,7 @@
public interface BinOptions {
/**
* Returns bins count to be used by a histogram
+ *
* @return bins count used to initialize Options, including overflow & underflow bins
* @hide
*/
@@ -61,6 +68,7 @@
* Returns bin index for the input sample value
* index == 0 stands for underflow
* index == getBinsCount() - 1 stands for overflow
+ *
* @return zero based index
* @hide
*/
@@ -76,17 +84,19 @@
private final float mBinSize;
/**
- * Creates otpions for uniform (linear) sized bins
- * @param binCount amount of histogram bins. 2 bin indexes will be calculated
- * automatically to represent undeflow & overflow bins
- * @param minValue is included in the first bin, values less than minValue
- * go to underflow bin
+ * Creates options for uniform (linear) sized bins
+ *
+ * @param binCount amount of histogram bins. 2 bin indexes will be calculated
+ * automatically to represent underflow & overflow bins
+ * @param minValue is included in the first bin, values less than minValue
+ * go to underflow bin
* @param exclusiveMaxValue is included in the overflow bucket. For accurate
- measure up to kMax, then exclusiveMaxValue
+ * measure up to kMax, then exclusiveMaxValue
* should be set to kMax + 1
* @hide
*/
- public UniformOptions(int binCount, float minValue, float exclusiveMaxValue) {
+ public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
+ float exclusiveMaxValue) {
if (binCount < 1) {
throw new IllegalArgumentException("Bin count should be positive number");
}
@@ -99,7 +109,7 @@
mExclusiveMaxValue = exclusiveMaxValue;
mBinSize = (mExclusiveMaxValue - minValue) / binCount;
- // Implicitly add 2 for the extra undeflow & overflow bins
+ // Implicitly add 2 for the extra underflow & overflow bins
mBinCount = binCount + 2;
}
@@ -120,4 +130,92 @@
return (int) ((sample - mMinValue) / mBinSize + 1);
}
}
+
+ /** Used by Histogram to map data sample to corresponding bin for scaled bins */
+ public static final class ScaledRangeOptions implements BinOptions {
+ // store minimum value per bin
+ final long[] mBins;
+
+ /**
+ * Creates options for scaled range bins
+ *
+ * @param binCount amount of histogram bins. 2 bin indexes will be calculated
+ * automatically to represent underflow & overflow bins
+ * @param minValue is included in the first bin, values less than minValue
+ * go to underflow bin
+ * @param firstBinWidth used to represent first bin width and as a reference to calculate
+ * width for consecutive bins
+ * @param scaleFactor used to calculate width for consecutive bins
+ * @hide
+ */
+ public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
+ @FloatRange(from = 1.f) float firstBinWidth,
+ @FloatRange(from = 1.f) float scaleFactor) {
+ if (binCount < 1) {
+ throw new IllegalArgumentException("Bin count should be positive number");
+ }
+
+ if (firstBinWidth < 1.f) {
+ throw new IllegalArgumentException(
+ "First bin width invalid (should be 1.f at minimum)");
+ }
+
+ if (scaleFactor < 1.f) {
+ throw new IllegalArgumentException(
+ "Scaled factor invalid (should be 1.f at minimum)");
+ }
+
+ // precalculating bins ranges (no need to create a bin for underflow reference value)
+ mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
+ }
+
+ @Override
+ public int getBinsCount() {
+ return mBins.length + 1;
+ }
+
+ @Override
+ public int getBinForSample(float sample) {
+ if (sample < mBins[0]) {
+ // goes to underflow
+ return 0;
+ } else if (sample >= mBins[mBins.length - 1]) {
+ // goes to overflow
+ return mBins.length;
+ }
+
+ return lower_bound(mBins, (long) sample) + 1;
+ }
+
+ // To find lower bound using binary search implementation of Arrays utility class
+ private static int lower_bound(long[] array, long sample) {
+ int index = Arrays.binarySearch(array, sample);
+ // If key is not present in the array
+ if (index < 0) {
+ // Index specify the position of the key when inserted in the sorted array
+ // so the element currently present at this position will be the lower bound
+ return Math.abs(index) - 2;
+ }
+ return index;
+ }
+
+ private static long[] initBins(int count, int minValue, float firstBinWidth,
+ float scaleFactor) {
+ long[] bins = new long[count];
+ bins[0] = minValue;
+ double lastWidth = firstBinWidth;
+ for (int i = 1; i < count; i++) {
+ // current bin minValue = previous bin width * scaleFactor
+ double currentBinMinValue = bins[i - 1] + lastWidth;
+ if (currentBinMinValue > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "Attempted to create a bucket larger than maxint");
+ }
+
+ bins[i] = (long) currentBinMinValue;
+ lastWidth *= scaleFactor;
+ }
+ return bins;
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 1505ccc..85cb15b 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -721,7 +721,8 @@
} catch (ErrnoException ex) {
throw new RuntimeException("Failed to capget()", ex);
}
- capabilities &= ((long) data[0].effective) | (((long) data[1].effective) << 32);
+ capabilities &= Integer.toUnsignedLong(data[0].effective) |
+ (Integer.toUnsignedLong(data[1].effective) << 32);
/* Hardcoded command line to start the system server */
String[] args = {
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 5cab674..1172f7b 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -1297,6 +1297,17 @@
return set;
}
+ /** Sets the default attributes of the screenshot layer used for animation. */
+ public static void configureScreenshotLayer(SurfaceControl.Transaction t, SurfaceControl layer,
+ ScreenCapture.ScreenshotHardwareBuffer buffer) {
+ t.setBuffer(layer, buffer.getHardwareBuffer());
+ t.setDataSpace(layer, buffer.getColorSpace().getDataSpace());
+ // Avoid showing dimming effect for HDR content when running animation.
+ if (buffer.containsHdrLayers()) {
+ t.setDimmingEnabled(layer, false);
+ }
+ }
+
/** Returns whether the hardware buffer passed in is marked as protected. */
public static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
return (hardwareBuffer.getUsage() & HardwareBuffer.USAGE_PROTECTED_CONTENT)
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index ad1fdba..ec525f0 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -85,7 +85,7 @@
Consts.TAG_WM),
WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
- WM_DEBUG_CONTENT_RECORDING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ WM_DEBUG_CONTENT_RECORDING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
diff --git a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
new file mode 100644
index 0000000..7755000
--- /dev/null
+++ b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
@@ -0,0 +1,3 @@
+# TODO(b/274465475): Migrate LatencyTracker testing to its own module
+marcinoc@google.com
+ilkos@google.com
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 43a9f5f..3c06755 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -48,6 +48,7 @@
import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
import android.Manifest;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,7 +56,6 @@
import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
-import android.os.ConditionVariable;
import android.os.SystemClock;
import android.os.Trace;
import android.provider.DeviceConfig;
@@ -79,7 +79,7 @@
* Class to track various latencies in SystemUI. It then writes the latency to statsd and also
* outputs it to logcat so these latencies can be captured by tests and then used for dashboards.
* <p>
- * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but
+ * This is currently only in Keyguard. It can be shared between SystemUI and Keyguard, but
* eventually we'd want to merge these two packages together so Keyguard can use common classes
* that are shared with SystemUI.
*/
@@ -285,8 +285,6 @@
UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
};
- private static LatencyTracker sLatencyTracker;
-
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SparseArray<Session> mSessions = new SparseArray<>();
@@ -294,20 +292,21 @@
private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>();
@GuardedBy("mLock")
private boolean mEnabled;
- @VisibleForTesting
- public final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
- public static LatencyTracker getInstance(Context context) {
- if (sLatencyTracker == null) {
- synchronized (LatencyTracker.class) {
- if (sLatencyTracker == null) {
- sLatencyTracker = new LatencyTracker();
- }
- }
- }
- return sLatencyTracker;
+ // Wrapping this in a holder class achieves lazy loading behavior
+ private static final class SLatencyTrackerHolder {
+ private static final LatencyTracker sLatencyTracker = new LatencyTracker();
}
+ public static LatencyTracker getInstance(Context context) {
+ return SLatencyTrackerHolder.sLatencyTracker;
+ }
+
+ /**
+ * Constructor for LatencyTracker
+ *
+ * <p>This constructor is only visible for test classes to inject their own consumer callbacks
+ */
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
@VisibleForTesting
public LatencyTracker() {
@@ -349,11 +348,8 @@
properties.getInt(actionName + TRACE_THRESHOLD_SUFFIX,
legacyActionTraceThreshold)));
}
- if (DEBUG) {
- Log.d(TAG, "updated action properties: " + mActionPropertiesMap);
- }
+ onDeviceConfigPropertiesUpdated(mActionPropertiesMap);
}
- mDeviceConfigPropertiesUpdated.open();
}
/**
@@ -477,7 +473,7 @@
*/
public void onActionStart(@Action int action, String tag) {
synchronized (mLock) {
- if (!isEnabled()) {
+ if (!isEnabled(action)) {
return;
}
// skip if the action is already instrumenting.
@@ -501,7 +497,7 @@
*/
public void onActionEnd(@Action int action) {
synchronized (mLock) {
- if (!isEnabled()) {
+ if (!isEnabled(action)) {
return;
}
Session session = mSessions.get(action);
@@ -540,6 +536,24 @@
}
/**
+ * Testing API to get the time when a given action was started.
+ *
+ * @param action Action which to retrieve start time from
+ * @return Elapsed realtime timestamp when the action started. -1 if the action is not active.
+ * @hide
+ */
+ @VisibleForTesting
+ @ElapsedRealtimeLong
+ public long getActiveActionStartTime(@Action int action) {
+ synchronized (mLock) {
+ if (mSessions.contains(action)) {
+ return mSessions.get(action).mStartRtc;
+ }
+ return -1;
+ }
+ }
+
+ /**
* Logs an action that has started and ended. This needs to be called from the main thread.
*
* @param action The action to end. One of the ACTION_* values.
@@ -549,6 +563,9 @@
boolean shouldSample;
int traceThreshold;
synchronized (mLock) {
+ if (!isEnabled(action)) {
+ return;
+ }
ActionProperties actionProperties = mActionPropertiesMap.get(action);
if (actionProperties == null) {
return;
@@ -559,28 +576,24 @@
traceThreshold = actionProperties.getTraceThreshold();
}
- if (traceThreshold > 0 && duration >= traceThreshold) {
- PerfettoTrigger.trigger(getTraceTriggerNameForAction(action));
+ boolean shouldTriggerPerfettoTrace = traceThreshold > 0 && duration >= traceThreshold;
+
+ if (DEBUG) {
+ Log.i(TAG, "logAction: " + getNameOfAction(STATSD_ACTION[action])
+ + " duration=" + duration
+ + " shouldSample=" + shouldSample
+ + " shouldTriggerPerfettoTrace=" + shouldTriggerPerfettoTrace);
}
- logActionDeprecated(action, duration, shouldSample);
- }
-
- /**
- * Logs an action that has started and ended. This needs to be called from the main thread.
- *
- * @param action The action to end. One of the ACTION_* values.
- * @param duration The duration of the action in ms.
- * @param writeToStatsLog Whether to write the measured latency to FrameworkStatsLog.
- */
- public static void logActionDeprecated(
- @Action int action, int duration, boolean writeToStatsLog) {
- Log.i(TAG, getNameOfAction(STATSD_ACTION[action]) + " latency=" + duration);
EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration);
-
- if (writeToStatsLog) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration);
+ if (shouldTriggerPerfettoTrace) {
+ onTriggerPerfetto(getTraceTriggerNameForAction(action));
+ }
+ if (shouldSample) {
+ onLogToFrameworkStats(
+ new FrameworkStatsLogEvent(action, FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED,
+ STATSD_ACTION[action], duration)
+ );
}
}
@@ -642,10 +655,10 @@
}
@VisibleForTesting
- static class ActionProperties {
+ public static class ActionProperties {
static final String ENABLE_SUFFIX = "_enable";
static final String SAMPLE_INTERVAL_SUFFIX = "_sample_interval";
- // TODO: migrate all usages of the legacy trace theshold property
+ // TODO: migrate all usages of the legacy trace threshold property
static final String LEGACY_TRACE_THRESHOLD_SUFFIX = "";
static final String TRACE_THRESHOLD_SUFFIX = "_trace_threshold";
@@ -655,7 +668,8 @@
private final int mSamplingInterval;
private final int mTraceThreshold;
- ActionProperties(
+ @VisibleForTesting
+ public ActionProperties(
@Action int action,
boolean enabled,
int samplingInterval,
@@ -668,20 +682,24 @@
this.mTraceThreshold = traceThreshold;
}
+ @VisibleForTesting
@Action
- int getAction() {
+ public int getAction() {
return mAction;
}
- boolean isEnabled() {
+ @VisibleForTesting
+ public boolean isEnabled() {
return mEnabled;
}
- int getSamplingInterval() {
+ @VisibleForTesting
+ public int getSamplingInterval() {
return mSamplingInterval;
}
- int getTraceThreshold() {
+ @VisibleForTesting
+ public int getTraceThreshold() {
return mTraceThreshold;
}
@@ -694,5 +712,103 @@
+ ", mTraceThreshold=" + mTraceThreshold
+ "}";
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null) {
+ return false;
+ }
+ if (!(o instanceof ActionProperties)) {
+ return false;
+ }
+ ActionProperties that = (ActionProperties) o;
+ return mAction == that.mAction
+ && mEnabled == that.mEnabled
+ && mSamplingInterval == that.mSamplingInterval
+ && mTraceThreshold == that.mTraceThreshold;
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mAction;
+ _hash = 31 * _hash + Boolean.hashCode(mEnabled);
+ _hash = 31 * _hash + mSamplingInterval;
+ _hash = 31 * _hash + mTraceThreshold;
+ return _hash;
+ }
+ }
+
+ /**
+ * Testing method intended to be overridden to determine when the LatencyTracker's device
+ * properties are updated.
+ */
+ @VisibleForTesting
+ public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) {
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties);
+ }
+ }
+
+ /**
+ * Testing class intended to be overridden to determine when LatencyTracker triggers perfetto.
+ */
+ @VisibleForTesting
+ public void onTriggerPerfetto(String triggerName) {
+ if (DEBUG) {
+ Log.i(TAG, "onTriggerPerfetto: triggerName=" + triggerName);
+ }
+ PerfettoTrigger.trigger(triggerName);
+ }
+
+ /**
+ * Testing method intended to be overridden to determine when LatencyTracker writes to
+ * FrameworkStatsLog.
+ */
+ @VisibleForTesting
+ public void onLogToFrameworkStats(FrameworkStatsLogEvent event) {
+ if (DEBUG) {
+ Log.i(TAG, "onLogToFrameworkStats: event=" + event);
+ }
+ FrameworkStatsLog.write(event.logCode, event.statsdAction, event.durationMillis);
+ }
+
+ /**
+ * Testing class intended to reject what should be written to the {@link FrameworkStatsLog}
+ *
+ * <p>This class is used in {@link #onLogToFrameworkStats(FrameworkStatsLogEvent)} for test code
+ * to observer when and what information is being logged by {@link LatencyTracker}
+ */
+ @VisibleForTesting
+ public static class FrameworkStatsLogEvent {
+
+ @VisibleForTesting
+ public final int action;
+ @VisibleForTesting
+ public final int logCode;
+ @VisibleForTesting
+ public final int statsdAction;
+ @VisibleForTesting
+ public final int durationMillis;
+
+ private FrameworkStatsLogEvent(int action, int logCode, int statsdAction,
+ int durationMillis) {
+ this.action = action;
+ this.logCode = logCode;
+ this.statsdAction = statsdAction;
+ this.durationMillis = durationMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "FrameworkStatsLogEvent{"
+ + " logCode=" + logCode
+ + ", statsdAction=" + statsdAction
+ + ", durationMillis=" + durationMillis
+ + "}";
+ }
}
}
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 1808bd5..9be8ea7 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -6,3 +6,4 @@
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
+per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index bd85874..bce53328 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -5,6 +5,9 @@
# Connectivity
per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
+# Choreographer
+per-file android_view_DisplayEventReceiver* = file:platform/frameworks/native:/services/surfaceflinger/OWNERS
+
# CPU
per-file *Cpu* = file:/core/java/com/android/internal/os/CPU_OWNERS
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d62f1cf..ce806a0 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2296,7 +2296,9 @@
// region shared with the child process we reduce the number of pages that
// transition to the private-dirty state when malloc adjusts the meta-data
// on each of the pages it is managing after the fork.
- mallopt(M_PURGE, 0);
+ if (mallopt(M_PURGE_ALL, 0) != 1) {
+ mallopt(M_PURGE, 0);
+ }
}
pid_t pid = fork();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 092f6e5..4c62723 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4482,11 +4482,23 @@
<permission android:name="android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"
android:protectionLevel="signature|privileged" />
+ <!-- Allows specifying candidate credential providers to be queried in Credential Manager
+ get flows, or to be preferred as a default in the Credential Manager create flows.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS"
+ android:protectionLevel="normal" />
+
<!-- Allows a browser to invoke credential manager APIs on behalf of another RP.
<p>Protection level: normal -->
<permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"
android:protectionLevel="normal" />
+ <!-- Allows a browser to invoke the set of query apis to get metadata about credential
+ candidates prepared during the CredentialManager.prepareGetCredential API.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS"
+ android:protectionLevel="normal" />
+
<!-- Allows permission to use Credential Manager UI for providing and saving credentials
@hide -->
<permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
diff --git a/core/res/TEST_MAPPING b/core/res/TEST_MAPPING
index 9185bae..4d09076 100644
--- a/core/res/TEST_MAPPING
+++ b/core/res/TEST_MAPPING
@@ -1,13 +1,13 @@
{
"presubmit": [
{
- "name": "CtsPermission2TestCases",
+ "name": "CtsPermissionPolicyTestCases",
"options": [
{
- "include-filter": "android.permission2.cts.PermissionPolicyTest#platformPermissionPolicyIsUnaltered"
+ "include-filter": "android.permissionpolicy.cts.PermissionPolicyTest#platformPermissionPolicyIsUnaltered"
},
{
- "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+ "include-filter": "android.permissionpolicy.cts.RuntimePermissionProperties"
}
]
}
diff --git a/core/res/res/drawable/accessibility_magnification_thumbnail_background_bg.xml b/core/res/res/drawable/accessibility_magnification_thumbnail_background_bg.xml
index 7b82b4d..bb6dc46 100644
--- a/core/res/res/drawable/accessibility_magnification_thumbnail_background_bg.xml
+++ b/core/res/res/drawable/accessibility_magnification_thumbnail_background_bg.xml
@@ -14,13 +14,13 @@
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-<item>
+ <item>
<shape android:shape="rectangle">
- <stroke
- android:width="1dp"
- android:color="@color/accessibility_magnification_thumbnail_stroke_color" />
- <corners android:radius="4dp"/>
- <solid android:color="@color/accessibility_magnification_thumbnail_background_color" />
+ <stroke
+ android:width="@dimen/accessibility_magnification_thumbnail_container_stroke_width"
+ android:color="@color/accessibility_magnification_thumbnail_container_stroke_color" />
+ <corners android:radius="8dp"/>
+ <solid android:color="@color/accessibility_magnification_thumbnail_container_background_color" />
</shape>
-</item>
+ </item>
</layer-list>
diff --git a/core/res/res/drawable/accessibility_magnification_thumbnail_background_fg.xml b/core/res/res/drawable/accessibility_magnification_thumbnail_background_fg.xml
new file mode 100644
index 0000000..ef77b34
--- /dev/null
+++ b/core/res/res/drawable/accessibility_magnification_thumbnail_background_fg.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <!-- We draw the border as a foreground too so the thumbnail doesn't encroach and overlap
+ the rounded corners.
+ We also draw it on the background, otherwise you can see a tiny bit of the black fill
+ poking out around the white border. -->
+ <stroke
+ android:width="@dimen/accessibility_magnification_thumbnail_container_stroke_width"
+ android:color="@color/accessibility_magnification_thumbnail_container_stroke_color" />
+ <corners android:radius="8dp"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/core/res/res/drawable/accessibility_magnification_thumbnail_bg.xml b/core/res/res/drawable/accessibility_magnification_thumbnail_bg.xml
index 77ba94e..cf0aee5 100644
--- a/core/res/res/drawable/accessibility_magnification_thumbnail_bg.xml
+++ b/core/res/res/drawable/accessibility_magnification_thumbnail_bg.xml
@@ -14,10 +14,13 @@
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-<item>
+ <item>
<shape android:shape="rectangle">
- <corners android:radius="2dp"/>
- <solid android:color="@color/accessibility_magnification_thumbnail_color" />
+ <stroke
+ android:width="2dp"
+ android:color="@color/accessibility_magnification_thumbnail_stroke_color" />
+ <corners android:radius="2dp"/>
+ <solid android:color="@color/accessibility_magnification_thumbnail_background_color" />
</shape>
-</item>
+ </item>
</layer-list>
diff --git a/core/res/res/drawable/ic_menu_redo_material.xml b/core/res/res/drawable/ic_menu_redo_material.xml
new file mode 100644
index 0000000..28e6733
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_redo_material.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9.9,19Q7.475,19 5.738,17.425Q4,15.85 4,13.5Q4,11.15 5.738,9.575Q7.475,8 9.9,8H16.2L13.6,5.4L15,4L20,9L15,14L13.6,12.6L16.2,10H9.9Q8.325,10 7.163,11Q6,12 6,13.5Q6,15 7.163,16Q8.325,17 9.9,17H17V19Z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_menu_undo_material.xml b/core/res/res/drawable/ic_menu_undo_material.xml
new file mode 100644
index 0000000..8e05a52
--- /dev/null
+++ b/core/res/res/drawable/ic_menu_undo_material.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2023 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.1,19H7V17H14.1Q15.675,17 16.837,16Q18,15 18,13.5Q18,12 16.837,11Q15.675,10 14.1,10H7.8L10.4,12.6L9,14L4,9L9,4L10.4,5.4L7.8,8H14.1Q16.525,8 18.263,9.575Q20,11.15 20,13.5Q20,15.85 18.263,17.425Q16.525,19 14.1,19Z"/>
+</vector>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 16a8bb7..710a70a 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -116,7 +116,8 @@
<com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_headerless_line_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
>
<!-- This is the simplest way to keep this text vertically centered without
gravity="center_vertical" which causes jumpiness in expansion animations. -->
diff --git a/core/res/res/layout/thumbnail_background_view.xml b/core/res/res/layout/thumbnail_background_view.xml
index 0ba01e9..423af83 100644
--- a/core/res/res/layout/thumbnail_background_view.xml
+++ b/core/res/res/layout/thumbnail_background_view.xml
@@ -16,8 +16,12 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/accessibility_magnification_thumbnail_background_bg">
+ android:background="@drawable/accessibility_magnification_thumbnail_background_bg"
+ android:foreground="@drawable/accessibility_magnification_thumbnail_background_fg"
+ android:padding="@dimen/accessibility_magnification_thumbnail_container_stroke_width"
+>
<View
+ android:padding="@dimen/accessibility_magnification_thumbnail_container_stroke_width"
android:id="@+id/accessibility_magnification_thumbnail_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 41c8b4a..cd25726 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -918,6 +918,10 @@
<attr name="actionModeFindDrawable" format="reference" />
<!-- Drawable to use for the Web Search action button in WebView selection action modes. -->
<attr name="actionModeWebSearchDrawable" format="reference" />
+ <!-- Drawable to use for the Undo action button in WebView selection action modes. -->
+ <attr name="actionModeUndoDrawable" format="reference" />
+ <!-- Drawable to use for the Redo action button in WebView selection action modes. -->
+ <attr name="actionModeRedoDrawable" format="reference" />
<!-- PopupWindow style to use for action modes when showing as a window overlay. -->
<attr name="actionModePopupWindowStyle" format="reference" />
@@ -10089,4 +10093,27 @@
<!-- Maximum width of height drawable. Drawables exceeding this size will be downsampled. -->
<attr name="maxDrawableHeight" format="dimension"/>
</declare-styleable>
+
+ <!-- =============================== -->
+ <!-- Credential Manager attributes -->
+ <!-- =============================== -->
+ <eat-comment />
+
+ <!-- Contains Credential Provider related metadata. Since providers are exposed
+ as services these should live under the service.
+ -->
+ <declare-styleable name="CredentialProvider">
+ <!-- A string that is displayed to the user in the Credential Manager settings
+ screen that can be used to provide more information about a provider. For
+ longer strings (40 char) it will be truncated. If multiple services
+ show the subtitle then the string will be joined together. -->
+ <attr name="settingsSubtitle" format="string" />
+ </declare-styleable>
+
+ <!-- A list of capabilities that indicates to the OS what kinds of credentials
+ this provider supports. This list is defined in CredentialProviderService. -->
+ <declare-styleable name="CredentialProvider_Capabilities" parent="CredentialProvider">
+ <!-- An individual capability declared by the provider. -->
+ <attr name="capability" format="string" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index dc75658..31e5f32 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -559,9 +559,10 @@
<color name="accessibility_color_inversion_background">#546E7A</color>
<!-- Fullscreen magnification thumbnail color -->
- <color name="accessibility_magnification_thumbnail_stroke_color">#E0E0E0</color>
- <color name="accessibility_magnification_thumbnail_background_color">#FCFCFC</color>
- <color name="accessibility_magnification_thumbnail_color">#252525</color>
+ <color name="accessibility_magnification_thumbnail_stroke_color">#0C0C0C</color>
+ <color name="accessibility_magnification_thumbnail_background_color">#B3FFFFFF</color>
+ <color name="accessibility_magnification_thumbnail_container_background_color">#99000000</color>
+ <color name="accessibility_magnification_thumbnail_container_stroke_color">#FFFFFF</color>
<!-- Color of camera light when camera is in use -->
<color name="camera_privacy_light_day">#FFFFFF</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 19fa7a3..781c213 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5498,6 +5498,10 @@
<!-- Whether vertical reachability repositioning is allowed for letterboxed fullscreen apps. -->
<bool name="config_letterboxIsVerticalReachabilityEnabled">false</bool>
+ <!-- Whether book mode automatic horizontal reachability positioning is allowed for letterboxed
+ fullscreen apps -->
+ <bool name="config_letterboxIsAutomaticReachabilityInBookModeEnabled">false</bool>
+
<!-- Default horizontal position of the letterboxed app window when reachability is
enabled and an app is fullscreen in landscape device orientation. When reachability is
enabled, the position can change between left, center and right. This config defines the
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 80bf795..b1b1edf 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -356,7 +356,7 @@
<dimen name="notification_headerless_margin_twoline">20dp</dimen>
<!-- The height of each of the 1 or 2 lines in the headerless notification template -->
- <dimen name="notification_headerless_line_height">24sp</dimen>
+ <dimen name="notification_headerless_line_height">24dp</dimen>
<!-- vertical margin for the headerless notification content -->
<dimen name="notification_headerless_min_height">56dp</dimen>
@@ -609,6 +609,9 @@
<!-- padding of fullscreen magnification thumbnail -->
<dimen name="accessibility_magnification_thumbnail_padding">12dp</dimen>
+ <!-- width of the border of the magnification thumbnail -->
+ <dimen name="accessibility_magnification_thumbnail_container_stroke_width">4dp</dimen>
+
<!-- The padding ratio of the Accessibility icon foreground drawable -->
<item name="accessibility_icon_foreground_padding_ratio" type="dimen">21.88%</item>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 696c0ed..12a6c52 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -130,6 +130,8 @@
<public name="focusedSearchResultHighlightColor" />
<public name="stylusHandwritingSettingsActivity" />
<public name="windowNoMoveAnimation" />
+ <public name="settingsSubtitle" />
+ <public name="capability" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 251db6d..b0ff86c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4487,6 +4487,7 @@
<java-symbol type="dimen" name="config_letterboxTabletopModePositionMultiplier" />
<java-symbol type="bool" name="config_letterboxIsHorizontalReachabilityEnabled" />
<java-symbol type="bool" name="config_letterboxIsVerticalReachabilityEnabled" />
+ <java-symbol type="bool" name="config_letterboxIsAutomaticReachabilityInBookModeEnabled" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForHorizontalReachability" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForBookModeReachability" />
@@ -4514,10 +4515,13 @@
<!-- FullScreenMagnification thumbnail -->
<java-symbol type="layout" name="thumbnail_background_view" />
<java-symbol type="drawable" name="accessibility_magnification_thumbnail_background_bg" />
+ <java-symbol type="drawable" name="accessibility_magnification_thumbnail_background_fg" />
<java-symbol type="drawable" name="accessibility_magnification_thumbnail_bg" />
<java-symbol type="color" name="accessibility_magnification_thumbnail_stroke_color" />
<java-symbol type="color" name="accessibility_magnification_thumbnail_background_color" />
- <java-symbol type="color" name="accessibility_magnification_thumbnail_color" />
+ <java-symbol type="color" name="accessibility_magnification_thumbnail_container_background_color" />
+ <java-symbol type="color" name="accessibility_magnification_thumbnail_container_stroke_color" />
+ <java-symbol type="dimen" name="accessibility_magnification_thumbnail_container_stroke_width" />
<java-symbol type="dimen" name="accessibility_magnification_thumbnail_padding" />
<java-symbol type="id" name="accessibility_magnification_thumbnail_view" />
<!-- Package with global data query permissions for AppSearch -->
@@ -5033,4 +5037,6 @@
<java-symbol name="materialColorSurfaceContainer" type="attr"/>
<java-symbol name="materialColorSurfaceContainer" type="attr"/>
+ <java-symbol type="attr" name="actionModeUndoDrawable" />
+ <java-symbol type="attr" name="actionModeRedoDrawable" />
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index e96de582..bdbf96b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -358,6 +358,8 @@
<item name="actionModeShareDrawable">@drawable/ic_menu_share_holo_dark</item>
<item name="actionModeFindDrawable">@drawable/ic_menu_find_holo_dark</item>
<item name="actionModeWebSearchDrawable">@drawable/ic_menu_search</item>
+ <item name="actionModeUndoDrawable">@drawable/ic_menu_undo_material</item>
+ <item name="actionModeRedoDrawable">@drawable/ic_menu_redo_material</item>
<item name="actionBarTabStyle">@style/Widget.ActionBar.TabView</item>
<item name="actionBarTabBarStyle">@style/Widget.ActionBar.TabBar</item>
<item name="actionBarTabTextStyle">@style/Widget.ActionBar.TabText</item>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index ba05791..aea0178 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -259,20 +259,6 @@
}
@Test
- public void programSelectorToHalProgramSelector_withInvalidDabSelector_returnsNull() {
- ProgramSelector invalidDbSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
- TEST_DAB_SID_EXT_ID,
- new ProgramSelector.Identifier[0],
- new long[0]);
-
- android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
- ConversionUtils.programSelectorToHalProgramSelector(invalidDbSelector);
-
- expect.withMessage("Invalid HAL DAB selector without required secondary ids")
- .that(invalidHalDabSelector).isNull();
- }
-
- @Test
public void programSelectorFromHalProgramSelector_withValidSelector() {
android.hardware.broadcastradio.ProgramSelector halDabSelector =
AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
@@ -289,18 +275,6 @@
}
@Test
- public void programSelectorFromHalProgramSelector_withInvalidSelector_returnsNull() {
- android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
- AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{});
-
- ProgramSelector invalidDabSelector =
- ConversionUtils.programSelectorFromHalProgramSelector(invalidHalDabSelector);
-
- expect.withMessage("Invalid DAB selector without required secondary ids")
- .that(invalidDabSelector).isNull();
- }
-
- @Test
public void programInfoFromHalProgramInfo_withValidProgramInfo() {
android.hardware.broadcastradio.ProgramSelector halDabSelector =
AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e811bb6..3ea1592 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -20,6 +20,7 @@
"BinderProxyCountingTestService/src/**/*.java",
"BinderDeathRecipientHelperApp/src/**/*.java",
"aidl/**/I*.aidl",
+ ":FrameworksCoreTestDoubles-sources",
],
aidl: {
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
index 7a1de0c..a753870 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
@@ -435,9 +435,11 @@
mActivityRule.runOnUiThread(s::start);
while (!listener.endIsCalled) {
- boolean passedStartDelay = a1.isStarted() || a2.isStarted() || a3.isStarted() ||
- a4.isStarted() || a5.isStarted();
- assertEquals(passedStartDelay, s.isRunning());
+ mActivityRule.runOnUiThread(() -> {
+ boolean passedStartDelay = a1.isStarted() || a2.isStarted() || a3.isStarted()
+ || a4.isStarted() || a5.isStarted();
+ assertEquals(passedStartDelay, s.isRunning());
+ });
Thread.sleep(50);
}
assertFalse(s.isRunning());
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
new file mode 100644
index 0000000..43266a5
--- /dev/null
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.PollingCheck;
+import android.view.View;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.filters.MediumTest;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class AnimatorSetCallsTest {
+ @Rule
+ public final ActivityScenarioRule<AnimatorSetActivity> mRule =
+ new ActivityScenarioRule<>(AnimatorSetActivity.class);
+
+ private AnimatorSetActivity mActivity;
+ private AnimatorSet mSet1;
+ private AnimatorSet mSet2;
+ private ObjectAnimator mAnimator;
+ private CountListener mListener1;
+ private CountListener mListener2;
+ private CountListener mListener3;
+
+ @Before
+ public void setUp() throws Exception {
+ mRule.getScenario().onActivity((activity) -> {
+ mActivity = activity;
+ View square = mActivity.findViewById(R.id.square1);
+
+ mSet1 = new AnimatorSet();
+ mListener1 = new CountListener();
+ mSet1.addListener(mListener1);
+ mSet1.addPauseListener(mListener1);
+
+ mSet2 = new AnimatorSet();
+ mListener2 = new CountListener();
+ mSet2.addListener(mListener2);
+ mSet2.addPauseListener(mListener2);
+
+ mAnimator = ObjectAnimator.ofFloat(square, "translationX", 0f, 100f);
+ mListener3 = new CountListener();
+ mAnimator.addListener(mListener3);
+ mAnimator.addPauseListener(mListener3);
+ mAnimator.setDuration(1);
+
+ mSet2.play(mAnimator);
+ mSet1.play(mSet2);
+ });
+ }
+
+ @Test
+ public void startEndCalledOnChildren() {
+ mRule.getScenario().onActivity((a) -> mSet1.start());
+ waitForOnUiThread(() -> mListener1.endForward > 0);
+
+ // only startForward and endForward should have been called once
+ mListener1.assertValues(
+ 1, 0, 1, 0, 0, 0, 0, 0
+ );
+ mListener2.assertValues(
+ 1, 0, 1, 0, 0, 0, 0, 0
+ );
+ mListener3.assertValues(
+ 1, 0, 1, 0, 0, 0, 0, 0
+ );
+ }
+
+ @Test
+ public void cancelCalledOnChildren() {
+ mRule.getScenario().onActivity((a) -> {
+ mSet1.start();
+ mSet1.cancel();
+ });
+ waitForOnUiThread(() -> mListener1.endForward > 0);
+
+ // only startForward and endForward should have been called once
+ mListener1.assertValues(
+ 1, 0, 1, 0, 1, 0, 0, 0
+ );
+ mListener2.assertValues(
+ 1, 0, 1, 0, 1, 0, 0, 0
+ );
+ mListener3.assertValues(
+ 1, 0, 1, 0, 1, 0, 0, 0
+ );
+ }
+
+ @Test
+ public void startEndReversedCalledOnChildren() {
+ mRule.getScenario().onActivity((a) -> mSet1.reverse());
+ waitForOnUiThread(() -> mListener1.endReverse > 0);
+
+ // only startForward and endForward should have been called once
+ mListener1.assertValues(
+ 0, 1, 0, 1, 0, 0, 0, 0
+ );
+ mListener2.assertValues(
+ 0, 1, 0, 1, 0, 0, 0, 0
+ );
+ mListener3.assertValues(
+ 0, 1, 0, 1, 0, 0, 0, 0
+ );
+ }
+
+ @Test
+ public void pauseResumeCalledOnChildren() {
+ mRule.getScenario().onActivity((a) -> {
+ mSet1.start();
+ mSet1.pause();
+ });
+ waitForOnUiThread(() -> mListener1.pause > 0);
+
+ // only startForward and pause should have been called once
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 0
+ );
+ mListener2.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 0
+ );
+ mListener3.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 0
+ );
+
+ mRule.getScenario().onActivity((a) -> mSet1.resume());
+ waitForOnUiThread(() -> mListener1.endForward > 0);
+
+ // resume and endForward should have been called once
+ mListener1.assertValues(
+ 1, 0, 1, 0, 0, 0, 1, 1
+ );
+ mListener2.assertValues(
+ 1, 0, 1, 0, 0, 0, 1, 1
+ );
+ mListener3.assertValues(
+ 1, 0, 1, 0, 0, 0, 1, 1
+ );
+ }
+
+ @Test
+ public void updateOnlyWhileChangingValues() {
+ ArrayList<Float> updateValues = new ArrayList<>();
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateValues.add((Float) animation.getAnimatedValue());
+ }
+ });
+
+ mSet1.setCurrentPlayTime(0);
+
+ assertEquals(1, updateValues.size());
+ assertEquals(0f, updateValues.get(0), 0f);
+ }
+
+ @Test
+ public void updateOnlyWhileRunning() {
+ ArrayList<Float> updateValues = new ArrayList<>();
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateValues.add((Float) animation.getAnimatedValue());
+ }
+ });
+
+ mRule.getScenario().onActivity((a) -> {
+ mSet1.start();
+ });
+
+ waitForOnUiThread(() -> mListener1.endForward > 0);
+
+ // the duration is only 1ms, so there should only be two values, 0 and 100.
+ assertEquals(0f, updateValues.get(0), 0f);
+ assertEquals(100f, updateValues.get(updateValues.size() - 1), 0f);
+
+ // now check all the values in the middle, which can never go from 100->0.
+ boolean isAtEnd = false;
+ for (int i = 1; i < updateValues.size() - 1; i++) {
+ float actual = updateValues.get(i);
+ if (actual == 100f) {
+ isAtEnd = true;
+ }
+ float expected = isAtEnd ? 100f : 0f;
+ assertEquals(expected, actual, 0f);
+ }
+ }
+
+ @Test
+ public void pauseResumeSeekingAnimators() {
+ ValueAnimator animator2 = ValueAnimator.ofFloat(0f, 1f);
+ mSet2.play(animator2).after(mAnimator);
+ mSet2.setStartDelay(100);
+ mSet1.setStartDelay(100);
+ mAnimator.setDuration(100);
+
+ mActivity.runOnUiThread(() -> {
+ mSet1.setCurrentPlayTime(0);
+ mSet1.pause();
+
+ // only startForward and pause should have been called once
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 0
+ );
+ mListener2.assertValues(
+ 0, 0, 0, 0, 0, 0, 0, 0
+ );
+ mListener3.assertValues(
+ 0, 0, 0, 0, 0, 0, 0, 0
+ );
+
+ mSet1.resume();
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 1
+ );
+ mListener2.assertValues(
+ 0, 0, 0, 0, 0, 0, 0, 0
+ );
+ mListener3.assertValues(
+ 0, 0, 0, 0, 0, 0, 0, 0
+ );
+
+ mSet1.setCurrentPlayTime(200);
+
+ // resume and endForward should have been called once
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 1
+ );
+ mListener2.assertValues(
+ 1, 0, 0, 0, 0, 0, 0, 0
+ );
+ mListener3.assertValues(
+ 1, 0, 0, 0, 0, 0, 0, 0
+ );
+
+ mSet1.pause();
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 2, 1
+ );
+ mListener2.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 0
+ );
+ mListener3.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 0
+ );
+ mSet1.resume();
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 2, 2
+ );
+ mListener2.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 1
+ );
+ mListener3.assertValues(
+ 1, 0, 0, 0, 0, 0, 1, 1
+ );
+
+ // now go to animator2
+ mSet1.setCurrentPlayTime(400);
+ mSet1.pause();
+ mSet1.resume();
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 3, 3
+ );
+ mListener2.assertValues(
+ 1, 0, 0, 0, 0, 0, 2, 2
+ );
+ mListener3.assertValues(
+ 1, 0, 1, 0, 0, 0, 1, 1
+ );
+
+ // now go back to mAnimator
+ mSet1.setCurrentPlayTime(250);
+ mSet1.pause();
+ mSet1.resume();
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 4, 4
+ );
+ mListener2.assertValues(
+ 1, 0, 0, 0, 0, 0, 3, 3
+ );
+ mListener3.assertValues(
+ 1, 1, 1, 0, 0, 0, 2, 2
+ );
+
+ // now go back to before mSet2 was being run
+ mSet1.setCurrentPlayTime(1);
+ mSet1.pause();
+ mSet1.resume();
+ mListener1.assertValues(
+ 1, 0, 0, 0, 0, 0, 5, 5
+ );
+ mListener2.assertValues(
+ 1, 0, 0, 1, 0, 0, 3, 3
+ );
+ mListener3.assertValues(
+ 1, 1, 1, 1, 0, 0, 2, 2
+ );
+ });
+ }
+
+ @Test
+ public void endInCancel() throws Throwable {
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mSet1.end();
+ }
+ };
+ mSet1.addListener(listener);
+ mActivity.runOnUiThread(() -> {
+ mSet1.start();
+ mSet1.cancel();
+ // Should go to the end value
+ View square = mActivity.findViewById(R.id.square1);
+ assertEquals(100f, square.getTranslationX(), 0.001f);
+ });
+ }
+
+ @Test
+ public void reentrantStart() throws Throwable {
+ CountDownLatch latch = new CountDownLatch(3);
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation, boolean isReverse) {
+ mSet1.start();
+ latch.countDown();
+ }
+ };
+ mSet1.addListener(listener);
+ mSet2.addListener(listener);
+ mAnimator.addListener(listener);
+ mActivity.runOnUiThread(() -> mSet1.start());
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+ mActivity.runOnUiThread(() -> {});
+ }
+
+ @Test
+ public void reentrantEnd() throws Throwable {
+ CountDownLatch latch = new CountDownLatch(3);
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ mSet1.end();
+ latch.countDown();
+ }
+ };
+ mSet1.addListener(listener);
+ mSet2.addListener(listener);
+ mAnimator.addListener(listener);
+ mActivity.runOnUiThread(() -> {
+ mSet1.start();
+ mSet1.end();
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+ mActivity.runOnUiThread(() -> {});
+ }
+
+ @Test
+ public void reentrantPause() throws Throwable {
+ CountDownLatch latch = new CountDownLatch(3);
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationPause(Animator animation) {
+ mSet1.pause();
+ latch.countDown();
+ }
+ };
+ mSet1.addPauseListener(listener);
+ mSet2.addPauseListener(listener);
+ mAnimator.addPauseListener(listener);
+ mActivity.runOnUiThread(() -> {
+ mSet1.start();
+ mSet1.pause();
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+ mActivity.runOnUiThread(() -> {});
+ }
+
+ @Test
+ public void reentrantResume() throws Throwable {
+ CountDownLatch latch = new CountDownLatch(3);
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationResume(Animator animation) {
+ mSet1.resume();
+ latch.countDown();
+ }
+ };
+ mSet1.addPauseListener(listener);
+ mSet2.addPauseListener(listener);
+ mAnimator.addPauseListener(listener);
+ mActivity.runOnUiThread(() -> {
+ mSet1.start();
+ mSet1.pause();
+ mSet1.resume();
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ // Make sure that the UI thread hasn't been destroyed by a stack overflow...
+ mActivity.runOnUiThread(() -> {});
+ }
+
+ private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) {
+ final boolean[] value = new boolean[1];
+ PollingCheck.waitFor(() -> {
+ mActivity.runOnUiThread(() -> value[0] = condition.canProceed());
+ return value[0];
+ });
+ }
+
+ private static class CountListener implements Animator.AnimatorListener,
+ Animator.AnimatorPauseListener {
+ public int startNoParam;
+ public int endNoParam;
+ public int startReverse;
+ public int startForward;
+ public int endForward;
+ public int endReverse;
+ public int cancel;
+ public int repeat;
+ public int pause;
+ public int resume;
+
+ public void assertValues(
+ int startForward,
+ int startReverse,
+ int endForward,
+ int endReverse,
+ int cancel,
+ int repeat,
+ int pause,
+ int resume
+ ) {
+ assertEquals("onAnimationStart() without direction", 0, startNoParam);
+ assertEquals("onAnimationEnd() without direction", 0, endNoParam);
+ assertEquals("onAnimationStart(forward)", startForward, this.startForward);
+ assertEquals("onAnimationStart(reverse)", startReverse, this.startReverse);
+ assertEquals("onAnimationEnd(forward)", endForward, this.endForward);
+ assertEquals("onAnimationEnd(reverse)", endReverse, this.endReverse);
+ assertEquals("onAnimationCancel()", cancel, this.cancel);
+ assertEquals("onAnimationRepeat()", repeat, this.repeat);
+ assertEquals("onAnimationPause()", pause, this.pause);
+ assertEquals("onAnimationResume()", resume, this.resume);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation, boolean isReverse) {
+ if (isReverse) {
+ startReverse++;
+ } else {
+ startForward++;
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation, boolean isReverse) {
+ if (isReverse) {
+ endReverse++;
+ } else {
+ endForward++;
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startNoParam++;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endNoParam++;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancel++;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ repeat++;
+ }
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ pause++;
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ resume++;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
index dee0a3e..a53d57f 100644
--- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
+++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java
@@ -40,6 +40,8 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@MediumTest
@@ -1067,6 +1069,64 @@
});
}
+ @Test
+ public void reentrantStart() throws Throwable {
+ CountDownLatch latch = new CountDownLatch(1);
+ a1.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation, boolean isReverse) {
+ a1.start();
+ latch.countDown();
+ }
+ });
+ mActivityRule.runOnUiThread(() -> a1.start());
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ // Make sure that the UI thread isn't blocked by an infinite loop:
+ mActivityRule.runOnUiThread(() -> {});
+ }
+
+ @Test
+ public void reentrantPause() throws Throwable {
+ CountDownLatch latch = new CountDownLatch(1);
+ a1.addPauseListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationPause(Animator animation) {
+ a1.pause();
+ latch.countDown();
+ }
+ });
+ mActivityRule.runOnUiThread(() -> {
+ a1.start();
+ a1.pause();
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ // Make sure that the UI thread isn't blocked by an infinite loop:
+ mActivityRule.runOnUiThread(() -> {});
+ }
+
+ @Test
+ public void reentrantResume() throws Throwable {
+ CountDownLatch latch = new CountDownLatch(1);
+ a1.addPauseListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationResume(Animator animation) {
+ a1.resume();
+ latch.countDown();
+ }
+ });
+ mActivityRule.runOnUiThread(() -> {
+ a1.start();
+ a1.pause();
+ a1.resume();
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ // Make sure that the UI thread isn't blocked by an infinite loop:
+ mActivityRule.runOnUiThread(() -> {});
+ }
+
class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener {
boolean wasRunning = false;
long firstRunningFrameTime = -1;
diff --git a/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java b/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java
index 81cd4da..8cc88ea 100644
--- a/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java
+++ b/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java
@@ -135,11 +135,15 @@
* @throws Exception
*/
@Before
- public void setUp() throws Exception {
+ public void setUp() throws Throwable {
final BasicAnimatorActivity activity = mActivityRule.getActivity();
Button button = activity.findViewById(R.id.animatingButton);
mAnimator = button.animate().x(100).y(100);
+ mActivityRule.runOnUiThread(() -> {
+ mAnimator.start();
+ mAnimator.cancel();
+ });
// mListener is the main testing mechanism of this file. The asserts of each test
// are embedded in the listener callbacks that it implements.
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 34d669b..6debbfe 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -63,7 +63,6 @@
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
-import android.app.Notification.CallStyle;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
@@ -1039,6 +1038,69 @@
}
@Test
+ public void areIconsDifferent_sameSmallIcon_false() {
+ Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
+ Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
+
+ assertThat(Notification.areIconsDifferent(n1, n2)).isFalse();
+ }
+
+ @Test
+ public void areIconsDifferent_differentSmallIcon_true() {
+ Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
+ Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build();
+
+ assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
+ }
+
+ @Test
+ public void areIconsDifferent_sameLargeIcon_false() {
+ Icon icon1 = Icon.createWithContentUri("uri");
+ Icon icon2 = Icon.createWithContentUri("uri");
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .setSmallIcon(1).setLargeIcon(icon1).build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .setSmallIcon(1).setLargeIcon(icon2).build();
+
+ // Note that this will almost certainly not happen for Icons created from Bitmaps, since
+ // their serialization/deserialization of Bitmaps (app -> system_process) results in a
+ // different getGenerationId() value. :(
+ assertThat(Notification.areIconsDifferent(n1, n2)).isFalse();
+ }
+
+ @Test
+ public void areIconsDifferent_differentLargeIcon_true() {
+ Icon icon1 = Icon.createWithContentUri("uri1");
+ Icon icon2 = Icon.createWithContentUri("uri2");
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .setSmallIcon(1).setLargeIcon(icon1).build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .setSmallIcon(2).setLargeIcon(icon2).build();
+
+ assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
+ }
+
+ @Test
+ public void areIconsDifferent_addedLargeIcon_true() {
+ Icon icon = Icon.createWithContentUri("uri");
+ Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
+ Notification n2 = new Notification.Builder(mContext, "test")
+ .setSmallIcon(2).setLargeIcon(icon).build();
+
+ assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
+ }
+
+ @Test
+ public void areIconsDifferent_removedLargeIcon_true() {
+ Icon icon = Icon.createWithContentUri("uri");
+ Notification n1 = new Notification.Builder(mContext, "test")
+ .setSmallIcon(1).setLargeIcon(icon).build();
+ Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build();
+
+ assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
+ }
+
+ @Test
public void testStyleChangeVisiblyDifferent_noStyles() {
Notification.Builder n1 = new Notification.Builder(mContext, "test");
Notification.Builder n2 = new Notification.Builder(mContext, "test");
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index e31d5ae..6f0c3d3 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -141,8 +141,9 @@
@Test
public void testGetCredential_nullRequest() {
+ GetCredentialRequest nullRequest = null;
assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(null, mMockActivity, null, mExecutor,
+ () -> mCredentialManager.getCredential(nullRequest, mMockActivity, null, mExecutor,
result -> {
}));
}
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayConfigTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayConfigTest.java
new file mode 100644
index 0000000..4e59064
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayConfigTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.SurfaceTexture;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.DisplayMetrics;
+import android.view.ContentRecordingSession;
+import android.view.Surface;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Tests for non-public APIs in {@link VirtualDisplayConfig}.
+ * See also related CTS tests.
+ *
+ * Run with:
+ * atest FrameworksCoreTests:VirtualDisplayConfigTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VirtualDisplayConfigTest {
+
+ private static final String NAME = "VirtualDisplayConfigTest";
+ private static final int WIDTH = 720;
+ private static final int HEIGHT = 480;
+ private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
+ private static final float REQUESTED_REFRESH_RATE = 30.0f;
+ private static final int FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+
+ // Values for hidden APIs.
+ private static final int DISPLAY_ID_TO_MIRROR = 10;
+ private static final IBinder WINDOW_TOKEN = new Binder("DisplayContentWindowToken");
+ private static final ContentRecordingSession CONTENT_RECORDING_SESSION =
+ ContentRecordingSession.createTaskSession(WINDOW_TOKEN);
+
+ private final Surface mSurface = new Surface(new SurfaceTexture(/*texName=*/1));
+
+ @Test
+ public void testParcelAndUnparcel_matches() {
+ final VirtualDisplayConfig originalConfig = buildGenericVirtualDisplay(NAME);
+
+ validateConstantFields(originalConfig);
+ assertThat(originalConfig.getName()).isEqualTo(NAME);
+
+
+ final Parcel parcel = Parcel.obtain();
+ originalConfig.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ final VirtualDisplayConfig recreatedConfig =
+ VirtualDisplayConfig.CREATOR.createFromParcel(parcel);
+
+ validateConstantFields(recreatedConfig);
+ assertThat(recreatedConfig.getName()).isEqualTo(NAME);
+ }
+
+ @Test
+ public void testEquals_matches() {
+ assertThat(buildGenericVirtualDisplay(NAME)).isEqualTo(buildGenericVirtualDisplay(NAME));
+ }
+
+ @Test
+ public void testEquals_different() {
+ assertThat(buildGenericVirtualDisplay(NAME + "2")).isNotEqualTo(
+ buildGenericVirtualDisplay(NAME));
+ }
+
+ private VirtualDisplayConfig buildGenericVirtualDisplay(String name) {
+ return new VirtualDisplayConfig.Builder(name, WIDTH, HEIGHT, DENSITY)
+ .setFlags(FLAGS)
+ .setSurface(mSurface)
+ .setDisplayCategories(Set.of("C1", "C2"))
+ .addDisplayCategory("C3")
+ .setRequestedRefreshRate(REQUESTED_REFRESH_RATE)
+ .setDisplayIdToMirror(DISPLAY_ID_TO_MIRROR)
+ .setWindowManagerMirroringEnabled(true)
+ .setContentRecordingSession(CONTENT_RECORDING_SESSION)
+ .build();
+ }
+
+ private void validateConstantFields(VirtualDisplayConfig config) {
+ assertThat(config.getWidth()).isEqualTo(WIDTH);
+ assertThat(config.getHeight()).isEqualTo(HEIGHT);
+ assertThat(config.getDensityDpi()).isEqualTo(DENSITY);
+ assertThat(config.getFlags()).isEqualTo(FLAGS);
+ assertThat(config.getSurface()).isNotNull();
+ assertThat(config.getDisplayCategories()).containsExactly("C1", "C2", "C3");
+ assertThat(config.getRequestedRefreshRate()).isEqualTo(REQUESTED_REFRESH_RATE);
+ assertThat(config.getDisplayIdToMirror()).isEqualTo(DISPLAY_ID_TO_MIRROR);
+ assertThat(config.isWindowManagerMirroringEnabled()).isTrue();
+ assertThat(config.getContentRecordingSession()).isEqualTo(CONTENT_RECORDING_SESSION);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 91fbe00..394ff0a 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -54,6 +54,7 @@
import android.content.Context;
import android.os.FileUtils.MemoryPipe;
import android.provider.DocumentsContract.Document;
+import android.util.DataUnit;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -504,31 +505,45 @@
@Test
public void testRoundStorageSize() throws Exception {
- final long M128 = 128000000L;
- final long M256 = 256000000L;
- final long M512 = 512000000L;
- final long G1 = 1000000000L;
- final long G2 = 2000000000L;
- final long G16 = 16000000000L;
- final long G32 = 32000000000L;
- final long G64 = 64000000000L;
+ final long GB1 = DataUnit.GIGABYTES.toBytes(1);
+ final long GiB1 = DataUnit.GIBIBYTES.toBytes(1);
+ final long GB2 = DataUnit.GIGABYTES.toBytes(2);
+ final long GiB2 = DataUnit.GIBIBYTES.toBytes(2);
+ final long GiB128 = DataUnit.GIBIBYTES.toBytes(128);
+ final long GB256 = DataUnit.GIGABYTES.toBytes(256);
+ final long GiB256 = DataUnit.GIBIBYTES.toBytes(256);
+ final long GB512 = DataUnit.GIGABYTES.toBytes(512);
+ final long GiB512 = DataUnit.GIBIBYTES.toBytes(512);
+ final long TB1 = DataUnit.TERABYTES.toBytes(1);
+ final long TiB1 = DataUnit.TEBIBYTES.toBytes(1);
+ final long TB2 = DataUnit.TERABYTES.toBytes(2);
+ final long TiB2 = DataUnit.TEBIBYTES.toBytes(2);
+ final long TB4 = DataUnit.TERABYTES.toBytes(4);
+ final long TiB4 = DataUnit.TEBIBYTES.toBytes(4);
+ final long TB8 = DataUnit.TERABYTES.toBytes(8);
+ final long TiB8 = DataUnit.TEBIBYTES.toBytes(8);
- assertEquals(M128, roundStorageSize(M128));
- assertEquals(M256, roundStorageSize(M128 + 1));
- assertEquals(M256, roundStorageSize(M256 - 1));
- assertEquals(M256, roundStorageSize(M256));
- assertEquals(M512, roundStorageSize(M256 + 1));
- assertEquals(M512, roundStorageSize(M512 - 1));
- assertEquals(M512, roundStorageSize(M512));
- assertEquals(G1, roundStorageSize(M512 + 1));
- assertEquals(G1, roundStorageSize(G1));
- assertEquals(G2, roundStorageSize(G1 + 1));
+ assertEquals(GB1, roundStorageSize(GB1 - 1));
+ assertEquals(GB1, roundStorageSize(GB1));
+ assertEquals(GB1, roundStorageSize(GB1 + 1));
+ assertEquals(GB1, roundStorageSize(GiB1 - 1));
+ assertEquals(GB1, roundStorageSize(GiB1));
+ assertEquals(GB2, roundStorageSize(GiB1 + 1));
+ assertEquals(GB2, roundStorageSize(GiB2));
- assertEquals(G16, roundStorageSize(G16));
- assertEquals(G32, roundStorageSize(G16 + 1));
- assertEquals(G32, roundStorageSize(G32 - 1));
- assertEquals(G32, roundStorageSize(G32));
- assertEquals(G64, roundStorageSize(G32 + 1));
+ assertEquals(GB256, roundStorageSize(GiB128 + 1));
+ assertEquals(GB256, roundStorageSize(GiB256));
+ assertEquals(GB512, roundStorageSize(GiB256 + 1));
+ assertEquals(GB512, roundStorageSize(GiB512));
+ assertEquals(TB1, roundStorageSize(GiB512 + 1));
+ assertEquals(TB1, roundStorageSize(TiB1));
+ assertEquals(TB2, roundStorageSize(TiB1 + 1));
+ assertEquals(TB2, roundStorageSize(TiB2));
+ assertEquals(TB4, roundStorageSize(TiB2 + 1));
+ assertEquals(TB4, roundStorageSize(TiB4));
+ assertEquals(TB8, roundStorageSize(TiB4 + 1));
+ assertEquals(TB8, roundStorageSize(TiB8));
+ assertEquals(TB1, roundStorageSize(1013077688320L)); // b/268571529
}
@Test
diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/coretests/src/android/util/DataUnitTest.java
index ec296b7..034cbdd 100644
--- a/core/tests/coretests/src/android/util/DataUnitTest.java
+++ b/core/tests/coretests/src/android/util/DataUnitTest.java
@@ -26,11 +26,13 @@
assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12));
assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12));
assertEquals(12_000_000_000L, DataUnit.GIGABYTES.toBytes(12));
+ assertEquals(12_000_000_000_000L, DataUnit.TERABYTES.toBytes(12));
}
public void testIec() throws Exception {
assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12));
assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12));
assertEquals(12_884_901_888L, DataUnit.GIBIBYTES.toBytes(12));
+ assertEquals(13_194_139_533_312L, DataUnit.TEBIBYTES.toBytes(12));
}
}
diff --git a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
index df96a7d..b3fe5c8 100644
--- a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
+++ b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java
@@ -89,20 +89,22 @@
}
@Test
- public void testIsSameDisplay() {
- assertThat(ContentRecordingSession.isSameDisplay(null, null)).isFalse();
+ public void testIsProjectionOnSameDisplay() {
+ assertThat(ContentRecordingSession.isProjectionOnSameDisplay(null, null)).isFalse();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
WINDOW_TOKEN);
session.setDisplayId(DEFAULT_DISPLAY);
- assertThat(ContentRecordingSession.isSameDisplay(session, null)).isFalse();
+ assertThat(ContentRecordingSession.isProjectionOnSameDisplay(session, null)).isFalse();
ContentRecordingSession incomingSession = ContentRecordingSession.createDisplaySession(
WINDOW_TOKEN);
incomingSession.setDisplayId(DEFAULT_DISPLAY);
- assertThat(ContentRecordingSession.isSameDisplay(session, incomingSession)).isTrue();
+ assertThat(ContentRecordingSession.isProjectionOnSameDisplay(session,
+ incomingSession)).isTrue();
incomingSession.setDisplayId(DEFAULT_DISPLAY + 1);
- assertThat(ContentRecordingSession.isSameDisplay(session, incomingSession)).isFalse();
+ assertThat(ContentRecordingSession.isProjectionOnSameDisplay(session,
+ incomingSession)).isFalse();
}
@Test
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index fccb177..8ae6381 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -269,6 +269,41 @@
}
@Test
+ public void onTouchEvent_startHandwriting_delegate_touchEventsHandled() {
+ // There is no delegator view and the delegate callback does nothing so handwriting will not
+ // be started. This is so we can test how touch events are handled before handwriting is
+ // started.
+ mTestView1.setHandwritingDelegatorCallback(() -> {});
+
+ final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+ final int y = (sHwArea1.top + sHwArea1.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y, 0);
+ boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop / 2;
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y, 0);
+ boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ final int x3 = x2 + mHandwritingSlop * 2;
+ MotionEvent stylusEvent3 = createStylusEvent(ACTION_MOVE, x3, y, 0);
+ boolean onTouchEventResult3 = mHandwritingInitiator.onTouchEvent(stylusEvent3);
+
+ final int x4 = x3 + mHandwritingSlop * 2;
+ MotionEvent stylusEvent4 = createStylusEvent(ACTION_MOVE, x4, y, 0);
+ boolean onTouchEventResult4 = mHandwritingInitiator.onTouchEvent(stylusEvent4);
+
+ assertThat(onTouchEventResult1).isFalse();
+ // stylusEvent2 does not trigger delegation since the touch slop distance has not been
+ // exceeded. onTouchEvent should return false so that the event is dispatched to the view
+ // tree.
+ assertThat(onTouchEventResult2).isFalse();
+ // After delegation is triggered by stylusEvent3, onTouchEvent should return true for
+ // ACTION_MOVE events so that the events are not dispatched to the view tree.
+ assertThat(onTouchEventResult3).isTrue();
+ assertThat(onTouchEventResult4).isTrue();
+ }
+
+ @Test
public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() {
final Rect rect = new Rect(600, 600, 900, 900);
final View testView = createView(rect, true /* autoHandwritingEnabled */,
diff --git a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
new file mode 100644
index 0000000..e6f10ad
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION;
+import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED;
+import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * This test class verifies the additional methods which {@link FakeLatencyTracker} exposes.
+ *
+ * <p>The typical {@link LatencyTracker} behavior test coverage is present in
+ * {@link LatencyTrackerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+public class FakeLatencyTrackerTest {
+
+ private FakeLatencyTracker mFakeLatencyTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ mFakeLatencyTracker = FakeLatencyTracker.create();
+ }
+
+ @Test
+ public void testForceEnabled() throws Exception {
+ mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234);
+
+ assertThat(mFakeLatencyTracker.getEventsWrittenToFrameworkStats(
+ ACTION_SHOW_VOICE_INTERACTION)).isEmpty();
+
+ mFakeLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, 1000);
+ mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234);
+ List<LatencyTracker.FrameworkStatsLogEvent> events =
+ mFakeLatencyTracker.getEventsWrittenToFrameworkStats(
+ ACTION_SHOW_VOICE_INTERACTION);
+ assertThat(events).hasSize(1);
+ assertThat(events.get(0).logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
+ assertThat(events.get(0).statsdAction).isEqualTo(
+ UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION);
+ assertThat(events.get(0).durationMillis).isEqualTo(1234);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
index d1f0b5e..645324d 100644
--- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -16,19 +16,22 @@
package com.android.internal.util;
+import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
import static android.text.TextUtils.formatSimple;
+import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED;
import static com.android.internal.util.LatencyTracker.STATSD_ACTION;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.provider.DeviceConfig;
-import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.LatencyTracker.ActionProperties;
+
import com.google.common.truth.Expect;
import org.junit.Before;
@@ -38,7 +41,6 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
-import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -49,27 +51,23 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LatencyTrackerTest {
- private static final String TAG = LatencyTrackerTest.class.getSimpleName();
private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
- private static final String ACTION_ENABLE_SUFFIX = "_enable";
- private static final Duration TEST_TIMEOUT = Duration.ofMillis(500);
@Rule
public final Expect mExpect = Expect.create();
+ // Fake is used because it tests the real logic of LatencyTracker, and it only fakes the
+ // outcomes (PerfettoTrigger and FrameworkStatsLog).
+ private FakeLatencyTracker mLatencyTracker;
+
@Before
- public void setUp() {
- DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
- LatencyTracker.SETTINGS_ENABLED_KEY);
- getAllActions().forEach(action -> {
- DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
- action.getName().toLowerCase() + ACTION_ENABLE_SUFFIX);
- });
+ public void setUp() throws Exception {
+ mLatencyTracker = FakeLatencyTracker.create();
}
@Test
public void testCujsMapToEnumsCorrectly() {
- List<Field> actions = getAllActions();
+ List<Field> actions = getAllActionFields();
Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
.filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
&& Modifier.isStatic(f.getModifiers())
@@ -101,7 +99,7 @@
@Test
public void testCujTypeEnumCorrectlyDefined() throws Exception {
- List<Field> cujEnumFields = getAllActions();
+ List<Field> cujEnumFields = getAllActionFields();
HashSet<Integer> allValues = new HashSet<>();
for (Field field : cujEnumFields) {
int fieldValue = field.getInt(null);
@@ -118,92 +116,242 @@
}
@Test
- public void testIsEnabled_globalEnabled() {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+ public void testIsEnabled_trueWhenGlobalEnabled() throws Exception {
+ DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
- LatencyTracker latencyTracker = new LatencyTracker();
- waitForLatencyTrackerToUpdateProperties(latencyTracker);
- assertThat(latencyTracker.isEnabled()).isTrue();
+ mLatencyTracker.waitForGlobalEnabledState(true);
+ mLatencyTracker.waitForAllPropertiesEnableState(true);
+
+ //noinspection deprecation
+ assertThat(mLatencyTracker.isEnabled()).isTrue();
}
@Test
- public void testIsEnabled_globalDisabled() {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+ public void testIsEnabled_falseWhenGlobalDisabled() throws Exception {
+ DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
- LatencyTracker latencyTracker = new LatencyTracker();
- waitForLatencyTrackerToUpdateProperties(latencyTracker);
- assertThat(latencyTracker.isEnabled()).isFalse();
+ mLatencyTracker.waitForGlobalEnabledState(false);
+ mLatencyTracker.waitForAllPropertiesEnableState(false);
+
+ //noinspection deprecation
+ assertThat(mLatencyTracker.isEnabled()).isFalse();
}
@Test
- public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet() {
- LatencyTracker latencyTracker = new LatencyTracker();
+ public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet()
+ throws Exception {
// using a single test action, but this applies to all actions
int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
- Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY + ", value=true");
- latencyTracker.mDeviceConfigPropertiesUpdated.close();
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+ DeviceConfig.deleteProperty(NAMESPACE_LATENCY_TRACKER,
+ "action_show_voice_interaction_enable");
+ mLatencyTracker.waitForAllPropertiesEnableState(false);
+ DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
- waitForLatencyTrackerToUpdateProperties(latencyTracker);
- assertThat(
- latencyTracker.isEnabled(action)).isTrue();
+ mLatencyTracker.waitForGlobalEnabledState(true);
+ mLatencyTracker.waitForAllPropertiesEnableState(true);
- Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY
- + ", value=false");
- latencyTracker.mDeviceConfigPropertiesUpdated.close();
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
- LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
- waitForLatencyTrackerToUpdateProperties(latencyTracker);
- assertThat(latencyTracker.isEnabled(action)).isFalse();
+ assertThat(mLatencyTracker.isEnabled(action)).isTrue();
}
@Test
public void testIsEnabledAction_actionPropertyOverridesGlobalProperty()
- throws DeviceConfig.BadConfigException {
- LatencyTracker latencyTracker = new LatencyTracker();
+ throws Exception {
// using a single test action, but this applies to all actions
int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
- String actionEnableProperty = "action_show_voice_interaction" + ACTION_ENABLE_SUFFIX;
- Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true");
+ DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
+ LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
+ mLatencyTracker.waitForGlobalEnabledState(false);
- latencyTracker.mDeviceConfigPropertiesUpdated.close();
- Map<String, String> properties = new HashMap<String, String>() {{
- put(LatencyTracker.SETTINGS_ENABLED_KEY, "false");
- put(actionEnableProperty, "true");
- }};
+ Map<String, String> deviceConfigProperties = new HashMap<>();
+ deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+ deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+ deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
DeviceConfig.setProperties(
- new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
- properties));
- waitForLatencyTrackerToUpdateProperties(latencyTracker);
- assertThat(latencyTracker.isEnabled(action)).isTrue();
+ new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+ deviceConfigProperties));
- latencyTracker.mDeviceConfigPropertiesUpdated.close();
- Log.i(TAG, "setting property=" + actionEnableProperty + ", value=false");
- properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "true");
- properties.put(actionEnableProperty, "false");
- DeviceConfig.setProperties(
- new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
- properties));
- waitForLatencyTrackerToUpdateProperties(latencyTracker);
- assertThat(latencyTracker.isEnabled(action)).isFalse();
+ mLatencyTracker.waitForMatchingActionProperties(
+ new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+ -1 /* traceThreshold */));
+
+ assertThat(mLatencyTracker.isEnabled(action)).isTrue();
}
- private void waitForLatencyTrackerToUpdateProperties(LatencyTracker latencyTracker) {
- try {
- Thread.sleep(TEST_TIMEOUT.toMillis());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- assertThat(latencyTracker.mDeviceConfigPropertiesUpdated.block(
- TEST_TIMEOUT.toMillis())).isTrue();
+ @Test
+ public void testLogsWhenEnabled() throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ Map<String, String> deviceConfigProperties = new HashMap<>();
+ deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+ deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+ deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+ deviceConfigProperties));
+ mLatencyTracker.waitForMatchingActionProperties(
+ new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+ -1 /* traceThreshold */));
+
+ mLatencyTracker.logAction(action, 1234);
+ assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1);
+ LatencyTracker.FrameworkStatsLogEvent frameworkStatsLog =
+ mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0);
+ assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
+ assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]);
+ assertThat(frameworkStatsLog.durationMillis).isEqualTo(1234);
+
+ mLatencyTracker.clearEvents();
+
+ mLatencyTracker.onActionStart(action);
+ mLatencyTracker.onActionEnd(action);
+ // assert that action was logged, but we cannot confirm duration logged
+ assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1);
+ frameworkStatsLog = mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0);
+ assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
+ assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]);
}
- private List<Field> getAllActions() {
- return Arrays.stream(LatencyTracker.class.getDeclaredFields())
- .filter(field -> field.getName().startsWith("ACTION_")
- && Modifier.isStatic(field.getModifiers())
- && field.getType() == int.class)
- .collect(Collectors.toList());
+ @Test
+ public void testDoesNotLogWhenDisabled() throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
+ "false", false);
+ mLatencyTracker.waitForActionEnabledState(action, false);
+ assertThat(mLatencyTracker.isEnabled(action)).isFalse();
+
+ mLatencyTracker.logAction(action, 1234);
+ assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+
+ mLatencyTracker.onActionStart(action);
+ mLatencyTracker.onActionEnd(action);
+ assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+ }
+
+ @Test
+ public void testOnActionEndDoesNotLogWithoutOnActionStart()
+ throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
+ "true", false);
+ mLatencyTracker.waitForActionEnabledState(action, true);
+ assertThat(mLatencyTracker.isEnabled(action)).isTrue();
+
+ mLatencyTracker.onActionEnd(action);
+ assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+ }
+
+ @Test
+ public void testOnActionEndDoesNotLogWhenCanceled()
+ throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
+ "true", false);
+ mLatencyTracker.waitForActionEnabledState(action, true);
+ assertThat(mLatencyTracker.isEnabled(action)).isTrue();
+
+ mLatencyTracker.onActionStart(action);
+ mLatencyTracker.onActionCancel(action);
+ mLatencyTracker.onActionEnd(action);
+ assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
+ }
+
+ @Test
+ public void testNeverTriggersPerfettoWhenThresholdNegative()
+ throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ Map<String, String> deviceConfigProperties = new HashMap<>();
+ deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+ deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+ deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+ deviceConfigProperties));
+ mLatencyTracker.waitForMatchingActionProperties(
+ new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+ -1 /* traceThreshold */));
+
+ mLatencyTracker.onActionStart(action);
+ mLatencyTracker.onActionEnd(action);
+ assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
+ }
+
+ @Test
+ public void testNeverTriggersPerfettoWhenDisabled()
+ throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ Map<String, String> deviceConfigProperties = new HashMap<>();
+ deviceConfigProperties.put("action_show_voice_interaction_enable", "false");
+ deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+ deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1");
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+ deviceConfigProperties));
+ mLatencyTracker.waitForMatchingActionProperties(
+ new ActionProperties(action, false /* enabled */, 1 /* samplingInterval */,
+ 1 /* traceThreshold */));
+
+ mLatencyTracker.onActionStart(action);
+ mLatencyTracker.onActionEnd(action);
+ assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
+ }
+
+ @Test
+ public void testTriggersPerfettoWhenAboveThreshold()
+ throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ Map<String, String> deviceConfigProperties = new HashMap<>();
+ deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+ deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+ deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1");
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+ deviceConfigProperties));
+ mLatencyTracker.waitForMatchingActionProperties(
+ new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+ 1 /* traceThreshold */));
+
+ mLatencyTracker.onActionStart(action);
+ // We need to sleep here to ensure that the end call is past the set trace threshold (1ms)
+ Thread.sleep(5 /* millis */);
+ mLatencyTracker.onActionEnd(action);
+ assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).hasSize(1);
+ assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames().get(0)).isEqualTo(
+ "com.android.telemetry.latency-tracker-ACTION_SHOW_VOICE_INTERACTION");
+ }
+
+ @Test
+ public void testNeverTriggersPerfettoWhenBelowThreshold()
+ throws Exception {
+ // using a single test action, but this applies to all actions
+ int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+ Map<String, String> deviceConfigProperties = new HashMap<>();
+ deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
+ deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
+ deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1000");
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
+ deviceConfigProperties));
+ mLatencyTracker.waitForMatchingActionProperties(
+ new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+ 1000 /* traceThreshold */));
+
+ mLatencyTracker.onActionStart(action);
+ // No sleep here to ensure that end call comes before 1000ms threshold
+ mLatencyTracker.onActionEnd(action);
+ assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
+ }
+
+ private List<Field> getAllActionFields() {
+ return Arrays.stream(LatencyTracker.class.getDeclaredFields()).filter(
+ field -> field.getName().startsWith("ACTION_") && Modifier.isStatic(
+ field.getModifiers()) && field.getType() == int.class).collect(
+ Collectors.toList());
}
private int getIntFieldChecked(Field field) {
diff --git a/core/tests/coretests/src/com/android/internal/util/OWNERS b/core/tests/coretests/src/com/android/internal/util/OWNERS
index d832745..dda11fb 100644
--- a/core/tests/coretests/src/com/android/internal/util/OWNERS
+++ b/core/tests/coretests/src/com/android/internal/util/OWNERS
@@ -1,2 +1,3 @@
per-file *Notification* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
\ No newline at end of file
+per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
diff --git a/core/tests/coretests/testdoubles/Android.bp b/core/tests/coretests/testdoubles/Android.bp
new file mode 100644
index 0000000..35f6911
--- /dev/null
+++ b/core/tests/coretests/testdoubles/Android.bp
@@ -0,0 +1,19 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ // SPDX-license-identifier-BSD
+ // legacy_unencumbered
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "FrameworksCoreTestDoubles-sources",
+ srcs: ["src/**/*.java"],
+ visibility: [
+ "//frameworks/base/core/tests/coretests",
+ "//frameworks/base/services/tests/voiceinteractiontests",
+ ],
+}
diff --git a/core/tests/coretests/testdoubles/OWNERS b/core/tests/coretests/testdoubles/OWNERS
new file mode 100644
index 0000000..baf92ec
--- /dev/null
+++ b/core/tests/coretests/testdoubles/OWNERS
@@ -0,0 +1 @@
+per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
new file mode 100644
index 0000000..306ecde
--- /dev/null
+++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static com.android.internal.util.LatencyTracker.ActionProperties.ENABLE_SUFFIX;
+import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
+import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.ConditionVariable;
+import android.provider.DeviceConfig;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+
+public final class FakeLatencyTracker extends LatencyTracker {
+
+ private static final String TAG = "FakeLatencyTracker";
+ private static final Duration FORCE_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<Integer, List<FrameworkStatsLogEvent>> mLatenciesLogged;
+ @GuardedBy("mLock")
+ private final List<String> mPerfettoTraceNamesTriggered;
+ private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate =
+ new AtomicReference<>();
+ @Nullable
+ @GuardedBy("mLock")
+ private Callable<Boolean> mShouldClosePropertiesUpdatedCallable = null;
+ private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
+
+ public static FakeLatencyTracker create() throws Exception {
+ Log.i(TAG, "create");
+ disableForAllActions();
+ FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker();
+ // always return the fake in the disabled state and let the client control the desired state
+ fakeLatencyTracker.waitForGlobalEnabledState(false);
+ fakeLatencyTracker.waitForAllPropertiesEnableState(false);
+ return fakeLatencyTracker;
+ }
+
+ FakeLatencyTracker() {
+ super();
+ mLatenciesLogged = new HashMap<>();
+ mPerfettoTraceNamesTriggered = new ArrayList<>();
+ }
+
+ private static void disableForAllActions() throws DeviceConfig.BadConfigException {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "false");
+ for (int action : STATSD_ACTION) {
+ Log.d(TAG, "disabling action=" + action + ", property=" + getNameOfAction(
+ action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX);
+ properties.put(getNameOfAction(action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX,
+ "false");
+ }
+
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties));
+ }
+
+ public void forceEnabled(int action, int traceThresholdMillis)
+ throws Exception {
+ String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT);
+ String actionEnableProperty = actionName + ENABLE_SUFFIX;
+ String actionSampleProperty = actionName + SAMPLE_INTERVAL_SUFFIX;
+ String actionTraceProperty = actionName + TRACE_THRESHOLD_SUFFIX;
+ Log.i(TAG, "setting property=" + actionTraceProperty + ", value=" + traceThresholdMillis);
+ Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true");
+
+ Map<String, String> properties = new HashMap<>(ImmutableMap.of(
+ actionEnableProperty, "true",
+ // Fake forces to sample every event
+ actionSampleProperty, String.valueOf(1),
+ actionTraceProperty, String.valueOf(traceThresholdMillis)
+ ));
+ DeviceConfig.setProperties(
+ new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties));
+ waitForMatchingActionProperties(
+ new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
+ traceThresholdMillis));
+ }
+
+ public List<FrameworkStatsLogEvent> getEventsWrittenToFrameworkStats(@Action int action) {
+ synchronized (mLock) {
+ Log.i(TAG, "getEventsWrittenToFrameworkStats: mLatenciesLogged=" + mLatenciesLogged);
+ return mLatenciesLogged.getOrDefault(action, Collections.emptyList());
+ }
+ }
+
+ public List<String> getTriggeredPerfettoTraceNames() {
+ synchronized (mLock) {
+ return mPerfettoTraceNamesTriggered;
+ }
+ }
+
+ public void clearEvents() {
+ synchronized (mLock) {
+ mLatenciesLogged.clear();
+ mPerfettoTraceNamesTriggered.clear();
+ }
+ }
+
+ @Override
+ public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) {
+ Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties);
+ mLastPropertiesUpdate.set(actionProperties);
+ synchronized (mLock) {
+ if (mShouldClosePropertiesUpdatedCallable != null) {
+ try {
+ boolean shouldClosePropertiesUpdated =
+ mShouldClosePropertiesUpdatedCallable.call();
+ Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result="
+ + shouldClosePropertiesUpdated);
+ if (shouldClosePropertiesUpdated) {
+ Log.i(TAG, "shouldClosePropertiesUpdatedCallable=true, opening condition");
+ mShouldClosePropertiesUpdatedCallable = null;
+ mDeviceConfigPropertiesUpdated.open();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "exception when calling callable", e);
+ throw new RuntimeException(e);
+ }
+ } else {
+ Log.i(TAG, "no conditional callable set, opening condition");
+ mDeviceConfigPropertiesUpdated.open();
+ }
+ }
+ }
+
+ @Override
+ public void onTriggerPerfetto(String triggerName) {
+ synchronized (mLock) {
+ mPerfettoTraceNamesTriggered.add(triggerName);
+ }
+ }
+
+ @Override
+ public void onLogToFrameworkStats(FrameworkStatsLogEvent event) {
+ synchronized (mLock) {
+ Log.i(TAG, "onLogToFrameworkStats: event=" + event);
+ List<FrameworkStatsLogEvent> eventList = mLatenciesLogged.getOrDefault(event.action,
+ new ArrayList<>());
+ eventList.add(event);
+ mLatenciesLogged.put(event.action, eventList);
+ }
+ }
+
+ public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception {
+ Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState);
+ synchronized (mLock) {
+ Log.i(TAG, "closing condition");
+ mDeviceConfigPropertiesUpdated.close();
+ // Update the callable to only close the properties updated condition when all the
+ // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+ // times so testing the resulting updates is required.
+ mShouldClosePropertiesUpdatedCallable = () -> {
+ Log.i(TAG, "verifying if last properties update has all properties enable="
+ + enabledState);
+ SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+ if (newProperties != null) {
+ for (int i = 0; i < newProperties.size(); i++) {
+ if (newProperties.get(i).isEnabled() != enabledState) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+ if (mShouldClosePropertiesUpdatedCallable.call()) {
+ return;
+ }
+ }
+ Log.i(TAG, "waiting for condition");
+ assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ }
+
+ public void waitForMatchingActionProperties(ActionProperties actionProperties)
+ throws Exception {
+ Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties);
+ synchronized (mLock) {
+ Log.i(TAG, "closing condition");
+ mDeviceConfigPropertiesUpdated.close();
+ // Update the callable to only close the properties updated condition when all the
+ // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+ // times so testing the resulting updates is required.
+ mShouldClosePropertiesUpdatedCallable = () -> {
+ Log.i(TAG, "verifying if last properties update contains matching property ="
+ + actionProperties);
+ SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+ if (newProperties != null) {
+ if (newProperties.size() > 0) {
+ return newProperties.get(actionProperties.getAction()).equals(
+ actionProperties);
+ }
+ }
+ return false;
+ };
+ if (mShouldClosePropertiesUpdatedCallable.call()) {
+ return;
+ }
+ }
+ Log.i(TAG, "waiting for condition");
+ assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ }
+
+ public void waitForActionEnabledState(int action, boolean enabledState) throws Exception {
+ Log.i(TAG, "waitForActionEnabledState:"
+ + " action=" + action + ", enabledState=" + enabledState);
+ synchronized (mLock) {
+ Log.i(TAG, "closing condition");
+ mDeviceConfigPropertiesUpdated.close();
+ // Update the callable to only close the properties updated condition when all the
+ // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+ // times so testing the resulting updates is required.
+ mShouldClosePropertiesUpdatedCallable = () -> {
+ Log.i(TAG, "verifying if last properties update contains action=" + action
+ + ", enabledState=" + enabledState);
+ SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+ if (newProperties != null) {
+ if (newProperties.size() > 0) {
+ return newProperties.get(action).isEnabled() == enabledState;
+ }
+ }
+ return false;
+ };
+ if (mShouldClosePropertiesUpdatedCallable.call()) {
+ return;
+ }
+ }
+ Log.i(TAG, "waiting for condition");
+ assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ }
+
+ public void waitForGlobalEnabledState(boolean enabledState) throws Exception {
+ Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState);
+ synchronized (mLock) {
+ Log.i(TAG, "closing condition");
+ mDeviceConfigPropertiesUpdated.close();
+ // Update the callable to only close the properties updated condition when all the
+ // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+ // times so testing the resulting updates is required.
+ mShouldClosePropertiesUpdatedCallable = () -> {
+ //noinspection deprecation
+ return isEnabled() == enabledState;
+ };
+ if (mShouldClosePropertiesUpdatedCallable.call()) {
+ return;
+ }
+ }
+ Log.i(TAG, "waiting for condition");
+ assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue();
+ }
+}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
new file mode 100644
index 0000000..ee62d75
--- /dev/null
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.expresslog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class ScaledRangeOptionsTest {
+ private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
+
+ @Test
+ public void testGetBinsCount() {
+ Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
+ assertEquals(3, options1.getBinsCount());
+
+ Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
+ assertEquals(12, options10.getBinsCount());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructZeroBinsCount() {
+ new Histogram.ScaledRangeOptions(0, 100, 100, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNegativeBinsCount() {
+ new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNegativeFirstBinWidth() {
+ new Histogram.ScaledRangeOptions(10, 100, -100, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooSmallFirstBinWidth() {
+ new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNegativeScaleFactor() {
+ new Histogram.ScaledRangeOptions(10, 100, 100, -2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooSmallScaleFactor() {
+ new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooBigScaleFactor() {
+ new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooBigBinRange() {
+ new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual1() {
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
+ assertEquals(12, options.getBinsCount());
+
+ assertEquals(11, options.getBinForSample(11));
+
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i));
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual2() {
+ // this should produce bin otpions similar to linear histogram with bin width 2
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
+ assertEquals(12, options.getBinsCount());
+
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * 2));
+ assertEquals(i, options.getBinForSample(i * 2 - 1));
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual5() {
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
+ assertEquals(4, options.getBinsCount());
+ for (int i = 0; i < 2; i++) {
+ for (int sample = 0; sample < 5; sample++) {
+ assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
+ }
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual10() {
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
+ assertEquals(0, options.getBinForSample(0));
+ assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
+ assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
+
+ final float binSize = (101 - 1) / 10f;
+ for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * binSize));
+ }
+ }
+
+ @Test
+ public void testBinIndexForScaleFactor2() {
+ final int binsCount = 10;
+ final int minValue = 10;
+ final int firstBinWidth = 5;
+ final int scaledFactor = 2;
+
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
+ binsCount, minValue, firstBinWidth, scaledFactor);
+ assertEquals(binsCount + 2, options.getBinsCount());
+ long[] binCounts = new long[10];
+
+ // precalculate max valid value - start value for the overflow bin
+ int lastBinStartValue = minValue; //firstBinMin value
+ int lastBinWidth = firstBinWidth;
+ for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
+ lastBinStartValue = lastBinStartValue + lastBinWidth;
+ lastBinWidth *= scaledFactor;
+ }
+
+ // underflow bin
+ for (int i = 1; i < minValue; i++) {
+ assertEquals(0, options.getBinForSample(i));
+ }
+
+ for (int i = 10; i < lastBinStartValue; i++) {
+ assertTrue(options.getBinForSample(i) > 0);
+ assertTrue(options.getBinForSample(i) <= binsCount);
+ binCounts[options.getBinForSample(i) - 1]++;
+ }
+
+ // overflow bin
+ assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
+
+ for (int i = 1; i < binsCount; i++) {
+ assertEquals(binCounts[i], binCounts[i - 1] * 2L);
+ }
+ }
+}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
index 9fa6d06..037dbb3 100644
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
@@ -24,11 +24,11 @@
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
+@SmallTest
public class UniformOptionsTest {
private static final String TAG = UniformOptionsTest.class.getSimpleName();
@Test
- @SmallTest
public void testGetBinsCount() {
Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
assertEquals(3, options1.getBinsCount());
@@ -38,25 +38,21 @@
}
@Test(expected = IllegalArgumentException.class)
- @SmallTest
public void testConstructZeroBinsCount() {
new Histogram.UniformOptions(0, 100, 1000);
}
@Test(expected = IllegalArgumentException.class)
- @SmallTest
public void testConstructNegativeBinsCount() {
new Histogram.UniformOptions(-1, 100, 1000);
}
@Test(expected = IllegalArgumentException.class)
- @SmallTest
public void testConstructMaxValueLessThanMinValue() {
new Histogram.UniformOptions(10, 1000, 100);
}
@Test
- @SmallTest
public void testBinIndexForRangeEqual1() {
Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -65,7 +61,6 @@
}
@Test
- @SmallTest
public void testBinIndexForRangeEqual2() {
Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
@@ -75,7 +70,6 @@
}
@Test
- @SmallTest
public void testBinIndexForRangeEqual5() {
Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
assertEquals(4, options.getBinsCount());
@@ -87,7 +81,6 @@
}
@Test
- @SmallTest
public void testBinIndexForRangeEqual10() {
Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
assertEquals(0, options.getBinForSample(0));
@@ -101,7 +94,6 @@
}
@Test
- @SmallTest
public void testBinIndexForRangeEqual90() {
final int binCount = 10;
final int minValue = 100;
diff --git a/data/etc/TEST_MAPPING b/data/etc/TEST_MAPPING
index 1a5db2f..5927720 100644
--- a/data/etc/TEST_MAPPING
+++ b/data/etc/TEST_MAPPING
@@ -2,10 +2,10 @@
"presubmit": [
{
"file_patterns": ["(/|^)platform.xml"],
- "name": "CtsPermission2TestCases",
+ "name": "CtsPermissionPolicyTestCases",
"options": [
{
- "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+ "include-filter": "android.permissionpolicy.cts.RuntimePermissionProperties"
}
]
}
diff --git a/data/etc/com.android.intentresolver.xml b/data/etc/com.android.intentresolver.xml
index f4e94ad..af64926 100644
--- a/data/etc/com.android.intentresolver.xml
+++ b/data/etc/com.android.intentresolver.xml
@@ -19,5 +19,6 @@
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
+ <permission name="android.permission.QUERY_CLONED_APPS"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 5549f88..a73010b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -7,24 +7,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-2123789565": {
- "message": "Found no matching mirror display for id=%d for DEFAULT_DISPLAY. Nothing to mirror.",
- "level": "WARN",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/DisplayContent.java"
- },
"-2121056984": {
"message": "%s",
"level": "WARN",
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
- "-2113780196": {
- "message": "Successfully created a ContentRecordingSession for displayId=%d to mirror content from displayId=%d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/DisplayContent.java"
- },
"-2111539867": {
"message": "remove IME snapshot, caller=%s",
"level": "INFO",
@@ -67,12 +55,24 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-2074882083": {
+ "message": "Content Recording: Unable to retrieve task to start recording for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-2072089308": {
"message": "Attempted to add window with token that is a sub-window: %s. Aborting.",
"level": "WARN",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-2072029833": {
+ "message": "Content Recording: Found no matching mirror display for id=%d for DEFAULT_DISPLAY. Nothing to mirror.",
+ "level": "WARN",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-2054442123": {
"message": "Setting Intent of %s to %s",
"level": "VERBOSE",
@@ -175,12 +175,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1944652783": {
- "message": "Unable to tell MediaProjectionManagerService to stop the active projection: %s",
- "level": "ERROR",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1941440781": {
"message": "Creating Pending Move-to-back: %s",
"level": "VERBOSE",
@@ -253,12 +247,24 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "-1885450608": {
+ "message": "Content Recording: Successfully created a ContentRecordingSession for displayId=%d to mirror content from displayId=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-1884933373": {
"message": "enableScreenAfterBoot: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
"level": "INFO",
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1883484959": {
+ "message": "Content Recording: Display %d state is now (%d), so update recording?",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-1872288685": {
"message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
@@ -355,12 +361,6 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-1781861035": {
- "message": "Display %d has content (%b) so pause recording",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1777196134": {
"message": "goodToGo(): No apps to animate, mPendingAnimations=%d",
"level": "DEBUG",
@@ -505,12 +505,6 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
- "-1605829532": {
- "message": "Unable to start recording due to null token for display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1598452494": {
"message": "activityDestroyedLocked: r=%s",
"level": "DEBUG",
@@ -571,6 +565,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
+ "-1549923951": {
+ "message": "Content Recording: Unable to retrieve window container to start recording for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1545962566": {
"message": "View server did not start",
"level": "WARN",
@@ -649,6 +649,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1480264178": {
+ "message": "Content Recording: Unable to update recording for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1478175541": {
"message": "No longer animating wallpaper targets!",
"level": "VERBOSE",
@@ -721,12 +727,6 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1423223548": {
- "message": "Unable to tell MediaProjectionManagerService about resizing the active projection: %s",
- "level": "ERROR",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1421296808": {
"message": "Moving to RESUMED: %s (in existing)",
"level": "VERBOSE",
@@ -787,12 +787,6 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "-1373875178": {
- "message": "Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d.",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1364754753": {
"message": "Task vanished taskId=%d",
"level": "VERBOSE",
@@ -817,12 +811,6 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-1326876381": {
- "message": "Provided surface for recording on display %d is not present, so do not update the surface",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1323783276": {
"message": "performEnableScreen: bootFinished() failed.",
"level": "WARN",
@@ -943,6 +931,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "-1217596375": {
+ "message": "Content Recording: Display %d has no content and is on, so start recording for state %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1209252064": {
"message": "Clear animatingExit: reason=clearAnimatingFlags win=%s",
"level": "DEBUG",
@@ -991,6 +985,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/Task.java"
},
+ "-1156314529": {
+ "message": "Content Recording: Unexpectedly null window container; unable to update recording for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1156118957": {
"message": "Updated config=%s",
"level": "DEBUG",
@@ -1015,6 +1015,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1136734598": {
+ "message": "Content Recording: Ignoring session on same display %d, with an existing session %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecordingController.java"
+ },
"-1136467585": {
"message": "The listener does not exist.",
"level": "INFO",
@@ -1087,6 +1093,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "-1097851684": {
+ "message": "Content Recording: Unable to start recording due to null token for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1089874824": {
"message": "SURFACE SHOW (performLayout): %s",
"level": "INFO",
@@ -1147,12 +1159,6 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "-1018968224": {
- "message": "Recorded task is removed, so stop recording on display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1016578046": {
"message": "Moving to %s Relaunching %s callers=%s",
"level": "INFO",
@@ -1297,6 +1303,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-869242375": {
+ "message": "Content Recording: Unable to start recording due to invalid region for display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-863438038": {
"message": "Aborting Transition: %d",
"level": "VERBOSE",
@@ -1351,12 +1363,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-838378223": {
- "message": "Attempting to mirror self on %d",
- "level": "WARN",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/DisplayContent.java"
- },
"-814760297": {
"message": "Looking for task of %s in %s",
"level": "DEBUG",
@@ -1429,6 +1435,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-767091913": {
+ "message": "Content Recording: Handle incoming session on display %d, with a pre-existing session %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecordingController.java"
+ },
"-766059044": {
"message": "Display id=%d selected orientation %s (%d), got rotation %s (%d)",
"level": "VERBOSE",
@@ -1453,12 +1465,6 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-751255162": {
- "message": "Unable to update recording for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-743856570": {
"message": "shouldWaitAnimatingExit: isAnimating: %s",
"level": "DEBUG",
@@ -1471,18 +1477,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-732715767": {
- "message": "Unable to retrieve window container to start recording for display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
- "-729864558": {
- "message": "Attempting to mirror %d from %d but no DisplayContent associated. Changing to mirror default display.",
- "level": "WARN",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/DisplayContent.java"
- },
"-729530161": {
"message": "Moving to DESTROYED: %s (no app)",
"level": "VERBOSE",
@@ -1717,6 +1711,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "-517666355": {
+ "message": "Content Recording: Display %d has content (%b) so pause recording",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-509601642": {
"message": " checking %s",
"level": "VERBOSE",
@@ -1771,6 +1771,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/Task.java"
},
+ "-452750194": {
+ "message": "Content Recording: Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-451552570": {
"message": "Current focused window being animated by recents. Overriding back callback to recents controller callback.",
"level": "DEBUG",
@@ -1855,12 +1861,6 @@
"group": "WM_DEBUG_KEEP_SCREEN_ON",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
- "-381522987": {
- "message": "Display %d state is now (%d), so update recording?",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/DisplayContent.java"
- },
"-381475323": {
"message": "DisplayContent: boot is waiting for window of type %d to be drawn",
"level": "DEBUG",
@@ -1969,12 +1969,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
- "-302468137": {
- "message": "Display %d was already recording, so apply transformations if necessary",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-292790591": {
"message": "Attempted to set IME policy to a display that does not exist: %d",
"level": "WARN",
@@ -1993,12 +1987,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-254406860": {
- "message": "Unable to tell MediaProjectionManagerService about visibility change on the active projection: %s",
- "level": "ERROR",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-251259736": {
"message": "No longer freezing: %s",
"level": "VERBOSE",
@@ -2017,12 +2005,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "-237664290": {
- "message": "Pause the recording session on display %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecordingController.java"
- },
"-235225312": {
"message": "Skipping config check for initializing activity: %s",
"level": "VERBOSE",
@@ -2077,6 +2059,12 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-180594244": {
+ "message": "Content Recording: Unable to tell MediaProjectionManagerService about visibility change on the active projection: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-177040661": {
"message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s",
"level": "DEBUG",
@@ -2113,12 +2101,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/Task.java"
},
- "-142844021": {
- "message": "Unable to start recording for display %d since the surface is not available.",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-134091882": {
"message": "Screenshotting Activity %s",
"level": "VERBOSE",
@@ -2161,6 +2143,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-88873335": {
+ "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-87705714": {
"message": "findFocusedWindow: focusedApp=null using new focus @ %s",
"level": "VERBOSE",
@@ -2347,12 +2335,6 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "96494268": {
- "message": "Stop MediaProjection on virtual display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"100936473": {
"message": "Wallpaper animation!",
"level": "VERBOSE",
@@ -2551,12 +2533,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/TransitionController.java"
},
- "264036181": {
- "message": "Unable to retrieve task to start recording for display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"269576220": {
"message": "Resuming rotation after drag",
"level": "DEBUG",
@@ -2665,6 +2641,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "339482207": {
+ "message": "Content Recording: Display %d was already recording, so apply transformations if necessary",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"341055768": {
"message": "resumeTopActivity: Skip resume: need to start pausing",
"level": "VERBOSE",
@@ -2959,8 +2941,8 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/Session.java"
},
- "609880497": {
- "message": "Display %d has no content and is on, so start recording for state %d",
+ "612856628": {
+ "message": "Content Recording: Stop MediaProjection on virtual display %d",
"level": "VERBOSE",
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecorder.java"
@@ -3139,12 +3121,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
- "778774915": {
- "message": "Unable to record task since feature is disabled %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"781471998": {
"message": "moveWindowTokenToDisplay: Cannot move to the original display for token: %s",
"level": "WARN",
@@ -3181,6 +3157,12 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
+ "801521566": {
+ "message": "Content Recording: Attempting to mirror %d from %d but no DisplayContent associated. Changing to mirror default display.",
+ "level": "WARN",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"806891543": {
"message": "Setting mOrientationChangeComplete=true because wtoken %s numInteresting=%d numDrawn=%d",
"level": "INFO",
@@ -3271,6 +3253,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "937080808": {
+ "message": "Content Recording: Recorded task is removed, so stop recording on display %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"939638078": {
"message": "config_deviceTabletopRotations is not defined. Half-fold letterboxing will work inconsistently.",
"level": "WARN",
@@ -3517,6 +3505,12 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/DisplayPolicy.java"
},
+ "1145016093": {
+ "message": "Content Recording: Attempting to mirror self on %d",
+ "level": "WARN",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"1149424314": {
"message": "Unregister display organizer=%s uid=%d",
"level": "VERBOSE",
@@ -3745,12 +3739,6 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
- "1401287081": {
- "message": "Handle incoming session on display %d, with a pre-existing session %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecordingController.java"
- },
"1401295262": {
"message": "Mode default, asking user",
"level": "WARN",
@@ -3787,12 +3775,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "1444064727": {
- "message": "Unexpectedly null window container; unable to update recording for display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"1448683958": {
"message": "Override pending remote transitionSet=%b adapter=%s",
"level": "INFO",
@@ -3877,6 +3859,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1546187372": {
+ "message": "Content Recording: Pause the recording session on display %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecordingController.java"
+ },
"1557732761": {
"message": "For Intent %s bringing to top: %s",
"level": "DEBUG",
@@ -3889,6 +3877,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1563836923": {
+ "message": "Content Recording: Unable to record task since feature is disabled %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"1577579529": {
"message": "win=%s destroySurfaces: appStopped=%b win.mWindowRemovalAllowed=%b win.mRemoveOnExit=%b",
"level": "ERROR",
@@ -3907,12 +3901,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
- "1608402305": {
- "message": "Unable to start recording due to invalid region for display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"1610646518": {
"message": "Enqueueing pending finish: %s",
"level": "VERBOSE",
@@ -3973,6 +3961,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/InsetsStateController.java"
},
+ "1661414284": {
+ "message": "Content Recording: Unable to tell MediaProjectionManagerService about resizing the active projection: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"1667162379": {
"message": "Creating Pending Transition: %s",
"level": "VERBOSE",
@@ -4021,6 +4015,12 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
},
+ "1712935427": {
+ "message": "Content Recording: Unable to start recording for display %d since the surface is not available.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"1720229827": {
"message": "Creating animation bounds layer",
"level": "INFO",
@@ -4051,6 +4051,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1750878635": {
+ "message": "Content Recording: Provided surface for recording on display %d is not present, so do not update the surface",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"1756082882": {
"message": "Orientation change skips hidden %s",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 24fea01..99bebb8 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -210,12 +210,16 @@
private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+
+ // HLG transfer with an SDR whitepoint of 203 nits
private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
- new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
- 0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true);
+ new Rgb.TransferParameters(2.0, 2.0, 1 / 0.17883277, 0.28466892, 0.55991073,
+ -0.685490157, Rgb.TransferParameters.TYPE_HLGish);
+
+ // PQ transfer with an SDR whitepoint of 203 nits
private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
- new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f,
- 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
+ new Rgb.TransferParameters(-1.555223, 1.860454, 32 / 2523.0, 2413 / 128.0,
+ -2392 / 128.0, 8192 / 1305.0, Rgb.TransferParameters.TYPE_PQish);
// See static initialization block next to #get(Named)
private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -1651,8 +1655,8 @@
BT2020_PRIMARIES,
ILLUMINANT_D65,
null,
- x -> transferHLGOETF(x),
- x -> transferHLGEOTF(x),
+ x -> transferHLGOETF(BT2020_HLG_TRANSFER_PARAMETERS, x),
+ x -> transferHLGEOTF(BT2020_HLG_TRANSFER_PARAMETERS, x),
0.0f, 1.0f,
BT2020_HLG_TRANSFER_PARAMETERS,
Named.BT2020_HLG.ordinal()
@@ -1663,8 +1667,8 @@
BT2020_PRIMARIES,
ILLUMINANT_D65,
null,
- x -> transferST2048OETF(x),
- x -> transferST2048EOTF(x),
+ x -> transferST2048OETF(BT2020_PQ_TRANSFER_PARAMETERS, x),
+ x -> transferST2048EOTF(BT2020_PQ_TRANSFER_PARAMETERS, x),
0.0f, 1.0f,
BT2020_PQ_TRANSFER_PARAMETERS,
Named.BT2020_PQ.ordinal()
@@ -1672,44 +1676,58 @@
sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_PQ, Named.BT2020_PQ.ordinal());
}
- private static double transferHLGOETF(double x) {
- double a = 0.17883277;
- double b = 0.28466892;
- double c = 0.55991073;
- double r = 0.5;
- return x > 1.0 ? a * Math.log(x - b) + c : r * Math.sqrt(x);
+ private static double transferHLGOETF(Rgb.TransferParameters params, double x) {
+ double sign = x < 0 ? -1.0 : 1.0;
+ x *= sign;
+
+ // Unpack the transfer params matching skia's packing & invert R, G, and a
+ final double R = 1.0 / params.a;
+ final double G = 1.0 / params.b;
+ final double a = 1.0 / params.c;
+ final double b = params.d;
+ final double c = params.e;
+ final double K = params.f + 1.0;
+
+ x /= K;
+ return sign * (x <= 1 ? R * Math.pow(x, G) : a * Math.log(x - b) + c);
}
- private static double transferHLGEOTF(double x) {
- double a = 0.17883277;
- double b = 0.28466892;
- double c = 0.55991073;
- double r = 0.5;
- return x <= 0.5 ? (x * x) / (r * r) : Math.exp((x - c) / a + b);
+ private static double transferHLGEOTF(Rgb.TransferParameters params, double x) {
+ double sign = x < 0 ? -1.0 : 1.0;
+ x *= sign;
+
+ // Unpack the transfer params matching skia's packing
+ final double R = params.a;
+ final double G = params.b;
+ final double a = params.c;
+ final double b = params.d;
+ final double c = params.e;
+ final double K = params.f + 1.0;
+
+ return K * sign * (x * R <= 1 ? Math.pow(x * R, G) : Math.exp((x - c) * a) + b);
}
- private static double transferST2048OETF(double x) {
- double m1 = (2610.0 / 4096.0) / 4.0;
- double m2 = (2523.0 / 4096.0) * 128.0;
- double c1 = (3424.0 / 4096.0);
- double c2 = (2413.0 / 4096.0) * 32.0;
- double c3 = (2392.0 / 4096.0) * 32.0;
+ private static double transferST2048OETF(Rgb.TransferParameters params, double x) {
+ double sign = x < 0 ? -1.0 : 1.0;
+ x *= sign;
- double tmp = Math.pow(x, m1);
- tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
- return Math.pow(tmp, m2);
+ double a = -params.a;
+ double b = params.d;
+ double c = 1.0 / params.f;
+ double d = params.b;
+ double e = -params.e;
+ double f = 1.0 / params.c;
+
+ double tmp = Math.max(a + b * Math.pow(x, c), 0);
+ return sign * Math.pow(tmp / (d + e * Math.pow(x, c)), f);
}
- private static double transferST2048EOTF(double x) {
- double m1 = (2610.0 / 4096.0) / 4.0;
- double m2 = (2523.0 / 4096.0) * 128.0;
- double c1 = (3424.0 / 4096.0);
- double c2 = (2413.0 / 4096.0) * 32.0;
- double c3 = (2392.0 / 4096.0) * 32.0;
+ private static double transferST2048EOTF(Rgb.TransferParameters pq, double x) {
+ double sign = x < 0 ? -1.0 : 1.0;
+ x *= sign;
- double tmp = Math.pow(Math.min(Math.max(x, 0.0), 1.0), 1.0 / m2);
- tmp = Math.max(tmp - c1, 0.0) / (c2 - c3 * tmp);
- return Math.pow(tmp, 1.0 / m1);
+ double tmp = Math.max(pq.a + pq.b * Math.pow(x, pq.c), 0);
+ return sign * Math.pow(tmp / (pq.d + pq.e * Math.pow(x, pq.c)), pq.f);
}
// Reciprocal piecewise gamma response
@@ -2276,6 +2294,10 @@
* </ul>
*/
public static class TransferParameters {
+
+ private static final double TYPE_PQish = -2.0;
+ private static final double TYPE_HLGish = -3.0;
+
/** Variable \(a\) in the equation of the EOTF described above. */
public final double a;
/** Variable \(b\) in the equation of the EOTF described above. */
@@ -2291,56 +2313,8 @@
/** Variable \(g\) in the equation of the EOTF described above. */
public final double g;
- private TransferParameters(double a, double b, double c, double d, double e,
- double f, double g, boolean nonCurveTransferParameters) {
- // nonCurveTransferParameters correspondes to a "special" transfer function
- if (!nonCurveTransferParameters) {
- if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)
- || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f)
- || Double.isNaN(g)) {
- throw new IllegalArgumentException("Parameters cannot be NaN");
- }
-
- // Next representable float after 1.0
- // We use doubles here but the representation inside our native code
- // is often floats
- if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
- throw new IllegalArgumentException(
- "Parameter d must be in the range [0..1], " + "was " + d);
- }
-
- if (d == 0.0 && (a == 0.0 || g == 0.0)) {
- throw new IllegalArgumentException(
- "Parameter a or g is zero, the transfer function is constant");
- }
-
- if (d >= 1.0 && c == 0.0) {
- throw new IllegalArgumentException(
- "Parameter c is zero, the transfer function is constant");
- }
-
- if ((a == 0.0 || g == 0.0) && c == 0.0) {
- throw new IllegalArgumentException("Parameter a or g is zero,"
- + " and c is zero, the transfer function is constant");
- }
-
- if (c < 0.0) {
- throw new IllegalArgumentException(
- "The transfer function must be increasing");
- }
-
- if (a < 0.0 || g < 0.0) {
- throw new IllegalArgumentException(
- "The transfer function must be positive or increasing");
- }
- }
- this.a = a;
- this.b = b;
- this.c = c;
- this.d = d;
- this.e = e;
- this.f = f;
- this.g = g;
+ private static boolean isSpecialG(double g) {
+ return g == TYPE_PQish || g == TYPE_HLGish;
}
/**
@@ -2365,7 +2339,7 @@
* @throws IllegalArgumentException If the parameters form an invalid transfer function
*/
public TransferParameters(double a, double b, double c, double d, double g) {
- this(a, b, c, d, 0.0, 0.0, g, false);
+ this(a, b, c, d, 0.0, 0.0, g);
}
/**
@@ -2384,7 +2358,52 @@
*/
public TransferParameters(double a, double b, double c, double d, double e,
double f, double g) {
- this(a, b, c, d, e, f, g, false);
+ if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)
+ || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f)
+ || Double.isNaN(g)) {
+ throw new IllegalArgumentException("Parameters cannot be NaN");
+ }
+ if (!isSpecialG(g)) {
+ // Next representable float after 1.0
+ // We use doubles here but the representation inside our native code
+ // is often floats
+ if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
+ throw new IllegalArgumentException(
+ "Parameter d must be in the range [0..1], " + "was " + d);
+ }
+
+ if (d == 0.0 && (a == 0.0 || g == 0.0)) {
+ throw new IllegalArgumentException(
+ "Parameter a or g is zero, the transfer function is constant");
+ }
+
+ if (d >= 1.0 && c == 0.0) {
+ throw new IllegalArgumentException(
+ "Parameter c is zero, the transfer function is constant");
+ }
+
+ if ((a == 0.0 || g == 0.0) && c == 0.0) {
+ throw new IllegalArgumentException("Parameter a or g is zero,"
+ + " and c is zero, the transfer function is constant");
+ }
+
+ if (c < 0.0) {
+ throw new IllegalArgumentException(
+ "The transfer function must be increasing");
+ }
+
+ if (a < 0.0 || g < 0.0) {
+ throw new IllegalArgumentException(
+ "The transfer function must be positive or increasing");
+ }
+ }
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ this.g = g;
}
@SuppressWarnings("SimplifiableIfStatement")
@@ -2424,6 +2443,17 @@
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
+
+ /**
+ * @hide
+ */
+ private boolean isHLGish() {
+ return g == TYPE_HLGish;
+ }
+
+ private boolean isPQish() {
+ return g == TYPE_PQish;
+ }
}
@NonNull private final float[] mWhitePoint;
@@ -2460,11 +2490,10 @@
float e, float f, float g, float[] xyz);
private static DoubleUnaryOperator generateOETF(TransferParameters function) {
- boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
- || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
- if (isNonCurveTransferParameters) {
- return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGOETF(x)
- : x -> transferST2048OETF(x);
+ if (function.isHLGish()) {
+ return x -> transferHLGOETF(function, x);
+ } else if (function.isPQish()) {
+ return x -> transferST2048OETF(function, x);
} else {
return function.e == 0.0 && function.f == 0.0
? x -> rcpResponse(x, function.a, function.b,
@@ -2475,11 +2504,10 @@
}
private static DoubleUnaryOperator generateEOTF(TransferParameters function) {
- boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
- || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
- if (isNonCurveTransferParameters) {
- return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGEOTF(x)
- : x -> transferST2048EOTF(x);
+ if (function.isHLGish()) {
+ return x -> transferHLGEOTF(function, x);
+ } else if (function.isPQish()) {
+ return x -> transferST2048OETF(function, x);
} else {
return function.e == 0.0 && function.f == 0.0
? x -> response(x, function.a, function.b,
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 51f99ec..60898ef 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -914,6 +914,7 @@
case "image/gif":
case "image/heif":
case "image/heic":
+ case "image/avif":
case "image/bmp":
case "image/x-ico":
case "image/vnd.wap.wbmp":
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
index 6fa1a69..372e4cb 100644
--- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java
@@ -40,7 +40,6 @@
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
-import java.util.Random;
/**
* Assorted utility methods for implementing crypto operations on top of KeyStore.
@@ -50,7 +49,6 @@
abstract class KeyStoreCryptoOperationUtils {
private static volatile SecureRandom sRng;
- private static final Random sRandom = new Random();
private KeyStoreCryptoOperationUtils() {}
@@ -213,7 +211,7 @@
} else {
// Keystore won't give us an operation challenge if the operation doesn't
// need user authorization. So we make our own.
- return sRandom.nextLong();
+ return getRng().nextLong();
}
}
}
diff --git a/libs/WindowManager/Shell/res/color/unfold_background.xml b/libs/WindowManager/Shell/res/color/unfold_background.xml
new file mode 100644
index 0000000..e33eb12
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/unfold_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="5" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index 27e0b18..5d77713 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -14,13 +14,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="@color/decor_button_dark_color">
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<group android:translateY="8.0">
<path
- android:fillColor="@android:color/white" android:pathData="M3,5V3H21V5Z"/>
+ android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
new file mode 100644
index 0000000..0225949
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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 android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#bf309fb5" />
+ <corners android:radius="20dp" />
+ <stroke android:width="1dp" color="#A00080FF"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
new file mode 100644
index 0000000..3e0297a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_baseline_expand_more_24.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<vector android:height="24dp" android:tint="#000000"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/black" android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
new file mode 100644
index 0000000..35562b6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.wm.shell.windowdecor.WindowDecorLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/desktop_mode_caption"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal"
+ android:background="@drawable/desktop_mode_decor_title">
+
+ <LinearLayout
+ android:id="@+id/open_menu_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingStart="8dp"
+ android:background="?android:selectableItemBackgroundBorderless">
+
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_margin="4dp"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/app_icon_text" />
+
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:minWidth="80dp"
+ android:textColor="@color/desktop_mode_caption_app_name_dark"
+ android:textSize="14sp"
+ android:textFontWeight="500"
+ android:gravity="center_vertical"
+ android:layout_weight="1"
+ android:paddingStart="4dp"
+ android:paddingEnd="4dp"
+ tools:text="Gmail"/>
+
+ <ImageButton
+ android:id="@+id/expand_menu_button"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:padding="4dp"
+ android:contentDescription="@string/collapse_menu_text"
+ android:src="@drawable/ic_baseline_expand_more_24"
+ android:tint="@color/desktop_mode_caption_expand_button_dark"
+ android:background="@null"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ android:focusable="false"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ <View
+ android:id="@+id/caption_handle"
+ android:layout_width="wrap_content"
+ android:layout_height="40dp"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/close_window"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:padding="4dp"
+ android:layout_marginEnd="8dp"
+ android:contentDescription="@string/close_button_text"
+ android:src="@drawable/decor_close_button_dark"
+ android:scaleType="fitCenter"
+ android:gravity="end"
+ android:background="?android:selectableItemBackgroundBorderless"
+ android:tint="@color/desktop_mode_caption_close_button_dark"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
index f9aeb6a..ac13eae 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
@@ -50,7 +50,7 @@
android:layout_marginEnd="10dp"
android:contentDescription="@string/collapse_menu_text"
android:layout_alignParentEnd="true"
- android:background="@drawable/caption_collapse_menu_button"
+ android:background="@drawable/ic_baseline_expand_more_24"
android:layout_centerVertical="true"/>
</RelativeLayout>
<LinearLayout
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
similarity index 67%
rename from libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 29cf151..5ab159c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -16,26 +16,21 @@
-->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/desktop_mode_caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="@drawable/desktop_mode_decor_title">
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/back_button"
- android:contentDescription="@string/back_button_text"
- android:background="@drawable/decor_back_button_dark"/>
- <Button
+
+ <ImageButton
android:id="@+id/caption_handle"
android:layout_width="128dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
+ android:layout_height="42dp"
android:contentDescription="@string/handle_text"
- android:background="@drawable/decor_handle_dark"/>
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/close_window"
- android:contentDescription="@string/close_button_text"
- android:background="@drawable/decor_close_button_dark"/>
+ android:src="@drawable/decor_handle_dark"
+ tools:tint="@color/desktop_mode_caption_handle_bar_dark"
+ android:scaleType="fitXY"
+ android:background="?android:selectableItemBackground"/>
+
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
index 413cfd7..a993469 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2022 The Android Open Source Project
+ ~ Copyright (C) 2023 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.
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogLayout
+<com.android.wm.shell.compatui.LetterboxEduDialogLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/LetterboxDialog">
@@ -78,13 +78,13 @@
android:orientation="horizontal"
android:paddingTop="48dp">
- <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ <com.android.wm.shell.compatui.LetterboxEduDialogActionLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/letterbox_education_ic_reposition"
app:text="@string/letterbox_education_reposition_text"/>
- <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+ <com.android.wm.shell.compatui.LetterboxEduDialogActionLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart=
@@ -118,4 +118,4 @@
</FrameLayout>
-</com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogLayout>
+</com.android.wm.shell.compatui.LetterboxEduDialogLayout>
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 0897712..848f28c 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Beweeg na regs onder"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-instellings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Maak borrel toe"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Moenie borrels wys nie"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Moenie dat gesprek \'n borrel word nie"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Klets met borrels"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nuwe gesprekke verskyn as swerwende ikone, of borrels Tik op borrel om dit oop te maak. Sleep om dit te skuif."</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index bc58e20..9ed281f 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ታችኛውን ቀኝ ያንቀሳቅሱ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"የ<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ቅንብሮች"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"አረፋን አሰናብት"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ዓረፋ አትፍጠር"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ውይይቶችን በአረፋ አታሳይ"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"አረፋዎችን በመጠቀም ይወያዩ"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"አዲስ ውይይቶች እንደ ተንሳፋፊ አዶዎች ወይም አረፋዎች ሆነው ይታያሉ። አረፋን ለመክፈት መታ ያድርጉ። ለመውሰድ ይጎትቱት።"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 2aaf924..a8480eb 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"তলৰ সোঁফালে নিয়ক"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ছেটিং"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল অগ্ৰাহ্য কৰক"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"বাবল কৰাটো বন্ধ কৰক"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"বাৰ্তালাপ বাবল নকৰিব"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bubbles ব্যৱহাৰ কৰি চাট কৰক"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"নতুন বাৰ্তালাপ উপঙি থকা চিহ্নসমূহ অথবা bubbles হিচাপে প্ৰদর্শিত হয়। Bubbles খুলিবলৈ টিপক। এইটো স্থানান্তৰ কৰিবলৈ টানি নিয়ক।"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index ad6e618..b158cdd 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Aşağıya sağa köçürün"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ayarları"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Yumrucuğu ləğv edin"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Yumrucuqları dayandırın"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Söhbəti yumrucuqda göstərmə"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Yumrucuqlardan istifadə edərək söhbət edin"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yeni söhbətlər üzən nişanlar və ya yumrucuqlar kimi görünür. Yumrucuğu açmaq üçün toxunun. Hərəkət etdirmək üçün sürüşdürün."</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 1dd09f5..954d34fd 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premesti dole desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Podešavanja za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Bez oblačića"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne koristi oblačiće za konverzaciju"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Ćaskajte u oblačićima"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nove konverzacije se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da biste otvorili oblačić. Prevucite da biste ga premestili."</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index b5d4eb1..a7757c6 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Преместване долу вдясно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Настройки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отхвърляне на балончетата"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Без балончета"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Без балончета за разговора"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Чат с балончета"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Новите разговори се показват като плаващи икони, или балончета. Докоснете балонче, за да го отворите, или го плъзнете, за да го преместите."</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 5c95f9e..f3f80b8 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"নিচে ডান দিকে সরান"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> সেটিংস"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"বাবল খারিজ করুন"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"বাবল করা বন্ধ করুন"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"কথোপকথন বাবল হিসেবে দেখাবে না"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"বাবল ব্যবহার করে চ্যাট করুন"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"নতুন কথোপকথন ভেসে থাকা আইকন বা বাবল হিসেবে দেখানো হয়। বাবল খুলতে ট্যাপ করুন। সেটি সরাতে ধরে টেনে আনুন।"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index f9ff29c..e7fa212 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pomjerite dolje desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke aplikacije <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Zaustavi oblačiće"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nemoj prikazivati razgovor u oblačićima"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatajte koristeći oblačiće"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi razgovori se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da otvorite oblačić. Prevucite da ga premjestite."</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index b54e60d..1021667 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mou a baix a la dreta"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuració de l\'aplicació <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignora la bombolla"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"No mostris com a bombolla"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostris la conversa com a bombolla"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Xateja amb bombolles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les converses noves es mostren com a icones flotants o bombolles. Toca per obrir una bombolla. Arrossega-la per moure-la."</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 11090e0..b2b76197 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Nach unten rechts verschieben"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Einstellungen für <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubble schließen"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Keine Bubbles zulassen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Unterhaltung nicht als Bubble anzeigen"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bubbles zum Chatten verwenden"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Neue Unterhaltungen erscheinen als unverankerte Symbole, „Bubbles“ genannt. Wenn du eine Bubble öffnen möchtest, tippe sie an. Wenn du sie verschieben möchtest, zieh an ihr."</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 1eca52a..a09a647 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Μετακίνηση κάτω δεξιά"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ρυθμίσεις <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Παράβλ. για συννεφ."</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Να μην εμφανίζει συννεφάκια"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Να μην γίνει προβολή της συζήτησης σε συννεφάκια."</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Συζητήστε χρησιμοποιώντας συννεφάκια."</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Οι νέες συζητήσεις εμφανίζονται ως κινούμενα εικονίδια ή συννεφάκια. Πατήστε για να ανοίξετε το συννεφάκι. Σύρετε για να το μετακινήσετε."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 8493434..0a0b30d 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index e9c78fc..33f5333 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 8493434..0a0b30d 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 8493434..0a0b30d 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 466a2bf..69823f9 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Move bottom right"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> settings"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dismiss bubble"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Don’t bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index b017591..b7b31f4 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ubicar abajo a la derecha"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configuración de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Descartar burbuja"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"No mostrar burbujas"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar la conversación en burbuja"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat con burbujas"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como elementos flotantes o burbujas. Presiona para abrir la burbuja. Arrástrala para moverla."</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 986a2c8..5ef402c 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover abajo a la derecha"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Ajustes de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cerrar burbuja"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"No mostrar burbujas"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"No mostrar conversación en burbuja"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatea con burbujas"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Las conversaciones nuevas aparecen como iconos flotantes llamados \"burbujas\". Toca una burbuja para abrirla. Arrástrala para moverla."</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index b84c4f2..e083be4 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Teisalda alla paremale"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Rakenduse <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> seaded"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Sule mull"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ära kuva mulle"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ära kuva vestlust mullina"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Vestelge mullide abil"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Uued vestlused kuvatakse hõljuvate ikoonidena ehk mullidena. Puudutage mulli avamiseks. Lohistage mulli, et seda liigutada."</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index a297219..e0922e4 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Eraman behealdera, eskuinetara"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> aplikazioaren ezarpenak"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Baztertu burbuila"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ez erakutsi burbuilarik"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ez erakutsi elkarrizketak burbuila gisa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Txateatu burbuilen bidez"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Elkarrizketa berriak ikono gainerakor edo burbuila gisa agertzen dira. Sakatu burbuila irekitzeko. Arrasta ezazu mugitzeko."</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index b379428..e847f6e 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"انتقال به پایین سمت چپ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"تنظیمات <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"رد کردن حبابک"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"حبابک نشان داده نشود"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"مکالمه در حباب نشان داده نشود"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"گپ بااستفاده از حبابکها"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"مکالمههای جدید بهصورت نمادهای شناور یا حبابکها نشان داده میشوند. برای باز کردن حبابکها ضربه بزنید. برای جابهجایی، آن را بکشید."</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 7e3eae0..c86711e 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer en bas à droite"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Fermer la bulle"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Désactiver les info-bulles"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatter en utilisant des bulles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou de bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index f3a3162..8de29c1 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"નીચે જમણે ખસેડો"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> સેટિંગ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"બબલને છોડી દો"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"બબલ બતાવશો નહીં"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"વાતચીતને બબલ કરશો નહીં"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"બબલનો ઉપયોગ કરીને ચૅટ કરો"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"નવી વાતચીત ફ્લોટિંગ આઇકન અથવા બબલ જેવી દેખાશે. બબલને ખોલવા માટે ટૅપ કરો. તેને ખસેડવા માટે ખેંચો."</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 134fb97..d71824f 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"सबसे नीचे दाईं ओर ले जाएं"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> की सेटिंग"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारिज करें"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"बबल होने से रोकें"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"बातचीत को बबल न करें"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"बबल्स का इस्तेमाल करके चैट करें"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"नई बातचीत फ़्लोटिंग आइकॉन या बबल्स की तरह दिखेंगी. बबल को खोलने के लिए टैप करें. इसे एक जगह से दूसरी जगह ले जाने के लिए खींचें और छोड़ें."</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index f0a8dee..de4cac2 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premjestite u donji desni kut"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Postavke za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Odbaci oblačić"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ne prikazuj oblačiće"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Zaustavi razgovor u oblačićima"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Oblačići u chatu"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi razgovori pojavljuju se kao pomične ikone ili oblačići. Dodirnite za otvaranje oblačića. Povucite da biste ga premjestili."</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index ea43370..667464f 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Áthelyezés le és jobbra"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> beállításai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Buborék elvetése"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ne jelenjen meg buborékban"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne jelenjen meg a beszélgetés buborékban"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Buborékokat használó csevegés"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Az új beszélgetések lebegő ikonként, vagyis buborékként jelennek meg. A buborék megnyitásához koppintson rá. Áthelyezéshez húzza a kívánt helyre."</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index df2f2df..8cd0bc5 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pindahkan ke kanan bawah"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setelan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Tutup balon"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Berhenti menampilkan balon"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan gunakan percakapan balon"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat dalam tampilan balon"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Percakapan baru muncul sebagai ikon mengambang, atau balon. Ketuk untuk membuka balon. Tarik untuk memindahkannya."</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index a6301b3..ae5e182 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Færðu neðst til hægri"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Stillingar <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Loka blöðru"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ekki sýna blöðrur"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ekki setja samtal í blöðru"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Spjalla með blöðrum"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Ný samtöl birtast sem fljótandi tákn eða blöðrur. Ýttu til að opna blöðru. Dragðu hana til að færa."</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index b925970..9963ace 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"העברה לפינה הימנית התחתונה"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"הגדרות <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"סגירת בועה"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ללא בועות"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"אין להציג בועות לשיחה"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"לדבר בבועות"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"שיחות חדשות מופיעות כסמלים צפים, או בועות. יש להקיש כדי לפתוח בועה. יש לגרור כדי להזיז אותה."</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index e1d1de4..02cf02f 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"გადაანაცვ. ქვემოთ და მარჯვნივ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-ის პარამეტრები"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ბუშტის დახურვა"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ბუშტის გამორთვა"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"აიკრძალოს საუბრის ბუშტები"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ჩეთი ბუშტების გამოყენებით"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"ახალი საუბრები გამოჩნდება როგორც მოტივტივე ხატულები ან ბუშტები. შეეხეთ ბუშტის გასახსნელად. გადაიტანეთ ჩავლებით."</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index de952ad..b60c231 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төменгі оң жаққа жылжыту"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлері"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Қалқымалы хабарды жабу"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Қалқыма хабарлар көрсетпеу"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Әңгіменің қалқыма хабары көрсетілмесін"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Қалқыма хабарлар арқылы сөйлесу"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңа әңгімелер қалқыма белгішелер немесе хабарлар түрінде көрсетіледі. Қалқыма хабарды ашу үшін түртіңіз. Жылжыту үшін сүйреңіз."</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 762f3a8..29ff6c3 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ផ្លាស់ទីទៅផ្នែកខាងក្រោមខាងស្ដាំ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ការកំណត់ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ច្រានចោលពពុះ"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"កុំបង្ហាញពពុះ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"កុំបង្ហាញការសន្ទនាជាពពុះ"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ជជែកដោយប្រើពពុះ"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"ការសន្ទនាថ្មីៗបង្ហាញជាពពុះ ឬរូបអណ្ដែត។ ចុច ដើម្បីបើកពពុះ។ អូស ដើម្បីផ្លាស់ទីពពុះនេះ។"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index f9582ff..a67e066 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ಬಬಲ್ ವಜಾಗೊಳಿಸಿ"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ಬಬಲ್ ತೋರಿಸಬೇಡಿ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ಸಂಭಾಷಣೆಯನ್ನು ಬಬಲ್ ಮಾಡಬೇಡಿ"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ಬಬಲ್ಸ್ ಬಳಸಿ ಚಾಟ್ ಮಾಡಿ"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"ಹೊಸ ಸಂಭಾಷಣೆಗಳು ತೇಲುವ ಐಕಾನ್ಗಳು ಅಥವಾ ಬಬಲ್ಸ್ ಆಗಿ ಗೋಚರಿಸುತ್ತವೆ. ಬಬಲ್ ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಅದನ್ನು ಡ್ರ್ಯಾಗ್ ಮಾಡಲು ಎಳೆಯಿರಿ."</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 2b30aab..0b88d7a 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"오른쪽 하단으로 이동"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> 설정"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"대화창 닫기"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"도움말 풍선 중지"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"대화를 대화창으로 표시하지 않기"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"대화창으로 채팅하기"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"새로운 대화가 플로팅 아이콘인 대화창으로 표시됩니다. 대화창을 열려면 탭하세요. 드래그하여 이동할 수 있습니다."</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 27b89b7..683a903 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдыруу"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлери"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Калкып чыкма билдирмелер көрсөтүлбөсүн"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 6aae84c..c311410 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ຍ້າຍຂວາລຸ່ມ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"ການຕັ້ງຄ່າ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ປິດຟອງໄວ້"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ບໍ່ຕ້ອງສະແດງ bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ຢ່າໃຊ້ຟອງໃນການສົນທະນາ"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ສົນທະນາໂດຍໃຊ້ຟອງ"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"ການສົນທະນາໃໝ່ຈະປາກົດເປັນໄອຄອນ ຫຼື ຟອງແບບລອຍ. ແຕະເພື່ອເປີດຟອງ. ລາກເພື່ອຍ້າຍມັນ."</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 3f2a17b..be8e247 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Perkelti į apačią dešinėje"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ nustatymai"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Atsisakyti burbulo"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nerodyti debesėlių"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerodyti pokalbio burbule"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Pokalbis naudojant burbulus"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nauji pokalbiai rodomi kaip slankiosios piktogramos arba burbulai. Palieskite, kad atidarytumėte burbulą. Vilkite, kad perkeltumėte."</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index ae85c1f..d6a1f16 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Pārvietot apakšpusē pa labi"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Lietotnes <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iestatījumi"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Nerādīt burbuli"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Pārtraukt burbuļu rādīšanu"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nerādīt sarunu burbuļos"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Tērzēšana, izmantojot burbuļus"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Jaunas sarunas tiek rādītas kā peldošas ikonas vai burbuļi. Pieskarieties, lai atvērtu burbuli. Velciet, lai to pārvietotu."</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 133eedb1..385dc32 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести долу десно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Поставки за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Отфрли балонче"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Не прикажувај балонче"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не прикажувај го разговорот во балончиња"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Разговор во балончиња"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Новите разговори ќе се појавуваат како лебдечки икони или балончиња. Допрете за отворање на балончето. Повлечете за да го преместите."</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index c38609e..561806c 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ക്രമീകരണം"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ബബിൾ ഡിസ്മിസ് ചെയ്യൂ"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ബബിൾ ചെയ്യരുത്"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"സംഭാഷണം ബബിൾ ചെയ്യരുത്"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ബബിളുകൾ ഉപയോഗിച്ച് ചാറ്റ് ചെയ്യുക"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"പുതിയ സംഭാഷണങ്ങൾ ഫ്ലോട്ടിംഗ് ഐക്കണുകളോ ബബിളുകളോ ആയി ദൃശ്യമാവുന്നു. ബബിൾ തുറക്കാൻ ടാപ്പ് ചെയ്യൂ. ഇത് നീക്കാൻ വലിച്ചിടുക."</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index e0b4141..c0c9eb7 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Баруун доош зөөх"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>-н тохиргоо"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Бөмбөлгийг хаах"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Бөмбөлөг бүү харуул"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Харилцан яриаг бүү бөмбөлөг болго"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Бөмбөлөг ашиглан чатлаарай"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Шинэ харилцан яриа нь хөвөгч дүрс тэмдэг эсвэл бөмбөлөг хэлбэрээр харагддаг. Бөмбөлгийг нээхийн тулд товшино уу. Түүнийг зөөхийн тулд чирнэ үү."</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index f85fe1b..a18d1f1 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"तळाशी उजवीकडे हलवा"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> सेटिंग्ज"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल डिसमिस करा"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"बबल दाखवू नका"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"संभाषणाला बबल करू नका"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"बबल वापरून चॅट करा"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"नवीन संभाषणे फ्लोटिंग आयकन किंवा बबल म्हणून दिसतात. बबल उघडण्यासाठी टॅप करा. हे हलवण्यासाठी ड्रॅग करा."</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 6f22599..b891c59 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Alihkan ke bawah sebelah kanan"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Tetapan <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ketepikan gelembung"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Hentikan gelembung"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Jangan jadikan perbualan dalam bentuk gelembung"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bersembang menggunakan gelembung"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Perbualan baharu muncul sebagai ikon terapung atau gelembung. Ketik untuk membuka gelembung. Seret untuk mengalihkan gelembung tersebut."</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 5a88cd0..2b6adc1 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ညာအောက်ခြေသို့ ရွှေ့ပါ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ဆက်တင်များ"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ပူဖောင်းကွက် ပယ်ရန်"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ပူဖောင်းကွက် မပြပါနှင့်"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"စကားဝိုင်းကို ပူဖောင်းကွက် မပြုလုပ်ပါနှင့်"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ပူဖောင်းကွက် သုံး၍ ချတ်လုပ်ခြင်း"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"စကားဝိုင်းအသစ်များကို မျောနေသည့် သင်္ကေတများ သို့မဟုတ် ပူဖောင်းကွက်များအဖြစ် မြင်ရပါမည်။ ပူဖောင်းကွက်ကိုဖွင့်ရန် တို့ပါ။ ရွှေ့ရန် ၎င်းကို ဖိဆွဲပါ။"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 6b164e9..3afa9fb 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"पुछारमा दायाँतिर सार्नुहोस्"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> का सेटिङहरू"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"बबल खारेज गर्नुहोस्"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"बबल नदेखाइयोस्"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"वार्तालाप बबलको रूपमा नदेखाइयोस्"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"बबलहरू प्रयोग गरी कुराकानी गर्नुहोस्"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"नयाँ वार्तालापहरू तैरने आइकन वा बबलका रूपमा देखिन्छन्। बबल खोल्न ट्याप गर्नुहोस्। बबल सार्न सो बबललाई ड्र्याग गर्नुहोस्।"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index 2d02ed5..21e89c3 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Naar rechtsonder verplaatsen"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Instellingen voor <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bubbel sluiten"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Niet als bubbel tonen"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Gesprekken niet in bubbels tonen"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatten met bubbels"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nieuwe gesprekken worden als zwevende iconen of bubbels getoond. Tik om een bubbel te openen. Sleep om een bubbel te verplaatsen."</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 6400283..e781f45 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ତଳ ଡାହାଣକୁ ନିଅନ୍ତୁ"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ସେଟିଂସ୍"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ବବଲ୍ ଖାରଜ କରନ୍ତୁ"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ବବଲ କରନ୍ତୁ ନାହିଁ"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ବାର୍ତ୍ତାଳାପକୁ ବବଲ୍ କରନ୍ତୁ ନାହିଁ"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"ବବଲଗୁଡ଼ିକୁ ବ୍ୟବହାର କରି ଚାଟ୍ କରନ୍ତୁ"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"ନୂଆ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ଫ୍ଲୋଟିଂ ଆଇକନ୍ କିମ୍ବା ବବଲ୍ ଭାବେ ଦେଖାଯିବ। ବବଲ୍ ଖୋଲିବାକୁ ଟାପ୍ କରନ୍ତୁ। ଏହାକୁ ମୁଭ୍ କରିବାକୁ ଟାଣନ୍ତୁ।"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 680bd5d..ef7773b9 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Przenieś w prawy dolny róg"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – ustawienia"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zamknij dymek"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nie twórz dymków"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nie wyświetlaj rozmowy jako dymka"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Czatuj, korzystając z dymków"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nowe rozmowy będą wyświetlane jako pływające ikony lub dymki. Kliknij, by otworzyć dymek. Przeciągnij, by go przenieść."</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index c30b6f1..b034990 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Parar de mostrar balões"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse usando balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 00f4aa0..c739ba0 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover parte inferior direita"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Definições de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorar balão"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Não apresentar balões"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não apresentar a conversa em balões"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse no chat através de balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"As novas conversas aparecem como ícones flutuantes ou balões. Toque para abrir o balão. Arraste para o mover."</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index c30b6f1..b034990 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mover para canto inferior direito"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Configurações de <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Dispensar balão"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Parar de mostrar balões"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Não criar balões de conversa"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Converse usando balões"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 4cfb1fa..cbe11dd 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Mută în dreapta jos"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Setări <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Închide balonul"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nu afișa bule"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nu afișa conversația în balon"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat cu baloane"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Conversațiile noi apar ca pictograme flotante sau baloane. Atinge pentru a deschide balonul. Trage pentru a-l muta."</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index 741a5d1..7602282 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Перенести в правый нижний угол"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: настройки"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Скрыть всплывающий чат"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Отключить всплывающие подсказки"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не показывать всплывающий чат для разговора"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Всплывающие чаты"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Новые разговоры будут появляться в виде плавающих значков, или всплывающих чатов. Чтобы открыть чат, нажмите на него, а чтобы переместить – перетащите."</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 4b89ab1..c908184 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Presunúť doprava nadol"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavenia aplikácie <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Zavrieť bublinu"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Nezobrazovať bubliny"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Nezobrazovať konverzáciu ako bublinu"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Čet pomocou bublín"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nové konverzácie sa zobrazujú ako plávajúce ikony či bubliny. Bublinu otvoríte klepnutím. Premiestnite ju presunutím."</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 6eb83ed..ed7feb9 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Premakni spodaj desno"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Nastavitve za <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Opusti oblaček"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ne prikazuj oblačkov aplikacij"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Pogovora ne prikaži v oblačku"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Klepet z oblački"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Novi pogovori so prikazani kot lebdeče ikone ali oblački. Če želite odpreti oblaček, se ga dotaknite. Če ga želite premakniti, ga povlecite."</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index e804c52..0b64d5d 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Lëvize poshtë djathtas"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cilësimet e <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Hiqe flluskën"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Mos shfaq flluska"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Mos e vendos bisedën në flluskë"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bisedo duke përdorur flluskat"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Bisedat e reja shfaqen si ikona pluskuese ose flluska. Trokit për të hapur flluskën. Zvarrit për ta zhvendosur."</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 9aafa87..8f44eeb 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Премести доле десно"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Подешавања за <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Одбаци облачић"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Без облачића"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Не користи облачиће за конверзацију"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Ћаскајте у облачићима"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Нове конверзације се приказују као плутајуће иконе или облачићи. Додирните да бисте отворили облачић. Превуците да бисте га преместили."</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 6316d52..1bd744a 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Flytta längst ned till höger"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Inställningar för <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Stäng bubbla"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Visa inte bubblor"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Visa inte konversationen i bubblor"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chatta med bubblor"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nya konversationer visas som flytande ikoner, så kallade bubblor. Tryck på bubblan om du vill öppna den. Dra den om du vill flytta den."</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 46bd638..f2f3bbc 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Sogeza chini kulia"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mipangilio ya <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Ondoa kiputo"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Isifanye viputo"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Usiweke viputo kwenye mazungumzo"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Piga gumzo ukitumia viputo"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Mazungumzo mapya huonekena kama aikoni au viputo vinavyoelea. Gusa ili ufungue kiputo. Buruta ili ukisogeze."</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 512bd52..581a5ab 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"దిగవు కుడివైపునకు జరుపు"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> సెట్టింగ్లు"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"బబుల్ను విస్మరించు"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"బబుల్ను చూపడం ఆపండి"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"సంభాషణను బబుల్ చేయవద్దు"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"బబుల్స్ను ఉపయోగించి చాట్ చేయండి"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"కొత్త సంభాషణలు తేలియాడే చిహ్నాలుగా లేదా బబుల్స్ లాగా కనిపిస్తాయి. బబుల్ని తెరవడానికి నొక్కండి. తరలించడానికి లాగండి."</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index e533869..44afb58 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"ย้ายไปด้านขาวล่าง"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"การตั้งค่า <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"ปิดบับเบิล"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"ไม่ต้องแสดงบับเบิล"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"ไม่ต้องแสดงการสนทนาเป็นบับเบิล"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"แชทโดยใช้บับเบิล"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"การสนทนาใหม่ๆ จะปรากฏเป็นไอคอนแบบลอยหรือบับเบิล แตะเพื่อเปิดบับเบิล ลากเพื่อย้ายที่"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 426e83a..d287e9d 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Ilipat sa kanan sa ibaba"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Mga setting ng <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"I-dismiss ang bubble"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Huwag i-bubble"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Huwag ipakita sa bubble ang mga pag-uusap"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Mag-chat gamit ang bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Lumalabas bilang mga nakalutang na icon o bubble ang mga bagong pag-uusap. I-tap para buksan ang bubble. I-drag para ilipat ito."</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index f4373e6..5599351 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"نیچے دائیں جانب لے جائیں"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ترتیبات"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"بلبلہ برخاست کریں"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"بلبلہ دکھانا بند کریں"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"گفتگو بلبلہ نہ کریں"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"بلبلے کے ذریعے چیٹ کریں"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"نئی گفتگوئیں فلوٹنگ آئیکن یا بلبلے کے طور پر ظاہر ہوں گی۔ بلبلہ کھولنے کے لیے تھپتھپائیں۔ اسے منتقل کرنے کے لیے گھسیٹیں۔"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 8c07814..54898156 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Quyi oʻngga surish"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> sozlamalari"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Bulutchani yopish"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Qalqib chiqmasin"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Suhbatlar bulutchalar shaklida chiqmasin"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Bulutchalar yordamida subhatlashish"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Yangi xabarlar qalqib chiquvchi belgilar yoki bulutchalar kabi chiqadi. Xabarni ochish uchun bildirishnoma ustiga bosing. Xabarni qayta joylash uchun bildirishnomani suring."</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index bbb3639..bb144a7 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Chuyển tới dưới cùng bên phải"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"Cài đặt <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Đóng bong bóng"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Không hiện bong bóng trò chuyện"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Dừng sử dụng bong bóng cho cuộc trò chuyện"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Trò chuyện bằng bong bóng trò chuyện"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Các cuộc trò chuyện mới sẽ xuất hiện dưới dạng biểu tượng nổi hoặc bong bóng trò chuyện. Nhấn để mở bong bóng trò chuyện. Kéo để di chuyển bong bóng trò chuyện."</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index caca25a..4b2ee18 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"移至右下角"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>设置"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"关闭对话泡"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"不显示对话泡"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"不以对话泡形式显示对话"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"使用对话泡聊天"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"新对话会以浮动图标或对话泡形式显示。点按即可打开对话泡。拖动即可移动对话泡。"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 7787e77..1a6e46c 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -68,8 +68,7 @@
<string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Hambisa inkinobho ngakwesokudla"</string>
<string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> izilungiselelo"</string>
<string name="bubble_dismiss_text" msgid="8816558050659478158">"Cashisa ibhamuza"</string>
- <!-- no translation found for bubbles_dont_bubble (3216183855437329223) -->
- <skip />
+ <string name="bubbles_dont_bubble" msgid="3216183855437329223">"Ungabhamuzi"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ungayibhamuzi ingxoxo"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Xoxa usebenzisa amabhamuza"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Izingxoxo ezintsha zivela njengezithonjana ezintantayo, noma amabhamuza. Thepha ukuze uvule ibhamuza. Hudula ukuze ulihambise."</string>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 6fb70006..2a03b03 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -54,4 +54,14 @@
<color name="splash_screen_bg_light">#FFFFFF</color>
<color name="splash_screen_bg_dark">#000000</color>
<color name="splash_window_background_default">@color/splash_screen_bg_light</color>
+
+ <!-- Desktop Mode -->
+ <color name="desktop_mode_caption_handle_bar_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_handle_bar_dark">#1C1C17</color>
+ <color name="desktop_mode_caption_expand_button_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_expand_button_dark">#48473A</color>
+ <color name="desktop_mode_caption_close_button_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
+ <color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
+ <color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 680ad51..88ca10d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -365,6 +365,7 @@
<dimen name="freeform_decor_caption_menu_width">256dp</dimen>
<dimen name="freeform_decor_caption_menu_height">250dp</dimen>
+ <dimen name="freeform_decor_caption_menu_height_no_windowing_controls">210dp</dimen>
<dimen name="freeform_resize_handle">30dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index e24c228..85a353f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -206,12 +206,14 @@
public Bubble(Intent intent,
UserHandle user,
+ @Nullable Icon icon,
Executor mainExecutor) {
mKey = KEY_APP_BUBBLE;
mGroupKey = null;
mLocusId = null;
mFlags = 0;
mUser = user;
+ mIcon = icon;
mShowBubbleUpdateDot = false;
mMainExecutor = mainExecutor;
mTaskId = INVALID_TASK_ID;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 48fe65d..d2889e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -57,6 +57,7 @@
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
@@ -1034,8 +1035,9 @@
*
* @param intent the intent to display in the bubble expanded view.
* @param user the {@link UserHandle} of the user to start this activity for.
+ * @param icon the {@link Icon} to use for the bubble view.
*/
- public void showOrHideAppBubble(Intent intent, UserHandle user) {
+ public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
if (intent == null || intent.getPackage() == null) {
Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ ((intent != null) ? " with package: " + intent.getPackage() : " "));
@@ -1063,7 +1065,7 @@
}
} else {
// App bubble does not exist, lets add and expand it
- Bubble b = new Bubble(intent, user, mMainExecutor);
+ Bubble b = new Bubble(intent, user, icon, mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1871,9 +1873,9 @@
}
@Override
- public void showOrHideAppBubble(Intent intent, UserHandle user) {
+ public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
mMainExecutor.execute(
- () -> BubbleController.this.showOrHideAppBubble(intent, user));
+ () -> BubbleController.this.showOrHideAppBubble(intent, user, icon));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 5555bec..876a720 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -26,6 +26,7 @@
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Icon;
import android.hardware.HardwareBuffer;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
@@ -135,8 +136,9 @@
*
* @param intent the intent to display in the bubble expanded view.
* @param user the {@link UserHandle} of the user to start this activity for.
+ * @param icon the {@link Icon} to use for the bubble view.
*/
- void showOrHideAppBubble(Intent intent, UserHandle user);
+ void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon);
/** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
boolean isAppBubbleTaskId(int taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index cb1a6e7..ac6e4c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -59,7 +59,7 @@
*/
private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
SystemProperties.getBoolean(
- "persist.wm.debug.enable_move_floating_window_in_tabletop", false);
+ "persist.wm.debug.enable_move_floating_window_in_tabletop", true);
/**
* Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b447a54..5459094 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -738,6 +738,10 @@
getRefBounds2(mTempRect);
t.setPosition(leash2, mTempRect.left, mTempRect.top)
.setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
+ // Make right or bottom side surface always higher than left or top side to avoid weird
+ // animation when dismiss split. e.g. App surface fling above on decor surface.
+ t.setLayer(leash1, 1);
+ t.setLayer(leash2, 2);
if (mImePositionProcessor.adjustSurfaceLayoutForIme(
t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 76d9152..6950f24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -42,7 +42,6 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index fe95d04..170c0ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -38,7 +38,6 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
index 02197f6..9974295 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import android.content.Context;
import android.content.res.TypedArray;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
similarity index 94%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
index 9232f36..df2f6ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import android.annotation.Nullable;
import android.content.Context;
@@ -26,7 +26,6 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.wm.shell.R;
-import com.android.wm.shell.compatui.DialogContainerSupplier;
/**
* Container for Letterbox Education Dialog and background dim.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index c14c009..bfdbfe3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
@@ -36,8 +36,6 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
-import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index cc0da28..eb7c32f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -671,13 +671,17 @@
Context context,
ShellInit shellInit,
ShellController shellController,
+ DisplayController displayController,
ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer,
- transitions, desktopModeTaskRepository, mainExecutor);
+ return new DesktopTasksController(context, shellInit, shellController, displayController,
+ shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
+ desktopModeTaskRepository, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index c9c0e40..ad334b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -41,7 +41,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
-import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.DisplayAreaInfo;
@@ -364,10 +363,7 @@
}
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
- Pair<Transitions.TransitionHandler, WindowContainerTransaction> subHandler =
- mTransitions.dispatchRequest(transition, request, this);
- WindowContainerTransaction wct = subHandler != null
- ? subHandler.second : new WindowContainerTransaction();
+ WindowContainerTransaction wct = new WindowContainerTransaction();
bringDesktopAppsToFront(wct);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
new file mode 100644
index 0000000..015d5c1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.desktopmode;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Animated visual indicator for Desktop Mode windowing transitions.
+ */
+public class DesktopModeVisualIndicator {
+
+ private final Context mContext;
+ private final DisplayController mDisplayController;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
+ private final ActivityManager.RunningTaskInfo mTaskInfo;
+ private final SurfaceControl mTaskSurface;
+ private SurfaceControl mLeash;
+
+ private final SyncTransactionQueue mSyncQueue;
+ private SurfaceControlViewHost mViewHost;
+
+ public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
+ ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
+ Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
+ RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
+ mSyncQueue = syncQueue;
+ mTaskInfo = taskInfo;
+ mDisplayController = displayController;
+ mContext = context;
+ mTaskSurface = taskSurface;
+ mTaskOrganizer = taskOrganizer;
+ mRootTdaOrganizer = taskDisplayAreaOrganizer;
+ }
+
+ /**
+ * Create and animate the indicator for the exit desktop mode transition.
+ */
+ public void createFullscreenIndicator() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final Resources resources = mContext.getResources();
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ final int screenWidth = metrics.widthPixels;
+ final int screenHeight = metrics.heightPixels;
+ final int padding = mDisplayController
+ .getDisplayLayout(mTaskInfo.displayId).stableInsets().top;
+ final ImageView v = new ImageView(mContext);
+ v.setImageResource(R.drawable.desktop_windowing_transition_background);
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder();
+ mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
+ mLeash = builder
+ .setName("Fullscreen Indicator")
+ .setContainerLayer()
+ .build();
+ t.show(mLeash);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(screenWidth, screenHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ final WindowlessWindowManager windowManager = new WindowlessWindowManager(
+ mTaskInfo.configuration, mLeash,
+ null /* hostInputToken */);
+ mViewHost = new SurfaceControlViewHost(mContext,
+ mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
+ "FullscreenVisualIndicator");
+ mViewHost.setView(v, lp);
+ // We want this indicator to be behind the dragged task, but in front of all others.
+ t.setRelativeLayer(mLeash, mTaskSurface, -1);
+
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ final Rect startBounds = new Rect(padding, padding,
+ screenWidth - padding, screenHeight - padding);
+ final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v,
+ startBounds);
+ animator.start();
+ }
+
+ /**
+ * Release the indicator and its components when it is no longer needed.
+ */
+ public void releaseFullscreenIndicator() {
+ if (mViewHost == null) return;
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mLeash != null) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.remove(mLeash);
+ mLeash = null;
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ }
+ }
+ /**
+ * Animator for Desktop Mode transitions which supports bounds and alpha animation.
+ */
+ private static class VisualIndicatorAnimator extends ValueAnimator {
+ private static final int FULLSCREEN_INDICATOR_DURATION = 200;
+ private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f;
+ private static final float INDICATOR_FINAL_OPACITY = 0.7f;
+
+ private final ImageView mView;
+ private final Rect mStartBounds;
+ private final Rect mEndBounds;
+ private final RectEvaluator mRectEvaluator;
+
+ private VisualIndicatorAnimator(ImageView view, Rect startBounds,
+ Rect endBounds) {
+ mView = view;
+ mStartBounds = new Rect(startBounds);
+ mEndBounds = endBounds;
+ setFloatValues(0, 1);
+ mRectEvaluator = new RectEvaluator(new Rect());
+ }
+
+ /**
+ * Create animator for visual indicator of fullscreen transition
+ *
+ * @param view the view for this indicator
+ * @param startBounds the starting bounds of the fullscreen indicator
+ */
+ public static VisualIndicatorAnimator fullscreenIndicator(ImageView view,
+ Rect startBounds) {
+ view.getDrawable().setBounds(startBounds);
+ int width = startBounds.width();
+ int height = startBounds.height();
+ Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)),
+ (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)),
+ (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)),
+ (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height)));
+ VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, startBounds, endBounds);
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupFullscreenIndicatorAnimation(animator);
+ return animator;
+ }
+
+ /**
+ * Add necessary listener for animation of fullscreen indicator
+ */
+ private static void setupFullscreenIndicatorAnimation(
+ VisualIndicatorAnimator animator) {
+ animator.addUpdateListener(a -> {
+ if (animator.mView != null) {
+ animator.updateBounds(a.getAnimatedFraction(), animator.mView);
+ animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
+ } else {
+ animator.cancel();
+ }
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animator.mView.getDrawable().setBounds(animator.mEndBounds);
+ }
+ });
+ animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
+ }
+
+ /**
+ * Update bounds of view based on current animation fraction.
+ * Use of delta is to animate bounds independently, in case we need to
+ * run multiple animations simultaneously.
+ *
+ * @param fraction fraction to use, compared against previous fraction
+ * @param view the view to update
+ */
+ private void updateBounds(float fraction, ImageView view) {
+ Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
+ view.getDrawable().setBounds(currentBounds);
+ }
+
+ /**
+ * Fade in the fullscreen indicator
+ *
+ * @param fraction current animation fraction
+ */
+ private void updateIndicatorAlpha(float fraction, View view) {
+ view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 31c5e33..5696dfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -37,11 +38,14 @@
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
@@ -55,16 +59,20 @@
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
- private val context: Context,
- shellInit: ShellInit,
- private val shellController: ShellController,
- private val shellTaskOrganizer: ShellTaskOrganizer,
- private val transitions: Transitions,
- private val desktopModeTaskRepository: DesktopModeTaskRepository,
- @ShellMainThread private val mainExecutor: ShellExecutor
+ private val context: Context,
+ shellInit: ShellInit,
+ private val shellController: ShellController,
+ private val displayController: DisplayController,
+ private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val syncQueue: SyncTransactionQueue,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val transitions: Transitions,
+ private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ @ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
private val desktopMode: DesktopModeImpl
+ private var visualIndicator: DesktopModeVisualIndicator? = null
init {
desktopMode = DesktopModeImpl()
@@ -298,6 +306,52 @@
}
/**
+ * Perform checks required on drag move. Create/release fullscreen indicator as needed.
+ *
+ * @param taskInfo the task being dragged.
+ * @param taskSurface SurfaceControl of dragged task.
+ * @param y coordinate of dragged task. Used for checks against status bar height.
+ */
+ fun onDragPositioningMove(
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ y: Float
+ ) {
+ val statusBarHeight = displayController
+ .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+ if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ if (y <= statusBarHeight && visualIndicator == null) {
+ visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+ displayController, context, taskSurface, shellTaskOrganizer,
+ rootTaskDisplayAreaOrganizer)
+ visualIndicator?.createFullscreenIndicator()
+ } else if (y > statusBarHeight && visualIndicator != null) {
+ visualIndicator?.releaseFullscreenIndicator()
+ visualIndicator = null
+ }
+ }
+ }
+
+ /**
+ * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+ *
+ * @param taskInfo the task being dragged.
+ * @param y height of drag, to be checked against status bar height.
+ */
+ fun onDragPositioningEnd(
+ taskInfo: RunningTaskInfo,
+ y: Float
+ ) {
+ val statusBarHeight = displayController
+ .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+ if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ moveToFullscreen(taskInfo.taskId)
+ visualIndicator?.releaseFullscreenIndicator()
+ visualIndicator = null
+ }
+ }
+
+ /**
* Adds a listener to find out about changes in the visibility of freeform tasks.
*
* @param listener the listener to add.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 52f5a8c..c19d543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -536,6 +537,15 @@
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
return;
}
+ if (mSplitScreenOptional.isPresent()) {
+ // If pip activity will reparent to origin task case and if the origin task still under
+ // split root, just exit split screen here to ensure it could expand to fullscreen.
+ SplitScreenController split = mSplitScreenOptional.get();
+ if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+ split.exitSplitScreen(INVALID_TASK_ID,
+ SplitScreenController.EXIT_REASON_APP_FINISHED);
+ }
+ }
mSyncTransactionQueue.queue(wct);
mSyncTransactionQueue.runInSync(t -> {
// Make sure to grab the latest source hint rect as it could have been
@@ -1479,9 +1489,13 @@
applyFinishBoundsResize(wct, direction, false);
}
} else {
- final boolean isPipTopLeft =
- direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft();
- applyFinishBoundsResize(wct, direction, isPipTopLeft);
+ applyFinishBoundsResize(wct, direction, isPipToTopLeft());
+ // Use sync transaction to apply finish transaction for enter split case.
+ if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+ mSyncTransactionQueue.runInSync(t -> {
+ t.merge(tx);
+ });
+ }
}
finishResizeForMenu(destinationBounds);
@@ -1518,7 +1532,10 @@
mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
wct.setBounds(mToken, taskBounds);
- wct.setBoundsChangeTransaction(mToken, tx);
+ // Pip to split should use sync transaction to sync split bounds change.
+ if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
+ wct.setBoundsChangeTransaction(mToken, tx);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 748f4a19..582616d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -163,6 +163,10 @@
this::onKeepClearAreasChangedCallback;
private void onKeepClearAreasChangedCallback() {
+ if (mIsKeyguardShowingOrAnimating) {
+ // early bail out if the change was caused by keyguard showing up
+ return;
+ }
if (!mEnablePipKeepClearAlgorithm) {
// early bail out if the keep clear areas feature is disabled
return;
@@ -188,6 +192,10 @@
// early bail out if the keep clear areas feature is disabled
return;
}
+ if (mIsKeyguardShowingOrAnimating) {
+ // early bail out if the change was caused by keyguard showing up
+ return;
+ }
// only move if we're in PiP or transitioning into PiP
if (!mPipTransitionState.shouldBlockResizeRequest()) {
Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
@@ -639,9 +647,11 @@
DisplayLayout pendingLayout = mDisplayController
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId());
if (mIsInFixedRotation
+ || mIsKeyguardShowingOrAnimating
|| pendingLayout.rotation()
!= mPipBoundsState.getDisplayLayout().rotation()) {
- // bail out if there is a pending rotation or fixed rotation change
+ // bail out if there is a pending rotation or fixed rotation change or
+ // there's a keyguard present
return;
}
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
@@ -936,10 +946,10 @@
mPipBoundsState.getDisplayBounds().right,
mPipBoundsState.getDisplayBounds().bottom);
mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
- updatePipPositionForKeepClearAreas();
} else {
mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
}
+ updatePipPositionForKeepClearAreas();
}
private void setLauncherAppIconSize(int iconSizePx) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 979b7c7..167c032 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -282,7 +282,8 @@
public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent()
- && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
+ && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground(
+ taskInfo.taskId);
mFocusedTaskAllowSplitScreen = isSplitScreen
|| (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& taskInfo.supportsMultiWindow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index 23988a6..a7171fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -212,24 +212,25 @@
*/
@Override
public Size getSizeForAspectRatio(Size size, float aspectRatio) {
- // getting the percentage of the max size that current size takes
float currAspectRatio = (float) size.getWidth() / size.getHeight();
+
+ // getting the percentage of the max size that current size takes
Size currentMaxSize = getMaxSize(currAspectRatio);
float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
// getting the max size for the target aspect ratio
Size updatedMaxSize = getMaxSize(aspectRatio);
- int width = (int) (updatedMaxSize.getWidth() * currentPercent);
- int height = (int) (updatedMaxSize.getHeight() * currentPercent);
+ int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
+ int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
// adjust the dimensions if below allowed min edge size
if (width < getMinEdgeSize() && aspectRatio <= 1) {
width = getMinEdgeSize();
- height = (int) (width / aspectRatio);
+ height = Math.round(width / aspectRatio);
} else if (height < getMinEdgeSize() && aspectRatio > 1) {
height = getMinEdgeSize();
- width = (int) (height * aspectRatio);
+ width = Math.round(height * aspectRatio);
}
// reduce the dimensions of the updated size to the calculated percentage
@@ -366,7 +367,7 @@
mPipDisplayLayoutState = pipDisplayLayoutState;
boolean enablePipSizeLargeScreen = SystemProperties
- .getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
+ .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true);
// choose between two implementations of size spec logic
if (enablePipSizeLargeScreen) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index da8c805..db75be7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -237,12 +237,14 @@
}
void cancel(boolean toHome) {
- if (mFinishCB != null && mListener != null) {
+ if (mListener != null) {
try {
mListener.onAnimationCanceled(null, null);
} catch (RemoteException e) {
Slog.e(TAG, "Error canceling recents animation", e);
}
+ }
+ if (mFinishCB != null) {
finish(toHome, false /* userLeave */);
} else {
cleanUp();
@@ -324,10 +326,9 @@
mRecentsTaskId = taskInfo.taskId;
}
}
- if (mRecentsTask == null || !hasPausingTasks) {
+ if (mRecentsTask == null && !hasPausingTasks) {
// Recents is already running apparently, so this is a no-op.
- Slog.e(TAG, "Tried to start recents while it is already running. recents="
- + mRecentsTask);
+ Slog.e(TAG, "Tried to start recents while it is already running.");
cleanUp();
return false;
}
@@ -413,12 +414,14 @@
boolean hasChangingApp = false;
final TransitionUtil.LeafTaskFilter leafTaskFilter =
new TransitionUtil.LeafTaskFilter();
+ boolean hasTaskChange = false;
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ hasTaskChange = hasTaskChange || taskInfo != null;
final boolean isLeafTask = leafTaskFilter.test(change);
if (TransitionUtil.isOpeningType(change.getMode())) {
- if (mRecentsTask.equals(change.getContainer())) {
+ if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
recentsOpening = change;
} else if (isLeafTask) {
if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
@@ -431,7 +434,7 @@
openingTasks.add(change);
}
} else if (TransitionUtil.isClosingType(change.getMode())) {
- if (mRecentsTask.equals(change.getContainer())) {
+ if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
foundRecentsClosing = true;
} else if (isLeafTask) {
if (closingTasks == null) {
@@ -519,7 +522,12 @@
didMergeThings = true;
mState = STATE_NEW_TASK;
}
- if (!didMergeThings) {
+ if (!hasTaskChange) {
+ // Activity only transition, so consume the merge as it doesn't affect the rest of
+ // recents.
+ Slog.d(TAG, "Got an activity only transition during recents, so apply directly");
+ mergeActivityOnly(info, t);
+ } else if (!didMergeThings) {
// Didn't recognize anything in incoming transition so don't merge it.
Slog.w(TAG, "Don't know how to merge this transition.");
return;
@@ -537,6 +545,19 @@
}
}
+ /** For now, just set-up a jump-cut to the new activity. */
+ private void mergeActivityOnly(TransitionInfo info, SurfaceControl.Transaction t) {
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ t.show(change.getLeash());
+ t.setAlpha(change.getLeash(), 1.f);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ t.hide(change.getLeash());
+ }
+ }
+ }
+
@Override
public TaskSnapshot screenshotTask(int taskId) {
try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 94b9e90..7d5ab84 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,7 +31,6 @@
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -89,7 +88,6 @@
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -329,9 +327,14 @@
return mTaskOrganizer.getRunningTaskInfo(taskId);
}
+ /** Check task is under split or not by taskId. */
public boolean isTaskInSplitScreen(int taskId) {
- return isSplitScreenVisible()
- && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+ return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
+ }
+
+ /** Check split is foreground and task is under split or not by taskId. */
+ public boolean isTaskInSplitScreenForeground(int taskId) {
+ return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
}
public @SplitPosition int getSplitPosition(int taskId) {
@@ -339,8 +342,7 @@
}
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
- return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
- new WindowContainerTransaction());
+ return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction());
}
/**
@@ -351,13 +353,13 @@
mStageCoordinator.updateSurfaces(transaction);
}
- private boolean moveToStage(int taskId, @StageType int stageType,
- @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+ private boolean moveToStage(int taskId, @SplitPosition int stagePosition,
+ WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task == null) {
throw new IllegalArgumentException("Unknown taskId" + taskId);
}
- return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct);
+ return mStageCoordinator.moveToStage(task, stagePosition, wct);
}
public boolean removeFromSideStage(int taskId) {
@@ -382,10 +384,9 @@
}
public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
- final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
final int stagePosition =
leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
- moveToStage(taskId, stageType, stagePosition, wct);
+ moveToStage(taskId, stagePosition, wct);
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index a1eaf85..33cbdac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -373,56 +373,43 @@
return STAGE_TYPE_UNDEFINED;
}
- boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType,
- @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+ boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
+ WindowContainerTransaction wct) {
StageTaskListener targetStage;
int sideStagePosition;
- if (stageType == STAGE_TYPE_MAIN) {
- targetStage = mMainStage;
- sideStagePosition = reverseSplitPosition(stagePosition);
- } else if (stageType == STAGE_TYPE_SIDE) {
+ if (isSplitScreenVisible()) {
+ // If the split screen is foreground, retrieves target stage based on position.
+ targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
+ sideStagePosition = mSideStagePosition;
+ } else {
targetStage = mSideStage;
sideStagePosition = stagePosition;
- } else {
- if (isSplitScreenVisible()) {
- // If the split screen is activated, retrieves target stage based on position.
- targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
- sideStagePosition = mSideStagePosition;
- } else {
- // Exit split if it running background.
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
-
- targetStage = mSideStage;
- sideStagePosition = stagePosition;
- }
}
if (!isSplitActive()) {
- // prevent the fling divider to center transitioni if split screen didn't active.
- mIsDropEntering = true;
+ mSplitLayout.init();
+ prepareEnterSplitScreen(wct, task, stagePosition);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ });
+ } else {
+ setSideStagePosition(sideStagePosition, wct);
+ targetStage.addTask(task, wct);
+ targetStage.evictAllChildren(wct);
+ if (!isSplitScreenVisible()) {
+ final StageTaskListener anotherStage = targetStage == mMainStage
+ ? mSideStage : mMainStage;
+ anotherStage.reparentTopTask(wct);
+ anotherStage.evictAllChildren(wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ }
+ setRootForceTranslucent(false, wct);
+ mSyncQueue.queue(wct);
}
- setSideStagePosition(sideStagePosition, wct);
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- targetStage.evictAllChildren(evictWct);
-
- // Apply surface bounds before animation start.
- SurfaceControl.Transaction startT = mTransactionPool.acquire();
- if (startT != null) {
- updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
- startT.apply();
- mTransactionPool.release(startT);
- }
- // reparent the task to an invisible split root will make the activity invisible. Reorder
- // the root task to front to make the entering transition from pip to split smooth.
- wct.reorder(mRootTaskInfo.token, true);
- wct.reorder(targetStage.mRootTaskInfo.token, true);
- targetStage.addTask(task, wct);
-
- if (!evictWct.isEmpty()) {
- wct.merge(evictWct, true /* transfer */);
- }
- mTaskOrganizer.applyTransaction(wct);
+ // Due to drag already pip task entering split by this method so need to reset flag here.
+ mIsDropEntering = false;
return true;
}
@@ -1348,7 +1335,7 @@
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- setRootForceTranslucent(true, wct);
+ setRootForceTranslucent(true, finishedWCT);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a841b7f..d6f4d6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -220,12 +220,20 @@
mCallbacks.onNoLongerSupportMultiWindow();
return;
}
- mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ if (taskInfo.topActivity == null && mChildrenTaskInfo.contains(taskInfo.taskId)
+ && mChildrenTaskInfo.get(taskInfo.taskId).topActivity != null) {
+ // If top activity become null, it means the task is about to vanish, we use this
+ // signal to remove it from children list earlier for smooth dismiss transition.
+ mChildrenTaskInfo.remove(taskInfo.taskId);
+ mChildrenLeashes.remove(taskInfo.taskId);
+ } else {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ }
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
taskInfo.isVisible);
- if (!ENABLE_SHELL_TRANSITIONS) {
- updateChildTaskSurface(
- taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ if (!ENABLE_SHELL_TRANSITIONS && mChildrenLeashes.contains(taskInfo.taskId)) {
+ updateChildTaskSurface(taskInfo, mChildrenLeashes.get(taskInfo.taskId),
+ false /* firstAppeared */);
}
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index aa1e6ed..586cab0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -303,13 +303,18 @@
info.getChanges().remove(i);
}
}
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ --mixed.mInFlightSubAnimations;
+ mixed.joinFinishArgs(wct, wctCB);
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+ };
if (pipChange == null) {
if (mixed.mLeftoversHandler != null) {
+ mixed.mInFlightSubAnimations = 1;
if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
- info, startTransaction, finishTransaction, (wct, wctCB) -> {
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(wct, wctCB);
- })) {
+ info, startTransaction, finishTransaction, finishCB)) {
return true;
}
}
@@ -318,13 +323,6 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
+ " animation because remote-animation likely doesn't support it");
- Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
- --mixed.mInFlightSubAnimations;
- mixed.joinFinishArgs(wct, wctCB);
- if (mixed.mInFlightSubAnimations > 0) return;
- mActiveTransitions.remove(mixed);
- finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
- };
// Split the transition into 2 parts: the pip part and the rest.
mixed.mInFlightSubAnimations = 2;
// make a new startTransaction because pip's startEnterAnimation "consumes" it so
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index e643170..e632b56 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -31,7 +31,6 @@
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.ColorSpace;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -162,13 +161,12 @@
.setName("RotationLayer")
.build();
- final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
+ TransitionAnimation.configureScreenshotLayer(t, mScreenshotLayer, screenshotBuffer);
final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- t.setDataSpace(mScreenshotLayer, colorSpace.getDataSpace());
- t.setBuffer(mScreenshotLayer, hardwareBuffer);
t.show(mScreenshotLayer);
if (!isCustomRotate()) {
- mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, colorSpace);
+ mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
+ screenshotBuffer.getColorSpace());
}
hardwareBuffer.close();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 039bde9..b8b6d5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -534,7 +534,7 @@
}
if (info.getType() == TRANSIT_SLEEP) {
- if (activeIdx > 0) {
+ if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) {
// Sleep starts a process of forcing all prior transitions to finish immediately
finishForSleep(null /* forceFinish */);
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
index 86ca292..fe0a3fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -79,7 +79,7 @@
}
private float[] getBackgroundColor(Context context) {
- int colorInt = context.getResources().getColor(R.color.taskbar_background);
+ int colorInt = context.getResources().getColor(R.color.unfold_background);
return new float[]{
(float) red(colorInt) / 255.0F,
(float) green(colorInt) / 255.0F,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 6b45149..317b9a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -166,7 +166,6 @@
}
decoration.relayout(taskInfo);
- setupCaptionColor(taskInfo, decoration);
}
@Override
@@ -252,7 +251,7 @@
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
- } else if (id == R.id.caption_handle) {
+ } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
@@ -262,7 +261,6 @@
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
- decoration.setButtonVisibility(false);
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
}
@@ -302,7 +300,11 @@
return false;
}
case MotionEvent.ACTION_MOVE: {
+ final DesktopModeWindowDecoration decoration =
+ mWindowDecorByTaskId.get(mTaskId);
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+ decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mIsDragging = true;
@@ -311,18 +313,10 @@
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- final int statusBarHeight = mDisplayController
- .getDisplayLayout(taskInfo.displayId).stableInsets().top;
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
- if (DesktopModeStatus.isProto2Enabled()
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- // Switch a single task to fullscreen
- mDesktopTasksController.ifPresent(
- c -> c.moveToFullscreen(taskInfo));
- }
- }
+ mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
+ e.getRawY(dragPointerIdx)));
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
@@ -556,13 +550,6 @@
}
}
- private void setupCaptionColor(RunningTaskInfo taskInfo,
- DesktopModeWindowDecoration decoration) {
- if (taskInfo == null || taskInfo.taskDescription == null) return;
- final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
- decoration.setCaptionColor(statusBarColor);
- }
-
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.isProto2Enabled()
@@ -602,7 +589,6 @@
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
- setupCaptionColor(taskInfo, windowDecoration);
incrementEventReceiverTasks(taskInfo.displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 3c0ef96..6478fe7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -22,15 +22,10 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.util.Log;
import android.view.Choreographer;
@@ -38,6 +33,7 @@
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.window.WindowContainerTransaction;
@@ -48,20 +44,24 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
+import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link DesktopModeWindowDecorViewModel}. The caption bar contains a handle, back button, and
- * close button.
+ * {@link DesktopModeWindowDecorViewModel}.
*
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
+
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
+ private DesktopModeWindowDecorationViewHolder mWindowDecorViewHolder;
private View.OnClickListener mOnCaptionButtonClickListener;
private View.OnTouchListener mOnCaptionTouchListener;
private DragPositioningCallback mDragPositioningCallback;
@@ -70,10 +70,11 @@
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final int mCaptionMenuHeightId = R.dimen.freeform_decor_caption_menu_height;
+ private final int mCaptionMenuHeightWithoutWindowingControlsId =
+ R.dimen.freeform_decor_caption_menu_height_no_windowing_controls;
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
- private boolean mDesktopActive;
private AdditionalWindow mHandleMenu;
private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
private final int mHandleMenuShadowRadiusId = R.dimen.caption_menu_shadow_radius;
@@ -94,7 +95,6 @@
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
- mDesktopActive = DesktopModeStatus.isActive(mContext);
}
@Override
@@ -152,9 +152,11 @@
final int outsetRightId = R.dimen.freeform_resize_handle;
final int outsetBottomId = R.dimen.freeform_resize_handle;
+ final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
+ taskInfo.getWindowingMode());
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
+ mRelayoutParams.mLayoutResId = windowDecorLayoutId;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
if (isDragResizeable) {
@@ -172,24 +174,28 @@
return;
}
if (oldRootView != mResult.mRootView) {
- setupRootView();
- }
-
- // If this task is not focused, do not show caption.
- setCaptionVisibility(mTaskInfo.isFocused);
-
- if (mTaskInfo.isFocused) {
- if (DesktopModeStatus.isProto2Enabled()) {
- updateButtonVisibility();
- } else if (DesktopModeStatus.isProto1Enabled()) {
- // Only handle should show if Desktop Mode is inactive.
- boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
- if (mDesktopActive != desktopCurrentStatus) {
- mDesktopActive = desktopCurrentStatus;
- setButtonVisibility(mDesktopActive);
- }
+ if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) {
+ mWindowDecorViewHolder = new DesktopModeFocusedWindowDecorationViewHolder(
+ mResult.mRootView,
+ mOnCaptionTouchListener,
+ mOnCaptionButtonClickListener
+ );
+ } else if (mRelayoutParams.mLayoutResId
+ == R.layout.desktop_mode_app_controls_window_decor) {
+ mWindowDecorViewHolder = new DesktopModeAppControlsWindowDecorationViewHolder(
+ mResult.mRootView,
+ mOnCaptionTouchListener,
+ mOnCaptionButtonClickListener
+ );
+ } else {
+ throw new IllegalArgumentException("Unexpected layout resource id");
}
}
+ mWindowDecorViewHolder.bindData(mTaskInfo);
+
+ if (!mTaskInfo.isFocused) {
+ closeHandleMenu();
+ }
if (!isDragResizeable) {
closeDragResizeListener();
@@ -219,34 +225,14 @@
mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
}
- /**
- * Sets up listeners when a new root view is created.
- */
- private void setupRootView() {
- final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- caption.setOnTouchListener(mOnCaptionTouchListener);
- final View handle = caption.findViewById(R.id.caption_handle);
- handle.setOnTouchListener(mOnCaptionTouchListener);
- handle.setOnClickListener(mOnCaptionButtonClickListener);
- if (DesktopModeStatus.isProto1Enabled()) {
- final View back = caption.findViewById(R.id.back_button);
- back.setOnClickListener(mOnCaptionButtonClickListener);
- final View close = caption.findViewById(R.id.close_window);
- close.setOnClickListener(mOnCaptionButtonClickListener);
- }
- updateButtonVisibility();
- }
-
private void setupHandleMenu() {
final View menu = mHandleMenu.mWindowViewHost.getView();
final View fullscreen = menu.findViewById(R.id.fullscreen_button);
fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
final View desktop = menu.findViewById(R.id.desktop_button);
- if (DesktopModeStatus.isProto2Enabled()) {
- desktop.setOnClickListener(mOnCaptionButtonClickListener);
- } else if (DesktopModeStatus.isProto1Enabled()) {
- desktop.setVisibility(View.GONE);
- }
+ desktop.setOnClickListener(mOnCaptionButtonClickListener);
+ final ViewGroup windowingBtns = menu.findViewById(R.id.windowing_mode_buttons);
+ windowingBtns.setVisibility(DesktopModeStatus.isProto1Enabled() ? View.GONE : View.VISIBLE);
final View split = menu.findViewById(R.id.split_screen_button);
split.setOnClickListener(mOnCaptionButtonClickListener);
final View close = menu.findViewById(R.id.close_button);
@@ -255,98 +241,26 @@
collapse.setOnClickListener(mOnCaptionButtonClickListener);
menu.setOnTouchListener(mOnCaptionTouchListener);
- String packageName = mTaskInfo.baseActivity.getPackageName();
- PackageManager pm = mContext.getApplicationContext().getPackageManager();
- // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
- try {
- ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
- PackageManager.ApplicationInfoFlags.of(0));
- final ImageView appIcon = menu.findViewById(R.id.application_icon);
- appIcon.setImageDrawable(pm.getApplicationIcon(applicationInfo));
- final TextView appName = menu.findViewById(R.id.application_name);
- appName.setText(pm.getApplicationLabel(applicationInfo));
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Package not found: " + packageName, e);
- }
- }
-
- /**
- * Sets caption visibility based on task focus.
- * Note: Only applicable to Desktop Proto 1; Proto 2 only closes handle menu on focus loss
- * @param visible whether or not the caption should be visible
- */
- private void setCaptionVisibility(boolean visible) {
- if (!visible) closeHandleMenu();
- if (!DesktopModeStatus.isProto1Enabled()) return;
- final int v = visible ? View.VISIBLE : View.GONE;
- final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- captionView.setVisibility(v);
-
- }
-
- /**
- * Sets the visibility of buttons and color of caption based on desktop mode status
- */
- void updateButtonVisibility() {
- if (DesktopModeStatus.isProto2Enabled()) {
- setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
- } else if (DesktopModeStatus.isProto1Enabled()) {
- mDesktopActive = DesktopModeStatus.isActive(mContext);
- setButtonVisibility(mDesktopActive);
- }
- }
-
- /**
- * Show or hide buttons
- */
- void setButtonVisibility(boolean visible) {
- final int visibility = visible && DesktopModeStatus.isProto1Enabled()
- ? View.VISIBLE : View.GONE;
- final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- final View back = caption.findViewById(R.id.back_button);
- final View close = caption.findViewById(R.id.close_window);
- back.setVisibility(visibility);
- close.setVisibility(visibility);
- final int buttonTintColorRes =
- mDesktopActive ? R.color.decor_button_dark_color
- : R.color.decor_button_light_color;
- final ColorStateList buttonTintColor =
- caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
- final View handle = caption.findViewById(R.id.caption_handle);
- final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
- handleBackground.setTintList(buttonTintColor);
+ final ImageView appIcon = menu.findViewById(R.id.application_icon);
+ final TextView appName = menu.findViewById(R.id.application_name);
+ loadAppInfo(appName, appIcon);
}
boolean isHandleMenuActive() {
return mHandleMenu != null;
}
- void setCaptionColor(int captionColor) {
- if (mResult.mRootView == null) {
- return;
- }
-
- final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
- final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
- captionDrawable.setColor(captionColor);
-
- final int buttonTintColorRes =
- Color.valueOf(captionColor).luminance() < 0.5
- ? R.color.decor_button_light_color
- : R.color.decor_button_dark_color;
- final ColorStateList buttonTintColor =
- caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
-
- final View handle = caption.findViewById(R.id.caption_handle);
- final Drawable handleBackground = handle.getBackground();
- handleBackground.setTintList(buttonTintColor);
- if (DesktopModeStatus.isProto1Enabled()) {
- final View back = caption.findViewById(R.id.back_button);
- final Drawable backBackground = back.getBackground();
- backBackground.setTintList(buttonTintColor);
- final View close = caption.findViewById(R.id.close_window);
- final Drawable closeBackground = close.getBackground();
- closeBackground.setTintList(buttonTintColor);
+ private void loadAppInfo(TextView appNameTextView, ImageView appIconImageView) {
+ String packageName = mTaskInfo.realActivity.getPackageName();
+ PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ try {
+ // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
+ ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
+ PackageManager.ApplicationInfoFlags.of(0));
+ appNameTextView.setText(pm.getApplicationLabel(applicationInfo));
+ appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package not found: " + packageName, e);
}
}
@@ -367,15 +281,26 @@
final int captionWidth = mTaskInfo.getConfiguration()
.windowConfiguration.getBounds().width();
final int menuWidth = loadDimensionPixelSize(resources, mHandleMenuWidthId);
- final int menuHeight = loadDimensionPixelSize(resources, mCaptionMenuHeightId);
+ // The windowing controls are disabled in proto1.
+ final int menuHeight = loadDimensionPixelSize(resources, DesktopModeStatus.isProto1Enabled()
+ ? mCaptionMenuHeightWithoutWindowingControlsId : mCaptionMenuHeightId);
final int shadowRadius = loadDimensionPixelSize(resources, mHandleMenuShadowRadiusId);
final int cornerRadius = loadDimensionPixelSize(resources, mHandleMenuCornerRadiusId);
- final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
- - mResult.mDecorContainerOffsetX;
- final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+ final int x, y;
+ if (mRelayoutParams.mLayoutResId
+ == R.layout.desktop_mode_app_controls_window_decor) {
+ // Align the handle menu to the left of the caption.
+ x = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX;
+ y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+ } else {
+ // Position the handle menu at the center of the caption.
+ x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
+ - mResult.mDecorContainerOffsetX;
+ y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+ }
mHandleMenuPosition.set(x, y);
- String namePrefix = "Caption Menu";
+ final String namePrefix = "Caption Menu";
mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
menuWidth, menuHeight, shadowRadius, cornerRadius);
mSyncQueue.runInSync(transaction -> {
@@ -503,6 +428,15 @@
super.close();
}
+ private int getDesktopModeWindowDecorLayoutId(int windowingMode) {
+ if (DesktopModeStatus.isProto1Enabled()) {
+ return R.layout.desktop_mode_app_controls_window_decor;
+ }
+ return windowingMode == WINDOWING_MODE_FREEFORM
+ ? R.layout.desktop_mode_app_controls_window_decor
+ : R.layout.desktop_mode_focused_window_decor;
+ }
+
static class Factory {
DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ddd3b44..31e93ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -84,6 +84,7 @@
};
RunningTaskInfo mTaskInfo;
+ int mLayoutResId;
final SurfaceControl mTaskSurface;
Display mDisplay;
@@ -162,6 +163,8 @@
if (params.mRunningTaskInfo != null) {
mTaskInfo = params.mRunningTaskInfo;
}
+ final int oldLayoutResId = mLayoutResId;
+ mLayoutResId = params.mLayoutResId;
if (!mTaskInfo.isVisible) {
releaseViews();
@@ -178,7 +181,8 @@
final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|| mDisplay == null
- || mDisplay.getDisplayId() != mTaskInfo.displayId) {
+ || mDisplay.getDisplayId() != mTaskInfo.displayId
+ || oldLayoutResId != mLayoutResId) {
releaseViews();
if (!obtainDisplayOrRegisterListener()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
new file mode 100644
index 0000000..95b5051
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -0,0 +1,94 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.pm.PackageManager
+import android.content.res.ColorStateList
+import android.graphics.drawable.GradientDrawable
+import android.util.Log
+import android.view.View
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.wm.shell.R
+
+/**
+ * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts
+ * finer controls such as a close window button and an "app info" section to pull up additional
+ * controls.
+ */
+internal class DesktopModeAppControlsWindowDecorationViewHolder(
+ rootView: View,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: View.OnClickListener
+) : DesktopModeWindowDecorationViewHolder(rootView) {
+
+ private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
+ private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
+ private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
+ private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
+ private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
+ private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
+ private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+
+ init {
+ captionView.setOnTouchListener(onCaptionTouchListener)
+ captionHandle.setOnTouchListener(onCaptionTouchListener)
+ openMenuButton.setOnClickListener(onCaptionButtonClickListener)
+ closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+ }
+
+ override fun bindData(taskInfo: RunningTaskInfo) {
+ bindAppInfo(taskInfo)
+
+ val captionDrawable = captionView.background as GradientDrawable
+ captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+
+ closeWindowButton.imageTintList = ColorStateList.valueOf(
+ getCaptionCloseButtonColor(taskInfo))
+ expandMenuButton.imageTintList = ColorStateList.valueOf(
+ getCaptionExpandButtonColor(taskInfo))
+ appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
+ }
+
+ private fun bindAppInfo(taskInfo: RunningTaskInfo) {
+ val packageName: String = taskInfo.realActivity.packageName
+ val pm: PackageManager = context.applicationContext.packageManager
+ try {
+ // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
+ val applicationInfo = pm.getApplicationInfo(packageName,
+ PackageManager.ApplicationInfoFlags.of(0))
+ appNameTextView.text = pm.getApplicationLabel(applicationInfo)
+ appIconImageView.setImageDrawable(pm.getApplicationIcon(applicationInfo))
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Package not found: $packageName", e)
+ }
+ }
+
+ private fun getCaptionAppNameTextColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_app_name_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_app_name_dark)
+ }
+ }
+
+ private fun getCaptionCloseButtonColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_close_button_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_close_button_dark)
+ }
+ }
+
+ private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_expand_button_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_expand_button_dark)
+ }
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
new file mode 100644
index 0000000..47a12a0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -0,0 +1,44 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.res.ColorStateList
+import android.graphics.drawable.GradientDrawable
+import android.view.View
+import android.widget.ImageButton
+import com.android.wm.shell.R
+
+/**
+ * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen). It
+ * hosts a simple handle bar from which to initiate a drag motion to enter desktop mode.
+ */
+internal class DesktopModeFocusedWindowDecorationViewHolder(
+ rootView: View,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: View.OnClickListener
+) : DesktopModeWindowDecorationViewHolder(rootView) {
+
+ private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
+ private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+
+ init {
+ captionView.setOnTouchListener(onCaptionTouchListener)
+ captionHandle.setOnTouchListener(onCaptionTouchListener)
+ captionHandle.setOnClickListener(onCaptionButtonClickListener)
+ }
+
+ override fun bindData(taskInfo: RunningTaskInfo) {
+ val captionColor = taskInfo.taskDescription.statusBarColor
+ val captionDrawable = captionView.background as GradientDrawable
+ captionDrawable.setColor(captionColor)
+
+ captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
+ }
+
+ private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
+ return if (shouldUseLightCaptionColors(taskInfo)) {
+ context.getColor(R.color.desktop_mode_caption_handle_bar_light)
+ } else {
+ context.getColor(R.color.desktop_mode_caption_handle_bar_dark)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
new file mode 100644
index 0000000..514ea52
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -0,0 +1,28 @@
+package com.android.wm.shell.windowdecor.viewholder
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.graphics.Color
+import android.view.View
+
+/**
+ * Encapsulates the root [View] of a window decoration and its children to facilitate looking up
+ * children (via findViewById) and updating to the latest data from [RunningTaskInfo].
+ */
+internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
+ val context: Context = rootView.context
+
+ /**
+ * A signal to the view holder that new data is available and that the views should be updated
+ * to reflect it.
+ */
+ abstract fun bindData(taskInfo: RunningTaskInfo)
+
+ /**
+ * Whether the caption items should use the 'light' color variant so that there's good contrast
+ * with the caption background color.
+ */
+ protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+ return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index ffdb87f..5b06c9c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -79,7 +79,8 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
+ appExistAtStart = false)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 792e2b0..c840183 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -82,7 +82,8 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, sendNotificationApp,
+ fromOtherApp = false)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 8b025cd..919bf06 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -185,7 +185,8 @@
Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
appBubbleIntent.setPackage(mContext.getPackageName());
- mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor);
+ mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mock(Icon.class),
+ mMainExecutor);
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index bc0d93a..a6501f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -54,7 +54,6 @@
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
similarity index 91%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index a58620d..172c263 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -58,9 +58,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLayout = (LetterboxEduDialogLayout)
- LayoutInflater.from(mContext).inflate(R.layout.letterbox_education_dialog_layout,
- null);
+ mLayout = (LetterboxEduDialogLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.letterbox_education_dialog_layout, null);
mDismissButton = mLayout.findViewById(R.id.letterbox_education_dialog_dismiss_button);
mDialogContainer = mLayout.findViewById(R.id.letterbox_education_dialog_container);
mLayout.setDismissOnClickListener(mDismissCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 14190f1..47c9e06 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -56,7 +56,6 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.DialogAnimationController;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
@@ -400,15 +399,16 @@
false, isDocked);
}
- private LetterboxEduWindowManager createWindowManager(boolean eligible,
- int userId, boolean isTaskbarEduShowing) {
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId,
+ boolean isTaskbarEduShowing) {
return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false);
}
- private LetterboxEduWindowManager createWindowManager(boolean eligible,
- int userId, boolean isTaskbarEduShowing, boolean isDocked) {
+ private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId,
+ boolean isTaskbarEduShowing, boolean isDocked) {
doReturn(isDocked).when(mDockStateReader).isDocked();
- LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
+ LetterboxEduWindowManager
+ windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
createDisplayLayout(), mTransitions, mOnDismissCallback,
mAnimationController, mDockStateReader);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 43f8f7b..63de74f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -41,6 +41,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
@@ -418,6 +419,17 @@
assertThat(wct).isNotNull();
}
+ @Test
+ public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() {
+ RunningTaskInfo trigger = new RunningTaskInfo();
+ trigger.token = new MockToken().token();
+ trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mController.handleRequest(
+ mock(IBinder.class),
+ new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
+ verifyZeroInteractions(mTransitions);
+ }
+
private DesktopModeController createController() {
return new DesktopModeController(mContext, mShellInit, mShellController,
mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 95e78a8..5cad50d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -37,11 +37,14 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -73,7 +76,10 @@
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var shellController: ShellController
+ @Mock lateinit var displayController: DisplayController
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var syncQueue: SyncTransactionQueue
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
lateinit var mockitoSession: StaticMockitoSession
@@ -105,7 +111,10 @@
context,
shellInit,
shellController,
+ displayController,
shellTaskOrganizer,
+ syncQueue,
+ rootTaskDisplayAreaOrganizer,
transitions,
desktopModeTaskRepository,
TestShellExecutor()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2e2e49e..eda6fdc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -145,39 +145,48 @@
}
@Test
- public void testMoveToStage() {
+ public void testMoveToStage_splitActiveBackground() {
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
+
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
- mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
- assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
-
- mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mSideStage).addTask(eq(task), eq(wct));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
}
@Test
- public void testMoveToUndefinedStage() {
- final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
-
- // Verify move to undefined stage while split screen not activated moves task to side stage.
- when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false);
- mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
- mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- new WindowContainerTransaction());
- verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
- assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
-
- // Verify move to undefined stage after split screen activated moves task based on position.
+ public void testMoveToStage_splitActiveForeground() {
+ when(mStageCoordinator.isSplitActive()).thenReturn(true);
when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
- assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
- mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
- new WindowContainerTransaction());
- verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
- assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
+ // Assume current side stage is top or left.
+ mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mMainStage).addTask(eq(task), eq(wct));
+ assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct);
+ verify(mSideStage).addTask(eq(task), eq(wct));
+ assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
+ assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
+ }
+
+ @Test
+ public void testMoveToStage_splitInctive() {
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
}
@Test
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 41ced8c..139cdde 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -54,6 +54,7 @@
// collection
bool purgeScratchOnly = true;
// EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+ // WARNING: Enabling this option can lead to instability, see b/266626090
bool releaseContextOnStoppedOnly = false;
};
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 28d78b4..914266d 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -579,17 +579,9 @@
LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid);
jobject params;
- if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) {
- params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
- transferParams.a, transferParams.b, transferParams.c,
- transferParams.d, transferParams.e, transferParams.f,
- transferParams.g, true);
- } else {
- params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
- transferParams.a, transferParams.b, transferParams.c,
- transferParams.d, transferParams.e, transferParams.f,
- transferParams.g, false);
- }
+ params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+ transferParams.a, transferParams.b, transferParams.c, transferParams.d,
+ transferParams.e, transferParams.f, transferParams.g);
jfloatArray xyzArray = env->NewFloatArray(9);
jfloat xyz[9] = {
@@ -817,7 +809,7 @@
gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
"android/graphics/ColorSpace$Rgb$TransferParameters"));
gTransferParameters_constructorMethodID =
- GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDDZ)V");
+ GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDD)V");
gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8ab7159..8efd180 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6251,6 +6251,7 @@
* Volume behavior for an audio device where no software attenuation is applied, and
* the volume is kept synchronized between the host and the device itself through a
* device-specific protocol such as BT AVRCP.
+ * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
*/
@SystemApi
public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3;
@@ -6261,6 +6262,7 @@
* device-specific protocol (such as for hearing aids), based on the audio mode (e.g.
* normal vs in phone call).
* @see #setMode(int)
+ * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int)
*/
@SystemApi
public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4;
@@ -6270,11 +6272,6 @@
* A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set
* the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have
* no effect, or an unreliable effect.
- *
- * {@link #DEVICE_VOLUME_BEHAVIOR_FULL} will be returned instead by
- * {@link #getDeviceVolumeBehavior} for target SDK versions before U.
- *
- * @see #RETURN_DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY
*/
@SystemApi
public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5;
@@ -6316,27 +6313,18 @@
public @interface AbsoluteDeviceVolumeBehavior {}
/**
- * Volume behaviors that can be set with {@link #setDeviceVolumeBehavior}.
* @hide
- */
- @IntDef({
- DEVICE_VOLUME_BEHAVIOR_VARIABLE,
- DEVICE_VOLUME_BEHAVIOR_FULL,
- DEVICE_VOLUME_BEHAVIOR_FIXED,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface SettableDeviceVolumeBehavior {}
-
- /**
- * @hide
- * Throws IAE on a non-settable volume behavior value
+ * Throws IAE on an invalid volume behavior value
* @param volumeBehavior behavior value to check
*/
- public static void enforceSettableVolumeBehavior(int volumeBehavior) {
+ public static void enforceValidVolumeBehavior(int volumeBehavior) {
switch (volumeBehavior) {
case DEVICE_VOLUME_BEHAVIOR_VARIABLE:
case DEVICE_VOLUME_BEHAVIOR_FULL:
case DEVICE_VOLUME_BEHAVIOR_FIXED:
+ case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
+ case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
+ case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY:
return;
default:
throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior);
@@ -6346,8 +6334,11 @@
/**
* @hide
* Sets the volume behavior for an audio output device.
- *
- * @see SettableDeviceVolumeBehavior
+ * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE
+ * @see #DEVICE_VOLUME_BEHAVIOR_FULL
+ * @see #DEVICE_VOLUME_BEHAVIOR_FIXED
+ * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
+ * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE
* @param device the device to be affected
* @param deviceVolumeBehavior one of the device behaviors
*/
@@ -6357,10 +6348,10 @@
Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
})
public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
- @SettableDeviceVolumeBehavior int deviceVolumeBehavior) {
+ @DeviceVolumeBehavior int deviceVolumeBehavior) {
// verify arguments (validity of device type is enforced in server)
Objects.requireNonNull(device);
- enforceSettableVolumeBehavior(deviceVolumeBehavior);
+ enforceValidVolumeBehavior(deviceVolumeBehavior);
// communicate with service
final IAudioService service = getService();
try {
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index 9b238e1..6744359 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -49,6 +49,7 @@
import libcore.io.IoUtils;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
@@ -255,17 +256,19 @@
// get orientation
if (MediaFile.isExifMimeType(mimeType)) {
- exif = new ExifInterface(file);
- switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- orientation = 90;
- break;
- case ExifInterface.ORIENTATION_ROTATE_180:
- orientation = 180;
- break;
- case ExifInterface.ORIENTATION_ROTATE_270:
- orientation = 270;
- break;
+ try (FileInputStream is = new FileInputStream(file)) {
+ exif = new ExifInterface(is.getFD());
+ switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ orientation = 90;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ orientation = 180;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ orientation = 270;
+ break;
+ }
}
}
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml
new file mode 100644
index 0000000..aafe466
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M7,20H4Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20V6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18H7ZM9,20V18.2Q8.55,17.775 8.275,17.225Q8,16.675 8,16Q8,15.325 8.275,14.775Q8.55,14.225 9,13.8V12H13V13.8Q13.45,14.225 13.725,14.775Q14,15.325 14,16Q14,16.675 13.725,17.225Q13.45,17.775 13,18.2V20ZM11,17.5Q11.65,17.5 12.075,17.075Q12.5,16.65 12.5,16Q12.5,15.35 12.075,14.925Q11.65,14.5 11,14.5Q10.35,14.5 9.925,14.925Q9.5,15.35 9.5,16Q9.5,16.65 9.925,17.075Q10.35,17.5 11,17.5ZM21,20H16Q15.575,20 15.288,19.712Q15,19.425 15,19V10Q15,9.575 15.288,9.287Q15.575,9 16,9H21Q21.425,9 21.712,9.287Q22,9.575 22,10V19Q22,19.425 21.712,19.712Q21.425,20 21,20ZM17,18H20V11H17Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml
index 97d201d..190e0a8 100644
--- a/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_glasses.xml
@@ -20,7 +20,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="@android:color/system_neutral1_200">
+ android:tint="@android:color/system_accent1_200">
<path
android:fillColor="@android:color/white"
android:pathData="M6.85,15Q7.625,15 8.238,14.55Q8.85,14.1 9.1,13.375L9.475,12.225Q9.875,11.025 9.275,10.012Q8.675,9 7.55,9H4.025L4.5,12.925Q4.625,13.8 5.287,14.4Q5.95,15 6.85,15ZM17.15,15Q18.05,15 18.712,14.4Q19.375,13.8 19.5,12.925L19.975,9H16.475Q15.35,9 14.75,10.025Q14.15,11.05 14.55,12.25L14.9,13.375Q15.15,14.1 15.762,14.55Q16.375,15 17.15,15ZM6.85,17Q5.2,17 3.963,15.912Q2.725,14.825 2.525,13.175L2,9H1V7H7.55Q8.65,7 9.562,7.537Q10.475,8.075 11,9H13.025Q13.55,8.075 14.463,7.537Q15.375,7 16.475,7H23V9H22L21.475,13.175Q21.275,14.825 20.038,15.912Q18.8,17 17.15,17Q15.725,17 14.588,16.188Q13.45,15.375 13,14.025L12.625,12.9Q12.575,12.725 12.525,12.537Q12.475,12.35 12.425,12H11.575Q11.525,12.3 11.475,12.487Q11.425,12.675 11.375,12.85L11,14Q10.55,15.35 9.413,16.175Q8.275,17 6.85,17Z"/>
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
index 1611861..78120a3d 100644
--- a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_nearby_devices.xml
@@ -19,7 +19,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@android:color/system_accent1_200">
<path android:fillColor="@android:color/system_accent1_200"
android:pathData="M12,16.4 L7.6,12 12,7.6 16.4,12ZM13.4,21.375Q13.125,21.65 12.75,21.8Q12.375,21.95 12,21.95Q11.625,21.95 11.25,21.8Q10.875,21.65 10.6,21.375L2.625,13.4Q2.35,13.125 2.2,12.75Q2.05,12.375 2.05,12Q2.05,11.625 2.2,11.25Q2.35,10.875 2.625,10.6L10.575,2.65Q10.875,2.35 11.238,2.2Q11.6,2.05 12,2.05Q12.4,2.05 12.762,2.2Q13.125,2.35 13.425,2.65L21.375,10.6Q21.65,10.875 21.8,11.25Q21.95,11.625 21.95,12Q21.95,12.375 21.8,12.75Q21.65,13.125 21.375,13.4ZM12,19.2 L19.2,12Q19.2,12 19.2,12Q19.2,12 19.2,12L12,4.8Q12,4.8 12,4.8Q12,4.8 12,4.8L4.8,12Q4.8,12 4.8,12Q4.8,12 4.8,12L12,19.2Q12,19.2 12,19.2Q12,19.2 12,19.2Z"/>
</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_watch.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_watch.xml
new file mode 100644
index 0000000..f1fda17
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_watch.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@android:color/system_accent1_200">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M9,22 L7.65,17.45Q6.45,16.5 5.725,15.075Q5,13.65 5,12Q5,10.35 5.725,8.925Q6.45,7.5 7.65,6.55L9,2H15L16.35,6.55Q17.55,7.5 18.275,8.925Q19,10.35 19,12Q19,13.65 18.275,15.075Q17.55,16.5 16.35,17.45L15,22ZM12,17Q14.075,17 15.538,15.537Q17,14.075 17,12Q17,9.925 15.538,8.462Q14.075,7 12,7Q9.925,7 8.463,8.462Q7,9.925 7,12Q7,14.075 8.463,15.537Q9.925,17 12,17ZM10.1,5.25Q11.075,4.975 12,4.975Q12.925,4.975 13.9,5.25L13.5,4H10.5ZM10.5,20H13.5L13.9,18.75Q12.925,19.025 12,19.025Q11.075,19.025 10.1,18.75ZM10.1,4H10.5H13.5H13.9Q12.925,4 12,4Q11.075,4 10.1,4ZM10.5,20H10.1Q11.075,20 12,20Q12.925,20 13.9,20H13.5Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
index 15f6987..dc12d12 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_device_other.xml
@@ -19,7 +19,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@android:color/system_accent1_600">
<path android:fillColor="@android:color/white"
android:pathData="M7,20H4Q3.175,20 2.588,19.413Q2,18.825 2,18V6Q2,5.175 2.588,4.588Q3.175,4 4,4H20V6H4Q4,6 4,6Q4,6 4,6V18Q4,18 4,18Q4,18 4,18H7ZM9,20V18.2Q8.55,17.775 8.275,17.225Q8,16.675 8,16Q8,15.325 8.275,14.775Q8.55,14.225 9,13.8V12H13V13.8Q13.45,14.225 13.725,14.775Q14,15.325 14,16Q14,16.675 13.725,17.225Q13.45,17.775 13,18.2V20ZM11,17.5Q11.65,17.5 12.075,17.075Q12.5,16.65 12.5,16Q12.5,15.35 12.075,14.925Q11.65,14.5 11,14.5Q10.35,14.5 9.925,14.925Q9.5,15.35 9.5,16Q9.5,16.65 9.925,17.075Q10.35,17.5 11,17.5ZM21,20H16Q15.575,20 15.288,19.712Q15,19.425 15,19V10Q15,9.575 15.288,9.287Q15.575,9 16,9H21Q21.425,9 21.712,9.287Q22,9.575 22,10V19Q22,19.425 21.712,19.712Q21.425,20 21,20ZM17,18H20V11H17Z"/>
</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
index 9065520..0baf7ef 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_glasses.xml
@@ -20,7 +20,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@android:color/system_accent1_600">
<path
android:fillColor="@android:color/white"
android:pathData="M6.85,15Q7.625,15 8.238,14.55Q8.85,14.1 9.1,13.375L9.475,12.225Q9.875,11.025 9.275,10.012Q8.675,9 7.55,9H4.025L4.5,12.925Q4.625,13.8 5.287,14.4Q5.95,15 6.85,15ZM17.15,15Q18.05,15 18.712,14.4Q19.375,13.8 19.5,12.925L19.975,9H16.475Q15.35,9 14.75,10.025Q14.15,11.05 14.55,12.25L14.9,13.375Q15.15,14.1 15.762,14.55Q16.375,15 17.15,15ZM6.85,17Q5.2,17 3.963,15.912Q2.725,14.825 2.525,13.175L2,9H1V7H7.55Q8.65,7 9.562,7.537Q10.475,8.075 11,9H13.025Q13.55,8.075 14.463,7.537Q15.375,7 16.475,7H23V9H22L21.475,13.175Q21.275,14.825 20.038,15.912Q18.8,17 17.15,17Q15.725,17 14.588,16.188Q13.45,15.375 13,14.025L12.625,12.9Q12.575,12.725 12.525,12.537Q12.475,12.35 12.425,12H11.575Q11.525,12.3 11.475,12.487Q11.425,12.675 11.375,12.85L11,14Q10.55,15.35 9.413,16.175Q8.275,17 6.85,17Z"/>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_watch.xml b/packages/CompanionDeviceManager/res/drawable/ic_watch.xml
index d7a28d9..0f6b21f 100644
--- a/packages/CompanionDeviceManager/res/drawable/ic_watch.xml
+++ b/packages/CompanionDeviceManager/res/drawable/ic_watch.xml
@@ -20,7 +20,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:tint="@android:color/system_accent1_600">
<path android:fillColor="@android:color/white"
android:pathData="M9,22 L7.65,17.45Q6.45,16.5 5.725,15.075Q5,13.65 5,12Q5,10.35 5.725,8.925Q6.45,7.5 7.65,6.55L9,2H15L16.35,6.55Q17.55,7.5 18.275,8.925Q19,10.35 19,12Q19,13.65 18.275,15.075Q17.55,16.5 16.35,17.45L15,22ZM12,17Q14.075,17 15.538,15.537Q17,14.075 17,12Q17,9.925 15.538,8.462Q14.075,7 12,7Q9.925,7 8.463,8.462Q7,9.925 7,12Q7,14.075 8.463,15.537Q9.925,17 12,17ZM10.1,5.25Q11.075,4.975 12,4.975Q12.925,4.975 13.9,5.25L13.5,4H10.5ZM10.5,20H13.5L13.9,18.75Q12.925,19.025 12,19.025Q11.075,19.025 10.1,18.75ZM10.1,4H10.5H13.5H13.9Q12.925,4 12,4Q11.075,4 10.1,4ZM10.5,20H10.1Q11.075,20 12,20Q12.925,20 13.9,20H13.5Z"/>
</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index d1d2c70..d470d4c 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -38,8 +38,7 @@
android:layout_width="match_parent"
android:layout_height="32dp"
android:gravity="center"
- android:layout_marginTop="18dp"
- android:tint="@android:color/system_accent1_600"/>
+ android:layout_marginTop="18dp" />
<LinearLayout style="@style/Description">
<TextView
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_device.xml b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
index ac5294a..79f04b9 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_device.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_device.xml
@@ -29,7 +29,6 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="24dp"
- android:tint="@android:color/system_accent1_600"
android:importantForAccessibility="no"
android:contentDescription="@null"/>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 99b776c..71ae578 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -39,6 +39,7 @@
import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import static com.android.companiondevicemanager.Utils.getIcon;
+import static com.android.companiondevicemanager.Utils.getImageColor;
import static com.android.companiondevicemanager.Utils.getVendorHeaderIcon;
import static com.android.companiondevicemanager.Utils.getVendorHeaderName;
import static com.android.companiondevicemanager.Utils.hasVendorIcon;
@@ -56,7 +57,6 @@
import android.companion.IAssociationRequestCallback;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.Color;
@@ -463,8 +463,6 @@
final Drawable vendorIcon;
final CharSequence vendorName;
final Spanned title;
- int nightModeFlags = getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK;
if (!SUPPORTED_SELF_MANAGED_PROFILES.contains(deviceProfile)) {
throw new RuntimeException("Unsupported profile " + deviceProfile);
@@ -477,8 +475,7 @@
vendorName = getVendorHeaderName(this, packageName, userId);
mVendorHeaderImage.setImageDrawable(vendorIcon);
if (hasVendorIcon(this, packageName, userId)) {
- int color = nightModeFlags == Configuration.UI_MODE_NIGHT_YES
- ? android.R.color.system_accent1_200 : android.R.color.system_accent1_600;
+ int color = getImageColor(this);
mVendorHeaderImage.setColorFilter(getResources().getColor(color, /* Theme= */null));
}
} catch (PackageManager.NameNotFoundException e) {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index 328c67e..d8348d1 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -17,6 +17,7 @@
package com.android.companiondevicemanager;
import static com.android.companiondevicemanager.Utils.getIcon;
+import static com.android.companiondevicemanager.Utils.getImageColor;
import android.content.Context;
import android.view.LayoutInflater;
@@ -65,6 +66,10 @@
viewHolder.mImageView.setImageDrawable(
getIcon(mContext, android.R.drawable.stat_sys_data_bluetooth));
}
+
+ viewHolder.mImageView.setColorFilter(
+ mContext.getResources().getColor(getImageColor(mContext), /* Theme= */null));
+
return viewHolder;
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index fceca91..8c14f80 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -22,6 +22,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
@@ -120,6 +121,20 @@
}
}
+ private static boolean isDarkTheme(@NonNull Context context) {
+ int nightModeFlags = context.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ return nightModeFlags == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ // Get image color for the corresponding theme.
+ static int getImageColor(@NonNull Context context) {
+ if (isDarkTheme(context)) {
+ return android.R.color.system_accent1_200;
+ } else {
+ return android.R.color.system_accent1_600;
+ }
+ }
/**
* Getting ApplicationInfo from meta-data.
*/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index b32fe3f..28f9453 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -467,19 +467,19 @@
GetCredentialRequest.Builder(
Bundle()
).addCredentialOption(
- CredentialOption(
+ CredentialOption.Builder(
passwordOption.type,
passwordOption.requestData,
passwordOption.candidateQueryData,
- passwordOption.isSystemProviderRequired
- )
+ ).setIsSystemProviderRequired(passwordOption.isSystemProviderRequired)
+ .build()
).addCredentialOption(
- CredentialOption(
+ CredentialOption.Builder(
passkeyOption.type,
passkeyOption.requestData,
passkeyOption.candidateQueryData,
- passkeyOption.isSystemProviderRequired
- )
+ ).setIsSystemProviderRequired(passkeyOption.isSystemProviderRequired)
+ .build()
).build(),
"com.google.android.youtube"
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index e8e3974..5d72424 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -38,7 +38,9 @@
import com.android.credentialmanager.createflow.CreateCredentialScreen
import com.android.credentialmanager.createflow.hasContentToDisplay
import com.android.credentialmanager.getflow.GetCredentialScreen
+import com.android.credentialmanager.getflow.GetGenericCredentialScreen
import com.android.credentialmanager.getflow.hasContentToDisplay
+import com.android.credentialmanager.getflow.isFallbackScreen
import com.android.credentialmanager.ui.theme.PlatformTheme
@ExperimentalMaterialApi
@@ -118,11 +120,19 @@
providerActivityLauncher = launcher
)
} else if (getCredentialUiState != null && hasContentToDisplay(getCredentialUiState)) {
- GetCredentialScreen(
- viewModel = viewModel,
- getCredentialUiState = getCredentialUiState,
- providerActivityLauncher = launcher
- )
+ if (isFallbackScreen(getCredentialUiState)) {
+ GetGenericCredentialScreen(
+ viewModel = viewModel,
+ getCredentialUiState = getCredentialUiState,
+ providerActivityLauncher = launcher
+ )
+ } else {
+ GetCredentialScreen(
+ viewModel = viewModel,
+ getCredentialUiState = getCredentialUiState,
+ providerActivityLauncher = launcher
+ )
+ }
} else {
Log.d(Constants.LOG_TAG, "UI wasn't able to render neither get nor create flow")
reportInstantiationErrorAndFinishActivity(credManRepo)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index bcf692f..7b98049 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.createflow
+import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
@@ -668,7 +669,7 @@
entryHeadlineText = requestDisplayInfo.title,
entrySecondLineText = when (requestDisplayInfo.type) {
CredentialType.PASSKEY -> {
- if (requestDisplayInfo.subtitle != null) {
+ if (!TextUtils.isEmpty(requestDisplayInfo.subtitle)) {
requestDisplayInfo.subtitle + " • " + stringResource(
R.string.passkey_before_subtitle
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
new file mode 100644
index 0000000..8b95b5e
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.credentialmanager.getflow
+
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.IntentSenderRequest
+import androidx.compose.runtime.Composable
+import com.android.credentialmanager.CredentialSelectorViewModel
+
+@Composable
+fun GetGenericCredentialScreen(
+ viewModel: CredentialSelectorViewModel,
+ getCredentialUiState: GetCredentialUiState,
+ providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+) {
+ // TODO(b/274129098): Implement Screen for mDocs
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 263a632..7a86790 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -41,6 +41,10 @@
!state.requestDisplayInfo.preferImmediatelyAvailableCredentials)
}
+internal fun isFallbackScreen(state: GetCredentialUiState): Boolean {
+ return false
+}
+
internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): CredentialEntryInfo? {
if (providerDisplayInfo.authenticationEntryList.isNotEmpty()) {
return null
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 6669e35..a2118fa 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -37,9 +37,8 @@
<string name="install_confirm_question">Do you want to install this app?</string>
<!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
<string name="install_confirm_question_update">Do you want to update this app?</string>
- <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. -->
- <!-- Message for updating an existing app with update owner reminder [DO NOT TRANSLATE][CHAR LIMIT=NONE] -->
- <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>?</string>
+ <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+ <string name="install_confirm_question_update_owner_reminder">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
<!-- [CHAR LIMIT=100] -->
<string name="install_failed">App not installed.</string>
<!-- Reason displayed when installation fails because the package was blocked
@@ -82,6 +81,8 @@
<!-- [CHAR LIMIT=15] -->
<string name="ok">OK</string>
+ <!-- [CHAR LIMIT=30] -->
+ <string name="update_anyway">Update anyway</string>
<!-- [CHAR LIMIT=15] -->
<string name="manage_applications">Manage apps</string>
<!-- [CHAR LIMIT=30] -->
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d41cfbc2..c81e75b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -148,10 +148,11 @@
&& mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
viewToEnable.setText(
getString(R.string.install_confirm_question_update_owner_reminder,
- existingUpdateOwnerLabel, requestedUpdateOwnerLabel));
+ requestedUpdateOwnerLabel, existingUpdateOwnerLabel));
+ mOk.setText(R.string.update_anyway);
+ } else {
+ mOk.setText(R.string.update);
}
-
- mOk.setText(R.string.update);
} else {
// This is a new application with no permissions.
viewToEnable = requireViewById(R.id.install_confirm_question);
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 2071489..4fd2b5d 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -24,7 +24,7 @@
}
}
plugins {
- id 'com.android.application' version '8.0.0-beta03' apply false
- id 'com.android.library' version '8.0.0-beta03' apply false
+ id 'com.android.application' version '8.0.0-beta05' apply false
+ id 'com.android.library' version '8.0.0-beta05' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
}
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index c3d5431..ed85e33 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,7 +16,7 @@
#Thu Jul 14 10:36:06 CST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-rc-2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 4563b7d..9962c93 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -79,7 +79,7 @@
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
api "androidx.lifecycle:lifecycle-livedata-ktx"
api "androidx.lifecycle:lifecycle-runtime-compose"
- api "androidx.navigation:navigation-compose:2.6.0-alpha04"
+ api "androidx.navigation:navigation-compose:2.6.0-alpha07"
api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index f6bb3cc..47ac2df 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -109,7 +109,7 @@
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
TwoRowsTopAppBar(
- title = { Title(title = title, maxLines = 2) },
+ title = { Title(title = title, maxLines = 3) },
titleTextStyle = MaterialTheme.typography.displaySmall,
smallTitleTextStyle = MaterialTheme.typography.titleMedium,
titleBottomPadding = LargeTitleBottomPadding,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index c609004..9f33fcb 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -62,10 +62,12 @@
}
val permission = AppOpsManager.opToPermission(op)
- packageManager.updatePermissionFlags(permission, app.packageName,
- PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET,
- UserHandle.getUserHandleForUid(app.uid))
-
+ if (permission != null) {
+ packageManager.updatePermissionFlags(permission, app.packageName,
+ PackageManager.FLAG_PERMISSION_USER_SET,
+ PackageManager.FLAG_PERMISSION_USER_SET,
+ UserHandle.getUserHandleForUid(app.uid))
+ }
_mode.postValue(mode)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index f741f65..7b4c862 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -51,7 +51,8 @@
public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
mContext = context;
mBtManager = localBtManager;
- mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
+ mHearingAidDeviceManager = new HearingAidDeviceManager(context, localBtManager,
+ mCachedDevices);
mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
new file mode 100644
index 0000000..d8475b3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.settingslib.bluetooth;
+
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Constant values used to configure hearing aid audio routing.
+ *
+ * {@link HearingAidAudioRoutingHelper}
+ */
+public final class HearingAidAudioRoutingConstants {
+ public static final int[] CALL_ROUTING_ATTRIBUTES = new int[] {
+ // Stands for STRATEGY_PHONE
+ AudioAttributes.USAGE_VOICE_COMMUNICATION,
+ };
+
+ public static final int[] MEDIA_ROUTING_ATTRIBUTES = new int[] {
+ // Stands for STRATEGY_MEDIA, including USAGE_GAME, USAGE_ASSISTANT,
+ // USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, USAGE_ASSISTANCE_SONIFICATION
+ AudioAttributes.USAGE_MEDIA
+ };
+
+ public static final int[] RINGTONE_ROUTING_ATTRIBUTE = new int[] {
+ // Stands for STRATEGY_SONIFICATION, including USAGE_ALARM
+ AudioAttributes.USAGE_NOTIFICATION_RINGTONE
+ };
+
+ public static final int[] SYSTEM_SOUNDS_ROUTING_ATTRIBUTES = new int[] {
+ // Stands for STRATEGY_SONIFICATION_RESPECTFUL, including USAGE_NOTIFICATION_EVENT
+ AudioAttributes.USAGE_NOTIFICATION,
+ // Stands for STRATEGY_ACCESSIBILITY
+ AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+ // Stands for STRATEGY_DTMF
+ AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
+ };
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ RoutingValue.AUTO,
+ RoutingValue.HEARING_DEVICE,
+ RoutingValue.DEVICE_SPEAKER,
+ })
+
+ public @interface RoutingValue {
+ int AUTO = 0;
+ int HEARING_DEVICE = 1;
+ int DEVICE_SPEAKER = 2;
+ }
+
+ public static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
new file mode 100644
index 0000000..c9512cd
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 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.settingslib.bluetooth;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to configure the routing strategy for hearing aids.
+ */
+public class HearingAidAudioRoutingHelper {
+
+ private final AudioManager mAudioManager;
+
+ public HearingAidAudioRoutingHelper(Context context) {
+ mAudioManager = context.getSystemService(AudioManager.class);
+ }
+
+ /**
+ * Gets the list of {@link AudioProductStrategy} referred by the given list of usage values
+ * defined in {@link AudioAttributes}
+ */
+ public List<AudioProductStrategy> getSupportedStrategies(int[] attributeSdkUsageList) {
+ final List<AudioAttributes> audioAttrList = new ArrayList<>(attributeSdkUsageList.length);
+ for (int attributeSdkUsage : attributeSdkUsageList) {
+ audioAttrList.add(new AudioAttributes.Builder().setUsage(attributeSdkUsage).build());
+ }
+
+ final List<AudioProductStrategy> allStrategies = getAudioProductStrategies();
+ final List<AudioProductStrategy> supportedStrategies = new ArrayList<>();
+ for (AudioProductStrategy strategy : allStrategies) {
+ for (AudioAttributes audioAttr : audioAttrList) {
+ if (strategy.supportsAudioAttributes(audioAttr)) {
+ supportedStrategies.add(strategy);
+ }
+ }
+ }
+
+ return supportedStrategies.stream().distinct().collect(Collectors.toList());
+ }
+
+ /**
+ * Sets the preferred device for the given strategies.
+ *
+ * @param supportedStrategies A list of {@link AudioProductStrategy} used to configure audio
+ * routing
+ * @param hearingDevice {@link AudioDeviceAttributes} of the device to be changed in audio
+ * routing
+ * @param routingValue one of value defined in
+ * {@link HearingAidAudioRoutingConstants.RoutingValue}, denotes routing
+ * destination.
+ * @return {code true} if the routing value successfully configure
+ */
+ public boolean setPreferredDeviceRoutingStrategies(
+ List<AudioProductStrategy> supportedStrategies, AudioDeviceAttributes hearingDevice,
+ @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
+ boolean status;
+ switch (routingValue) {
+ case HearingAidAudioRoutingConstants.RoutingValue.AUTO:
+ status = removePreferredDeviceForStrategies(supportedStrategies);
+ return status;
+ case HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE:
+ status = removePreferredDeviceForStrategies(supportedStrategies);
+ status &= setPreferredDeviceForStrategies(supportedStrategies, hearingDevice);
+ return status;
+ case HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER:
+ status = removePreferredDeviceForStrategies(supportedStrategies);
+ status &= setPreferredDeviceForStrategies(supportedStrategies,
+ HearingAidAudioRoutingConstants.DEVICE_SPEAKER_OUT);
+ return status;
+ default:
+ throw new IllegalArgumentException("Unexpected routingValue: " + routingValue);
+ }
+ }
+
+ /**
+ * Gets the matched hearing device {@link AudioDeviceAttributes} for {@code device}.
+ *
+ * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} of {@code device}
+ *
+ * @param device the {@link CachedBluetoothDevice} need to be hearing aid device
+ * @return the requested AudioDeviceAttributes or {@code null} if not match
+ */
+ @Nullable
+ public AudioDeviceAttributes getMatchedHearingDeviceAttributes(CachedBluetoothDevice device) {
+ if (device == null || !device.isHearingAidDevice()) {
+ return null;
+ }
+
+ AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo audioDevice : audioDevices) {
+ // ASHA for TYPE_HEARING_AID, HAP for TYPE_BLE_HEADSET
+ if (audioDevice.getType() == AudioDeviceInfo.TYPE_HEARING_AID
+ || audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+ if (matchAddress(device, audioDevice)) {
+ return new AudioDeviceAttributes(audioDevice);
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean matchAddress(CachedBluetoothDevice device, AudioDeviceInfo audioDevice) {
+ final String audioDeviceAddress = audioDevice.getAddress();
+ final CachedBluetoothDevice subDevice = device.getSubDevice();
+ final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
+
+ return device.getAddress().equals(audioDeviceAddress)
+ || (subDevice != null && subDevice.getAddress().equals(audioDeviceAddress))
+ || (!memberDevices.isEmpty() && memberDevices.stream().anyMatch(
+ m -> m.getAddress().equals(audioDeviceAddress)));
+ }
+
+ private boolean setPreferredDeviceForStrategies(List<AudioProductStrategy> strategies,
+ AudioDeviceAttributes audioDevice) {
+ boolean status = true;
+ for (AudioProductStrategy strategy : strategies) {
+ status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice);
+
+ }
+
+ return status;
+ }
+
+ private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
+ boolean status = true;
+ for (AudioProductStrategy strategy : strategies) {
+ status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+ }
+
+ return status;
+ }
+
+ @VisibleForTesting
+ public List<AudioProductStrategy> getAudioProductStrategies() {
+ return AudioManager.getAudioProductStrategies();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index ebfec0a..4354e0c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -18,6 +18,11 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -33,12 +38,25 @@
private static final String TAG = "HearingAidDeviceManager";
private static final boolean DEBUG = BluetoothUtils.D;
+ private final ContentResolver mContentResolver;
private final LocalBluetoothManager mBtManager;
private final List<CachedBluetoothDevice> mCachedDevices;
- HearingAidDeviceManager(LocalBluetoothManager localBtManager,
+ private final HearingAidAudioRoutingHelper mRoutingHelper;
+ HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
List<CachedBluetoothDevice> CachedDevices) {
+ mContentResolver = context.getContentResolver();
mBtManager = localBtManager;
mCachedDevices = CachedDevices;
+ mRoutingHelper = new HearingAidAudioRoutingHelper(context);
+ }
+
+ @VisibleForTesting
+ HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
+ List<CachedBluetoothDevice> cachedDevices, HearingAidAudioRoutingHelper routingHelper) {
+ mContentResolver = context.getContentResolver();
+ mBtManager = localBtManager;
+ mCachedDevices = cachedDevices;
+ mRoutingHelper = routingHelper;
}
void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
@@ -192,12 +210,11 @@
case BluetoothProfile.STATE_CONNECTED:
onHiSyncIdChanged(cachedDevice.getHiSyncId());
CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
- if (mainDevice != null){
+ if (mainDevice != null) {
if (mainDevice.isConnected()) {
// When main device exists and in connected state, receiving sub device
// connection. To refresh main device UI
mainDevice.refresh();
- return true;
} else {
// When both Hearing Aid devices are disconnected, receiving sub device
// connection. To switch content and dispatch to notify UI change
@@ -207,9 +224,15 @@
// It is necessary to do remove and add for updating the mapping on
// preference and device
mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
- return true;
+ // Only need to set first device of a set. AudioDeviceInfo for
+ // GET_DEVICES_OUTPUTS will not change device.
+ setAudioRoutingConfig(cachedDevice);
}
+ return true;
}
+ // Only need to set first device of a set. AudioDeviceInfo for GET_DEVICES_OUTPUTS
+ // will not change device.
+ setAudioRoutingConfig(cachedDevice);
break;
case BluetoothProfile.STATE_DISCONNECTED:
mainDevice = findMainDevice(cachedDevice);
@@ -232,13 +255,83 @@
// It is necessary to do remove and add for updating the mapping on
// preference and device
mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
+
return true;
}
+ // Only need to clear when last device of a set get disconnected
+ clearAudioRoutingConfig();
break;
}
return false;
}
+ private void setAudioRoutingConfig(CachedBluetoothDevice device) {
+ AudioDeviceAttributes hearingDeviceAttributes =
+ mRoutingHelper.getMatchedHearingDeviceAttributes(device);
+ if (hearingDeviceAttributes == null) {
+ Log.w(TAG, "Can not find expected AudioDeviceAttributes for hearing device: "
+ + device.getDevice().getAnonymizedAddress());
+ return;
+ }
+
+ final int callRoutingValue = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.HEARING_AID_CALL_ROUTING,
+ HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ final int mediaRoutingValue = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.HEARING_AID_MEDIA_ROUTING,
+ HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ final int ringtoneRoutingValue = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
+ HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ final int systemSoundsRoutingValue = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
+ HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
+ hearingDeviceAttributes, callRoutingValue);
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
+ hearingDeviceAttributes, mediaRoutingValue);
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
+ hearingDeviceAttributes, ringtoneRoutingValue);
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
+ hearingDeviceAttributes, systemSoundsRoutingValue);
+ }
+
+ private void clearAudioRoutingConfig() {
+ // Don't need to pass hearingDevice when we want to reset it (set to AUTO).
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
+ /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
+ /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
+ /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ setPreferredDeviceRoutingStrategies(
+ HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
+ /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+ }
+
+ private void setPreferredDeviceRoutingStrategies(int[] attributeSdkUsageList,
+ AudioDeviceAttributes hearingDevice,
+ @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
+ final List<AudioProductStrategy> supportedStrategies =
+ mRoutingHelper.getSupportedStrategies(attributeSdkUsageList);
+
+ final boolean status = mRoutingHelper.setPreferredDeviceRoutingStrategies(
+ supportedStrategies, hearingDevice, routingValue);
+
+ if (!status) {
+ Log.w(TAG, "routingStrategies: " + supportedStrategies.toString() + "routingValue: "
+ + routingValue + " fail to configure AudioProductStrategy");
+ }
+ }
+
CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index a3c2e70..43e3a32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -361,6 +361,7 @@
cachedDevice.setHearingAidInfo(infoBuilder.build());
}
}
+
HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 071ab27..a9d15f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -97,7 +97,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef({SelectionBehavior.SELECTION_BEHAVIOR_NONE,
- SELECTION_BEHAVIOR_TRANSFER,
+ SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER,
SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP
})
public @interface SelectionBehavior {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index f06623d..4b3820e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -403,7 +403,7 @@
*/
@Test
public void updateHearingAidDevices_directToHearingAidDeviceManager() {
- mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager,
+ mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
mCachedDeviceManager.mCachedDevices));
mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
mCachedDeviceManager.updateHearingAidsDevices();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
new file mode 100644
index 0000000..8b5ea30
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 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.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Tests for {@link HearingAidAudioRoutingHelper}. */
+@RunWith(RobolectricTestRunner.class)
+public class HearingAidAudioRoutingHelperTest {
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Spy
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
+ private static final String NOT_EXPECT_DEVICE_ADDRESS = "11:B2:B2:B2:B2:B2";
+
+ @Mock
+ private AudioProductStrategy mAudioStrategy;
+ @Spy
+ private AudioManager mAudioManager = mContext.getSystemService(AudioManager.class);
+ @Mock
+ private AudioDeviceInfo mAudioDeviceInfo;
+ @Mock
+ private CachedBluetoothDevice mCachedBluetoothDevice;
+ @Mock
+ private CachedBluetoothDevice mSubCachedBluetoothDevice;
+ private AudioDeviceAttributes mHearingDeviceAttribute;
+ private HearingAidAudioRoutingHelper mHelper;
+
+ @Before
+ public void setUp() {
+ doReturn(mAudioManager).when(mContext).getSystemService(AudioManager.class);
+ when(mAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+ when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
+ new AudioDeviceInfo[]{mAudioDeviceInfo});
+ when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
+ AudioManager.STREAM_MUSIC))
+ .thenReturn((new AudioAttributes.Builder()).build());
+
+ mHearingDeviceAttribute = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ TEST_DEVICE_ADDRESS);
+ mHelper = spy(new HearingAidAudioRoutingHelper(mContext));
+ doReturn(List.of(mAudioStrategy)).when(mHelper).getAudioProductStrategies();
+ }
+
+ @Test
+ public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() {
+ mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+ mHearingDeviceAttribute,
+ HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+ verify(mAudioManager, atLeastOnce()).removePreferredDeviceForStrategy(mAudioStrategy);
+ }
+
+ @Test
+ public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
+ mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+ mHearingDeviceAttribute,
+ HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE);
+
+ verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
+ mHearingDeviceAttribute);
+ }
+
+ @Test
+ public void setPreferredDeviceRoutingStrategies_valueDeviceSpeaker_callSetStrategy() {
+ final AudioDeviceAttributes speakerDevice = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+ mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+ mHearingDeviceAttribute,
+ HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER);
+
+ verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
+ speakerDevice);
+ }
+
+ @Test
+ public void getMatchedHearingDeviceAttributes_mainHearingDevice_equalAddress() {
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+
+ final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+ mCachedBluetoothDevice).getAddress();
+
+ assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+ }
+
+ @Test
+ public void getMatchedHearingDeviceAttributes_subHearingDevice_equalAddress() {
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
+ when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
+ when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+ when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+
+ final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+ mCachedBluetoothDevice).getAddress();
+
+ assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+ }
+
+ @Test
+ public void getMatchedHearingDeviceAttributes_memberHearingDevice_equalAddress() {
+ when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+ when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+ final Set<CachedBluetoothDevice> memberDevices = new HashSet<CachedBluetoothDevice>();
+ memberDevices.add(mSubCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberDevices);
+
+ final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+ mCachedBluetoothDevice).getAddress();
+
+ assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 470d8e0..a839136 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -18,7 +18,12 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -29,18 +34,32 @@
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
import android.os.Parcel;
+import androidx.test.core.app.ApplicationProvider;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
+
+import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class HearingAidDeviceManagerTest {
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
private final static long HISYNCID1 = 10;
private final static long HISYNCID2 = 11;
private final static String DEVICE_NAME_1 = "TestName_1";
@@ -51,6 +70,15 @@
private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
private final BluetoothClass DEVICE_CLASS =
createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+
+ private CachedBluetoothDevice mCachedDevice1;
+ private CachedBluetoothDevice mCachedDevice2;
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ private HearingAidDeviceManager mHearingAidDeviceManager;
+ private AudioDeviceAttributes mHearingDeviceAttribute;
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ @Spy
+ private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext);
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
@@ -60,14 +88,12 @@
@Mock
private HearingAidProfile mHearingAidProfile;
@Mock
+ private AudioProductStrategy mAudioStrategy;
+ @Mock
private BluetoothDevice mDevice1;
@Mock
private BluetoothDevice mDevice2;
- private CachedBluetoothDevice mCachedDevice1;
- private CachedBluetoothDevice mCachedDevice2;
- private CachedBluetoothDeviceManager mCachedDeviceManager;
- private HearingAidDeviceManager mHearingAidDeviceManager;
- private Context mContext;
+
private BluetoothClass createBtClass(int deviceClass) {
Parcel p = Parcel.obtain();
@@ -81,8 +107,6 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
@@ -94,10 +118,18 @@
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+ when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
+ AudioManager.STREAM_MUSIC))
+ .thenReturn((new AudioAttributes.Builder()).build());
+ doReturn(List.of(mAudioStrategy)).when(mHelper).getSupportedStrategies(any(int[].class));
+ mHearingDeviceAttribute = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ DEVICE_ADDRESS_1);
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
- mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager,
- mCachedDeviceManager.mCachedDevices));
+ mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
+ mCachedDeviceManager.mCachedDevices, mHelper));
mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
}
@@ -446,6 +478,44 @@
}
@Test
+ public void onProfileConnectionStateChanged_connected_callSetStrategies() {
+ when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+ mHearingDeviceAttribute);
+
+ mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+ BluetoothProfile.STATE_CONNECTED);
+
+ verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
+ eq(List.of(mAudioStrategy)), any(AudioDeviceAttributes.class), anyInt());
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_disconnected_callSetStrategiesWithAutoValue() {
+ when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+ mHearingDeviceAttribute);
+
+ mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
+ eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
+ eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
+ }
+ @Test
+ public void onProfileConnectionStateChanged_unpairing_callSetStrategiesWithAutoValue() {
+ when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+ mHearingDeviceAttribute);
+
+ when(mCachedDevice1.getUnpairing()).thenReturn(true);
+ mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+ BluetoothProfile.STATE_DISCONNECTED);
+
+ verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
+ eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
+ eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
+ }
+
+ @Test
public void findMainDevice() {
when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 3fe12b3..85623b2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -52,6 +52,7 @@
|| (val == BatteryManager.BATTERY_PLUGGED_AC)
|| (val == BatteryManager.BATTERY_PLUGGED_USB)
|| (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+ || (val == BatteryManager.BATTERY_PLUGGED_DOCK)
|| (val
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_USB))
@@ -64,7 +65,13 @@
|| (val
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_USB
- | BatteryManager.BATTERY_PLUGGED_WIRELESS));
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_DOCK));
} catch (NumberFormatException e) {
return false;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index a8eeec3..80030f7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -50,10 +50,6 @@
@GuardedBy("mLock")
private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- // Maximum number of backing stores allowed
- static final int NUM_MAX_BACKING_STORE = 8;
-
@GuardedBy("mLock")
private int mNumBackingStore = 0;
@@ -65,8 +61,24 @@
// The generation number is only increased when a new non-predefined setting is inserted
private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = "";
- public GenerationRegistry(Object lock) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ // Minimum number of backing stores; supports 3 users
+ static final int MIN_NUM_BACKING_STORE = 8;
+ // Maximum number of backing stores; supports 18 users
+ static final int MAX_NUM_BACKING_STORE = 38;
+
+ private final int mMaxNumBackingStore;
+
+ GenerationRegistry(Object lock, int maxNumUsers) {
mLock = lock;
+ // Add some buffer to maxNumUsers to accommodate corner cases when the actual number of
+ // users in the system exceeds the limit
+ maxNumUsers = maxNumUsers + 2;
+ // Number of backing stores needed for N users is (N + N + 1 + 1) = N * 2 + 2
+ // N Secure backing stores and N System backing stores, 1 Config and 1 Global for all users
+ // However, we always make sure that at least 3 users and at most 18 users are supported.
+ mMaxNumBackingStore = Math.min(Math.max(maxNumUsers * 2 + 2, MIN_NUM_BACKING_STORE),
+ MAX_NUM_BACKING_STORE);
}
/**
@@ -195,7 +207,7 @@
}
if (backingStore == null) {
try {
- if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
+ if (mNumBackingStore >= mMaxNumBackingStore) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error creating backing store - at capacity");
}
@@ -275,4 +287,9 @@
}
return -1;
}
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ int getMaxNumBackingStores() {
+ return mMaxNumBackingStore;
+ }
}
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 7e89bfc..721b3c4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2831,7 +2831,7 @@
public SettingsRegistry() {
mHandler = new MyHandler(getContext().getMainLooper());
- mGenerationRegistry = new GenerationRegistry(mLock);
+ mGenerationRegistry = new GenerationRegistry(mLock, UserManager.getMaxSupportedUsers());
mBackupManager = new BackupManager(getContext());
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 47abb35..e0e3720 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -265,7 +265,6 @@
Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
Settings.Global.ENABLE_DISKSTATS_LOGGING,
Settings.Global.ENABLE_EPHEMERAL_FEATURE,
- Settings.Global.ENABLE_RESTRICTED_BUCKET,
Settings.Global.ENABLE_TARE,
Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED,
Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index 586d6f7..12865f4 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -36,7 +36,7 @@
public class GenerationRegistryTest {
@Test
public void testGenerationsWithRegularSetting() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
@@ -93,7 +93,7 @@
@Test
public void testGenerationsWithConfigSetting() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
final String prefix = "test_namespace/";
final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
@@ -110,10 +110,10 @@
@Test
public void testMaxNumBackingStores() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
- for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
+ for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); i++) {
b.clear();
final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
generationRegistry.addGenerationData(b, key, testSecureSetting);
@@ -121,7 +121,7 @@
}
b.clear();
final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
- GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
+ generationRegistry.getMaxNumBackingStores() + 1);
generationRegistry.addGenerationData(b, key, testSecureSetting);
// Should fail to add generation because the number of backing stores has reached limit
checkBundle(b, -1, -1, true);
@@ -133,7 +133,7 @@
@Test
public void testMaxSizeBackingStore() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
@@ -153,7 +153,7 @@
@Test
public void testUnsetSettings() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1);
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
final String testSecureSetting = "test_secure_setting";
Bundle b = new Bundle();
@@ -172,7 +172,7 @@
@Test
public void testGlobalSettings() throws IOException {
- final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2);
final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0);
final String testGlobalSetting = "test_global_setting";
final Bundle b = new Bundle();
@@ -188,6 +188,18 @@
assertThat(array).isEqualTo(array2);
}
+ @Test
+ public void testNumberOfBackingStores() {
+ GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0);
+ // Test that the capacity of the backing stores is always valid
+ assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
+ GenerationRegistry.MIN_NUM_BACKING_STORE);
+ generationRegistry = new GenerationRegistry(new Object(), 100);
+ // Test that the capacity of the backing stores is always valid
+ assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo(
+ GenerationRegistry.MAX_NUM_BACKING_STORE);
+ }
+
private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
throws IOException {
final MemoryIntArray array = getArray(b);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8b38deb..941697b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -376,7 +376,7 @@
name: "SystemUIRobo-stub",
defaults: [
"platform_app_defaults",
- "SystemUI_app_defaults",
+ "SystemUI_optimized_defaults",
"SystemUI_compose_defaults",
],
manifest: "tests/AndroidManifest-base.xml",
@@ -443,7 +443,7 @@
}
systemui_optimized_java_defaults {
- name: "SystemUI_app_defaults",
+ name: "SystemUI_optimized_defaults",
soong_config_variables: {
SYSTEMUI_OPTIMIZE_JAVA: {
optimize: {
@@ -452,12 +452,10 @@
shrink: true,
shrink_resources: true,
proguard_compatibility: false,
- proguard_flags_files: ["proguard.flags"],
},
conditions_default: {
optimize: {
proguard_compatibility: false,
- proguard_flags_files: ["proguard.flags"],
},
},
},
@@ -468,7 +466,7 @@
name: "SystemUI",
defaults: [
"platform_app_defaults",
- "SystemUI_app_defaults",
+ "SystemUI_optimized_defaults",
],
static_libs: [
"SystemUI-core",
@@ -483,6 +481,9 @@
kotlincflags: ["-Xjvm-default=enable"],
dxflags: ["--multi-dex"],
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
required: [
"privapp_whitelist_com.android.systemui",
"wmshell.protolog.json.gz",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 650d5fa..9b3f1a8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -235,7 +235,10 @@
<uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
+ <!-- role holder APIs -->
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
+ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<!-- It's like, reality, but, you know, virtual -->
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
@@ -347,6 +350,7 @@
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
+ <protected-broadcast android:name="com.android.systemui.action.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG" />
<protected-broadcast android:name="com.android.systemui.STARTED" />
<application
@@ -632,12 +636,6 @@
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true">
</activity>
- <activity-alias
- android:name=".UsbDebuggingActivityAlias"
- android:permission="android.permission.DUMP"
- android:targetActivity=".usb.UsbDebuggingActivity"
- android:exported="true">
- </activity-alias>
<activity android:name=".usb.UsbDebuggingSecondaryUserActivity"
android:theme="@style/Theme.SystemUI.Dialog.Alert"
android:finishOnCloseSystemDialogs="true"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
index b9e38cf..99fe26c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
@@ -53,6 +53,7 @@
modifier: Modifier = Modifier,
) {
val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState()
+ val scrimAlpha: Float by viewModel.scrimAlpha.collectAsState()
// TODO(b/273298030): find a different way to get the height constraint from its parent.
BoxWithConstraints(modifier = modifier) {
@@ -61,7 +62,7 @@
Scrim(
modifier = Modifier.fillMaxSize(),
remoteTouch = viewModel::onScrimTouched,
- alpha = { viewModel.scrimAlpha.value },
+ alpha = { scrimAlpha },
isScrimEnabled = isScrimEnabled,
)
Shade(
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index a317178..762dcdc 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -55,7 +55,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/chipbar_text_size"
- android:textColor="@android:color/system_accent2_900"
+ android:textColor="@color/chipbar_text_and_icon_color"
android:alpha="0.0"
/>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 4f7d099..4d6c2022 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -321,7 +321,8 @@
<RelativeLayout
android:id="@+id/bottom_buttons"
android:layout_width="match_parent"
- android:layout_height="60dp"
+ android:layout_height="wrap_content"
+ android:minHeight="60dp"
android:gravity="center_vertical"
android:paddingStart="4dp"
android:paddingEnd="4dp"
diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
index b374074..80f5d87 100644
--- a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
+++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
@@ -24,7 +24,7 @@
android:orientation="horizontal"
android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
+ android:visibility="gone" >
<ImageView android:id="@+id/current_user_avatar"
android:layout_width="@dimen/status_bar_user_chip_avatar_size"
android:layout_height="@dimen/status_bar_user_chip_avatar_size"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 62e8c5f..31071ca 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -231,6 +231,9 @@
<color name="people_tile_background">@color/material_dynamic_secondary95</color>
+ <!-- Chipbar -->
+ <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color>
+
<!-- Internet Dialog -->
<!-- Material next state on color-->
<color name="settingslib_state_on_color">@color/settingslib_state_on</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 082f385..1602189 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -64,6 +64,10 @@
<!-- The number of rows in the QuickSettings -->
<integer name="quick_settings_max_rows">4</integer>
+ <!-- If the dp width of the available space is <= this value, potentially adjust the number
+ of media recommendation items-->
+ <integer name="default_qs_media_rec_width_dp">380</integer>
+
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<!-- The default tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ff86c59..412e70f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1094,6 +1094,7 @@
<dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+ <dimen name="qs_media_rec_default_width">380dp</dimen>
<dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
<dimen name="qs_media_rec_album_icon_size">16dp</dimen>
<dimen name="qs_media_rec_album_size">88dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
index 3efdc5a..4931b25 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
@@ -192,4 +192,4 @@
)
}
-private const val TRANSLATION_PERCENTAGE = 0.3f
+private const val TRANSLATION_PERCENTAGE = 0.08f
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 4aaa566..3b9060a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -259,7 +259,7 @@
largeTimeListener?.update(shouldTimeListenerRun)
}
- override fun onTimeFormatChanged(timeFormat: String) {
+ override fun onTimeFormatChanged(timeFormat: String?) {
clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 5ec59ab..7b781ce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,6 +22,7 @@
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import android.annotation.Nullable;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -458,6 +459,7 @@
mView.setClock(clock, mStatusBarStateController.getState());
}
+ @Nullable
private ClockController getClock() {
return mClockEventController.getClock();
}
@@ -510,7 +512,9 @@
}
/** Gets the animations for the current clock. */
+ @Nullable
public ClockAnimations getClockAnimations() {
- return getClock().getAnimations();
+ ClockController clock = getClock();
+ return clock == null ? null : clock.getAnimations();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 7255383..1a572b7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -260,6 +260,14 @@
*/
@Override
public void finish(boolean strongAuth, int targetUserId) {
+ if (mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)
+ && !mKeyguardStateController.canDismissLockScreen() && !strongAuth) {
+ Log.e(TAG,
+ "Tried to dismiss keyguard when lockscreen is not dismissible and user "
+ + "was not authenticated with a primary security method "
+ + "(pin/password/pattern).");
+ return;
+ }
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0e2f8f0..085f202 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -69,6 +69,7 @@
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import android.annotation.AnyThread;
@@ -479,6 +480,11 @@
sCurrentUser = currentUser;
}
+ /**
+ * @deprecated This can potentially return unexpected values in a multi user scenario
+ * as this state is managed by another component. Consider using {@link UserTracker}.
+ */
+ @Deprecated
public synchronized static int getCurrentUser() {
return sCurrentUser;
}
@@ -1610,7 +1616,7 @@
requestActiveUnlock(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT,
"assistant",
- false);
+ /* dismissKeyguard */ true);
}
}
@@ -1881,6 +1887,11 @@
updateFaceListeningState(BIOMETRIC_ACTION_STOP,
FACE_AUTH_UPDATED_POSTURE_CHANGED);
}
+ if (mPostureState == DEVICE_POSTURE_OPENED) {
+ mLogger.d("Posture changed to open - attempting to request active unlock");
+ requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE,
+ false);
+ }
}
};
@@ -2007,26 +2018,10 @@
FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_STARTED_WAKING_UP);
-
- final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin =
- mActiveUnlockConfig.isWakeupConsideredUnlockIntent(pmWakeReason)
- ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
- : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE;
- final String reason = "wakingUp - " + PowerManager.wakeReasonToString(pmWakeReason);
- if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(pmWakeReason)) {
- requestActiveUnlockDismissKeyguard(
- requestOrigin,
- reason
- );
- } else {
- requestActiveUnlock(
- requestOrigin,
- reason
- );
- }
} else {
mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
}
+ requestActiveUnlockFromWakeReason(pmWakeReason, true);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2660,6 +2655,32 @@
}
}
+ private void requestActiveUnlockFromWakeReason(@PowerManager.WakeReason int wakeReason,
+ boolean powerManagerWakeup) {
+ if (!mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(wakeReason)) {
+ mLogger.logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason);
+ return;
+ }
+
+ final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin =
+ mActiveUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason)
+ ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE;
+ final String reason = "wakingUp - " + PowerManager.wakeReasonToString(wakeReason)
+ + " powerManagerWakeup=" + powerManagerWakeup;
+ if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) {
+ requestActiveUnlockDismissKeyguard(
+ requestOrigin,
+ reason
+ );
+ } else {
+ requestActiveUnlock(
+ requestOrigin,
+ reason
+ );
+ }
+ }
+
/**
* Attempts to trigger active unlock from trust agent.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 5162807..1661806 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -62,6 +62,16 @@
)
}
+ fun logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason: Int) {
+ logBuffer.log(
+ "ActiveUnlock",
+ DEBUG,
+ { int1 = wakeReason },
+ { "Skip requesting active unlock from wake reason that doesn't trigger face auth" +
+ " reason=${PowerManager.wakeReasonToString(int1)}" }
+ )
+ }
+
fun logAuthInterruptDetected(active: Boolean) {
logBuffer.log(TAG, DEBUG, { bool1 = active }, { "onAuthInterruptDetected($bool1)" })
}
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index 90ecb46..de82ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -37,6 +37,8 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView
import com.android.systemui.animation.Interpolators
+import com.android.systemui.util.asIndenting
+import java.io.PrintWriter
/**
* A class that handles common actions of display cutout view.
@@ -324,4 +326,18 @@
}
}
}
+
+ open fun dump(pw: PrintWriter) {
+ val ipw = pw.asIndenting()
+ ipw.increaseIndent()
+ ipw.println("DisplayCutoutBaseView:")
+ ipw.increaseIndent()
+ ipw.println("shouldDrawCutout=$shouldDrawCutout")
+ ipw.println("cutout=${displayInfo.displayCutout}")
+ ipw.println("cameraProtectionProgress=$cameraProtectionProgress")
+ ipw.println("protectionRect=$protectionRect")
+ ipw.println("protectionRectOrig=$protectionRectOrig")
+ ipw.decreaseIndent()
+ ipw.decreaseIndent()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 54939fd..179eb39 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -37,6 +37,8 @@
import com.android.systemui.animation.Interpolators
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.asIndenting
+import java.io.PrintWriter
import java.util.concurrent.Executor
/**
@@ -416,4 +418,15 @@
path.transform(scaleMatrix)
}
}
+
+ override fun dump(pw: PrintWriter) {
+ val ipw = pw.asIndenting()
+ ipw.increaseIndent()
+ ipw.println("FaceScanningOverlay:")
+ super.dump(ipw)
+ ipw.println("rimProgress=$rimProgress")
+ ipw.println("rimRect=$rimRect")
+ ipw.println("this=$this")
+ ipw.decreaseIndent()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
index a74f2f8..99dd6b6 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -40,6 +40,8 @@
import android.view.RoundedCorners
import android.view.Surface
import androidx.annotation.VisibleForTesting
+import com.android.systemui.util.asIndenting
+import java.io.PrintWriter
import kotlin.math.ceil
import kotlin.math.floor
@@ -47,8 +49,8 @@
* When the HWC of the device supports Composition.DISPLAY_DECORATION, we use this layer to draw
* screen decorations.
*/
-class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport)
- : DisplayCutoutBaseView(context) {
+class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport) :
+ DisplayCutoutBaseView(context) {
val colorMode: Int
private val useInvertedAlphaColor: Boolean
private val color: Int
@@ -406,6 +408,20 @@
invalidate()
}
+ override fun dump(pw: PrintWriter) {
+ val ipw = pw.asIndenting()
+ ipw.increaseIndent()
+ ipw.println("ScreenDecorHwcLayer:")
+ super.dump(pw)
+ ipw.println("this=$this")
+ ipw.println("transparentRect=$transparentRect")
+ ipw.println("hasTopRoundedCorner=$hasTopRoundedCorner")
+ ipw.println("hasBottomRoundedCorner=$hasBottomRoundedCorner")
+ ipw.println("roundedCornerTopSize=$roundedCornerTopSize")
+ ipw.println("roundedCornerBottomSize=$roundedCornerBottomSize")
+ ipw.decreaseIndent()
+ }
+
companion object {
private val DEBUG_COLOR = ScreenDecorations.DEBUG_COLOR
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index fb65588..adc0412 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -23,6 +23,8 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static com.android.systemui.util.DumpUtilsKt.asIndenting;
+
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,6 +46,7 @@
import android.os.Trace;
import android.provider.Settings.Secure;
import android.util.DisplayUtils;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Size;
import android.view.Display;
@@ -1005,39 +1008,55 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("ScreenDecorations state:");
- pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
+ IndentingPrintWriter ipw = asIndenting(pw);
+ ipw.increaseIndent();
+ ipw.println("DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS);
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
return;
}
- pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
- pw.println(" shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility());
+ ipw.println("mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
+ ipw.println("shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility());
final boolean supportsShowingFaceScanningAnim = mFaceScanningFactory.getHasProviders();
- pw.println(" supportsShowingFaceScanningAnim:" + supportsShowingFaceScanningAnim);
+ ipw.println("supportsShowingFaceScanningAnim:" + supportsShowingFaceScanningAnim);
if (supportsShowingFaceScanningAnim) {
- pw.println(" canShowFaceScanningAnim:"
+ ipw.increaseIndent();
+ ipw.println("canShowFaceScanningAnim:"
+ mFaceScanningFactory.canShowFaceScanningAnim());
- pw.println(" shouldShowFaceScanningAnim (at time dump was taken):"
+ ipw.println("shouldShowFaceScanningAnim (at time dump was taken):"
+ mFaceScanningFactory.shouldShowFaceScanningAnim());
+ ipw.decreaseIndent();
}
- pw.println(" mPendingConfigChange:" + mPendingConfigChange);
+ FaceScanningOverlay faceScanningOverlay =
+ (FaceScanningOverlay) getOverlayView(mFaceScanningViewId);
+ if (faceScanningOverlay != null) {
+ faceScanningOverlay.dump(ipw);
+ }
+ ipw.println("mPendingConfigChange:" + mPendingConfigChange);
if (mHwcScreenDecorationSupport != null) {
- pw.println(" mHwcScreenDecorationSupport:");
- pw.println(" format="
+ ipw.increaseIndent();
+ ipw.println("mHwcScreenDecorationSupport:");
+ ipw.increaseIndent();
+ ipw.println("format="
+ PixelFormat.formatToString(mHwcScreenDecorationSupport.format));
- pw.println(" alphaInterpretation="
+ ipw.println("alphaInterpretation="
+ alphaInterpretationToString(mHwcScreenDecorationSupport.alphaInterpretation));
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
} else {
- pw.println(" mHwcScreenDecorationSupport: null");
+ ipw.increaseIndent();
+ pw.println("mHwcScreenDecorationSupport: null");
+ ipw.decreaseIndent();
}
if (mScreenDecorHwcLayer != null) {
- pw.println(" mScreenDecorHwcLayer:");
- pw.println(" transparentRegion=" + mScreenDecorHwcLayer.transparentRect);
+ ipw.increaseIndent();
+ mScreenDecorHwcLayer.dump(ipw);
+ ipw.decreaseIndent();
} else {
- pw.println(" mScreenDecorHwcLayer: null");
+ ipw.println("mScreenDecorHwcLayer: null");
}
if (mOverlays != null) {
- pw.println(" mOverlays(left,top,right,bottom)=("
+ ipw.println("mOverlays(left,top,right,bottom)=("
+ (mOverlays[BOUNDS_POSITION_LEFT] != null) + ","
+ (mOverlays[BOUNDS_POSITION_TOP] != null) + ","
+ (mOverlays[BOUNDS_POSITION_RIGHT] != null) + ","
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 873a695..64a9cc9 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -16,6 +16,10 @@
package com.android.systemui;
+import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X;
+import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_Y;
+import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
+
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import android.animation.Animator;
@@ -40,6 +44,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -47,14 +52,14 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.animation.PhysicsAnimator.SpringConfig;
import java.util.function.Consumer;
public class SwipeHelper implements Gefingerpoken {
static final String TAG = "com.android.systemui.SwipeHelper";
- private static final boolean DEBUG = false;
private static final boolean DEBUG_INVALIDATE = false;
- private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
private static final boolean CONSTRAIN_SWIPE = true;
private static final boolean FADE_OUT_DURING_SWIPE = true;
private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
@@ -66,7 +71,6 @@
private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
- private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
public static final float SWIPE_PROGRESS_FADE_END = 0.6f; // fraction of thumbnail width
// beyond which swipe progress->0
@@ -78,6 +82,9 @@
private float mMinSwipeProgress = 0f;
private float mMaxSwipeProgress = 1f;
+ private final SpringConfig mSnapBackSpringConfig =
+ new SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+
private final FlingAnimationUtils mFlingAnimationUtils;
private float mPagingTouchSlop;
private final float mSlopMultiplier;
@@ -188,23 +195,27 @@
vt.getYVelocity();
}
- protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
- ObjectAnimator anim = ObjectAnimator.ofFloat(v,
- mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
- return anim;
- }
-
- private float getPerpendicularVelocity(VelocityTracker vt) {
- return mSwipeDirection == X ? vt.getYVelocity() :
- vt.getXVelocity();
- }
-
- protected Animator getViewTranslationAnimator(View v, float target,
+ protected Animator getViewTranslationAnimator(View view, float target,
AnimatorUpdateListener listener) {
- ObjectAnimator anim = createTranslationAnimation(v, target);
+
+ cancelSnapbackAnimation(view);
+
+ if (view instanceof ExpandableNotificationRow) {
+ return ((ExpandableNotificationRow) view).getTranslateViewAnimator(target, listener);
+ }
+
+ return createTranslationAnimation(view, target, listener);
+ }
+
+ protected Animator createTranslationAnimation(View view, float newPos,
+ AnimatorUpdateListener listener) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(view,
+ mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
+
if (listener != null) {
anim.addUpdateListener(listener);
}
+
return anim;
}
@@ -327,6 +338,7 @@
mTouchedView = mCallback.getChildAtPosition(ev);
if (mTouchedView != null) {
+ cancelSnapbackAnimation(mTouchedView);
onDownUpdate(mTouchedView, ev);
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mTouchedView);
mVelocityTracker.addMovement(ev);
@@ -526,47 +538,59 @@
}
/**
- * After snapChild() and related animation finished, this function will be called.
+ * Starts a snapback animation and cancels any previous translate animations on the given view.
+ *
+ * @param animView view to animate
+ * @param targetLeft the end position of the translation
+ * @param velocity the initial velocity of the animation
*/
- protected void onSnapChildWithAnimationFinished() {}
-
- public void snapChild(final View animView, final float targetLeft, float velocity) {
+ protected void snapChild(final View animView, final float targetLeft, float velocity) {
final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
- AnimatorUpdateListener updateListener = animation -> onTranslationUpdate(animView,
- (float) animation.getAnimatedValue(), canBeDismissed);
- Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
- if (anim == null) {
- onSnapChildWithAnimationFinished();
- return;
- }
- anim.addListener(new AnimatorListenerAdapter() {
- boolean wasCancelled = false;
+ cancelTranslateAnimation(animView);
- @Override
- public void onAnimationCancel(Animator animator) {
- wasCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- mSnappingChild = false;
- if (!wasCancelled) {
- updateSwipeProgressFromOffset(animView, canBeDismissed);
- resetSwipeState();
- }
- onSnapChildWithAnimationFinished();
- }
+ PhysicsAnimator<? extends View> anim =
+ createSnapBackAnimation(animView, targetLeft, velocity);
+ anim.addUpdateListener((target, values) -> {
+ onTranslationUpdate(target, getTranslation(target), canBeDismissed);
});
- prepareSnapBackAnimation(animView, anim);
+ anim.addEndListener((t, p, wasFling, cancelled, finalValue, finalVelocity, allEnded) -> {
+ mSnappingChild = false;
+
+ if (!cancelled) {
+ updateSwipeProgressFromOffset(animView, canBeDismissed);
+ resetSwipeState();
+ }
+ onChildSnappedBack(animView, targetLeft);
+ });
mSnappingChild = true;
- float maxDistance = Math.abs(targetLeft - getTranslation(animView));
- mFlingAnimationUtils.apply(anim, getTranslation(animView), targetLeft, velocity,
- maxDistance);
anim.start();
- mCallback.onChildSnappedBack(animView, targetLeft);
}
+ private PhysicsAnimator<? extends View> createSnapBackAnimation(View target, float toPosition,
+ float startVelocity) {
+ if (target instanceof ExpandableNotificationRow) {
+ return PhysicsAnimator.getInstance((ExpandableNotificationRow) target).spring(
+ createFloatPropertyCompat(ExpandableNotificationRow.TRANSLATE_CONTENT),
+ toPosition,
+ startVelocity,
+ mSnapBackSpringConfig);
+ }
+ return PhysicsAnimator.getInstance(target).spring(
+ mSwipeDirection == X ? TRANSLATION_X : TRANSLATION_Y, toPosition, startVelocity,
+ mSnapBackSpringConfig);
+ }
+
+ private void cancelTranslateAnimation(View animView) {
+ if (animView instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) animView).cancelTranslateAnimation();
+ }
+ cancelSnapbackAnimation(animView);
+ }
+
+ private void cancelSnapbackAnimation(View target) {
+ PhysicsAnimator.getInstance(target).cancel();
+ }
/**
* Called to update the content alpha while the view is swiped
@@ -576,17 +600,10 @@
}
/**
- * Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have
- * to tell us what to do
+ * Called after {@link #snapChild(View, float, float)} and its related animation has finished.
*/
protected void onChildSnappedBack(View animView, float targetLeft) {
- }
-
- /**
- * Called to update the snap back animation.
- */
- protected void prepareSnapBackAnimation(View view, Animator anim) {
- // Do nothing
+ mCallback.onChildSnappedBack(animView, targetLeft);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 46e945b..3f22f18 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -103,11 +103,6 @@
}
}
- override fun onInit() {
- mView.setAlphaInDuration(sysuiContext.resources.getInteger(
- R.integer.auth_ripple_alpha_in_duration).toLong())
- }
-
@VisibleForTesting
public override fun onViewAttached() {
authController.addCallback(authControllerCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 8409462..b007134 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -54,12 +54,11 @@
private var lockScreenColorVal = Color.WHITE
private val fadeDuration = 83L
private val retractDuration = 400L
- private var alphaInDuration: Long = 0
private val dwellShader = DwellRippleShader()
private val dwellPaint = Paint()
private val rippleShader = RippleShader()
private val ripplePaint = Paint()
- private var unlockedRippleAnimator: AnimatorSet? = null
+ private var unlockedRippleAnimator: Animator? = null
private var fadeDwellAnimator: Animator? = null
private var retractDwellAnimator: Animator? = null
private var dwellPulseOutAnimator: Animator? = null
@@ -85,12 +84,12 @@
}
init {
- rippleShader.color = 0xffffffff.toInt() // default color
rippleShader.rawProgress = 0f
rippleShader.pixelDensity = resources.displayMetrics.density
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
updateRippleFadeParams()
ripplePaint.shader = rippleShader
+ setLockScreenColor(0xffffffff.toInt()) // default color
dwellShader.color = 0xffffffff.toInt() // default color
dwellShader.progress = 0f
@@ -111,10 +110,6 @@
dwellRadius = sensorRadius * 1.5f
}
- fun setAlphaInDuration(duration: Long) {
- alphaInDuration = duration
- }
-
/**
* Animate dwell ripple inwards back to radius 0
*/
@@ -253,7 +248,6 @@
override fun onAnimationEnd(animation: Animator?) {
drawDwell = false
- resetRippleAlpha()
}
})
start()
@@ -277,22 +271,7 @@
}
}
- val alphaInAnimator = ValueAnimator.ofInt(0, 62).apply {
- duration = alphaInDuration
- addUpdateListener { animator ->
- rippleShader.color = ColorUtils.setAlphaComponent(
- rippleShader.color,
- animator.animatedValue as Int
- )
- invalidate()
- }
- }
-
- unlockedRippleAnimator = AnimatorSet().apply {
- playTogether(
- rippleAnimator,
- alphaInAnimator
- )
+ unlockedRippleAnimator = rippleAnimator.apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
drawRipple = true
@@ -310,17 +289,12 @@
unlockedRippleAnimator?.start()
}
- fun resetRippleAlpha() {
- rippleShader.color = ColorUtils.setAlphaComponent(
- rippleShader.color,
- 255
- )
- }
-
fun setLockScreenColor(color: Int) {
lockScreenColorVal = color
- rippleShader.color = lockScreenColorVal
- resetRippleAlpha()
+ rippleShader.color = ColorUtils.setAlphaComponent(
+ lockScreenColorVal,
+ 62
+ )
}
fun updateDwellRippleColor(isDozing: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 61d039b..c98a62f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -195,7 +195,7 @@
scope.launch {
alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
if (isVisible) {
- show(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
+ show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
} else {
hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
}
@@ -436,13 +436,17 @@
@BiometricOverlayConstants.ShowReason reason: Int
) {
fun update() {
- val c = context.getColor(R.color.biometric_dialog_accent)
- val chevronFill = context.getColor(R.color.sfps_chevron_fill)
val isKeyguard = reason == REASON_AUTH_KEYGUARD
if (isKeyguard) {
+ val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
+ val chevronFill =
+ com.android.settingslib.Utils.getColorAttrDefaultColor(
+ context,
+ android.R.attr.textColorPrimaryInverse
+ )
for (key in listOf(".blue600", ".blue400")) {
addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
+ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
}
}
addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
deleted file mode 100644
index 079c0b3..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (C) 2022 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.biometrics
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.graphics.PixelFormat
-import android.graphics.Point
-import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
-import android.hardware.fingerprint.IUdfpsOverlay
-import android.os.Handler
-import android.provider.Settings
-import android.view.MotionEvent
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsOverlayParams
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.concurrency.Execution
-import java.util.Optional
-import java.util.concurrent.Executor
-import javax.inject.Inject
-import javax.inject.Provider
-import kotlin.math.cos
-import kotlin.math.pow
-import kotlin.math.sin
-
-private const val TAG = "UdfpsOverlay"
-
-const val SETTING_OVERLAY_DEBUG = "udfps_overlay_debug"
-
-// Number of sensor points needed inside ellipse for good overlap
-private const val NEEDED_POINTS = 2
-
-@SuppressLint("ClickableViewAccessibility")
-@SysUISingleton
-class UdfpsOverlay
-@Inject
-constructor(
- private val context: Context,
- private val execution: Execution,
- private val windowManager: WindowManager,
- private val fingerprintManager: FingerprintManager?,
- private val handler: Handler,
- private val biometricExecutor: Executor,
- private val alternateTouchProvider: Optional<Provider<AlternateUdfpsTouchProvider>>,
- @Main private val fgExecutor: DelayableExecutor,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val authController: AuthController,
- private val udfpsLogger: UdfpsLogger,
- private var featureFlags: FeatureFlags
-) : CoreStartable {
-
- /** The view, when [isShowing], or null. */
- var overlayView: UdfpsOverlayView? = null
- private set
-
- private var requestId: Long = 0
- private var onFingerDown = false
- val size = windowManager.maximumWindowMetrics.bounds
-
- val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf()
- var points: Array<Point> = emptyArray()
- var processedMotionEvent = false
- var isShowing = false
-
- private var params: UdfpsOverlayParams = UdfpsOverlayParams()
-
- private val coreLayoutParams =
- WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
- 0 /* flags set in computeLayoutParams() */,
- PixelFormat.TRANSLUCENT
- )
- .apply {
- title = TAG
- fitInsetsTypes = 0
- gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
- layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- flags = Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS
- privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
- // Avoid announcing window title.
- accessibilityTitle = " "
- inputFeatures = INPUT_FEATURE_SPY
- }
-
- fun onTouch(event: MotionEvent): Boolean {
- val view = overlayView!!
-
- return when (event.action) {
- MotionEvent.ACTION_DOWN,
- MotionEvent.ACTION_MOVE -> {
- onFingerDown = true
- if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
- view.processMotionEvent(event)
-
- val goodOverlap =
- if (featureFlags.isEnabled(Flags.NEW_ELLIPSE_DETECTION)) {
- isGoodEllipseOverlap(event)
- } else {
- isGoodCentroidOverlap(event)
- }
-
- if (!processedMotionEvent && goodOverlap) {
- biometricExecutor.execute {
- alternateTouchProvider
- .map(Provider<AlternateUdfpsTouchProvider>::get)
- .get()
- .onPointerDown(
- requestId,
- event.rawX.toInt(),
- event.rawY.toInt(),
- event.touchMinor,
- event.touchMajor
- )
- }
- fgExecutor.execute {
- if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
- keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
- }
-
- view.configureDisplay {
- biometricExecutor.execute {
- alternateTouchProvider
- .map(Provider<AlternateUdfpsTouchProvider>::get)
- .get()
- .onUiReady()
- }
- }
-
- processedMotionEvent = true
- }
- }
-
- view.invalidate()
- }
- true
- }
- MotionEvent.ACTION_UP,
- MotionEvent.ACTION_CANCEL -> {
- if (processedMotionEvent && alternateTouchProvider.isPresent) {
- biometricExecutor.execute {
- alternateTouchProvider
- .map(Provider<AlternateUdfpsTouchProvider>::get)
- .get()
- .onPointerUp(requestId)
- }
- fgExecutor.execute {
- if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
- keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
- }
- }
-
- processedMotionEvent = false
- }
-
- if (view.isDisplayConfigured) {
- view.unconfigureDisplay()
- }
-
- view.invalidate()
- true
- }
- else -> false
- }
- }
-
- fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
- return points.count { checkPoint(event, it) } >= NEEDED_POINTS
- }
-
- fun isGoodCentroidOverlap(event: MotionEvent): Boolean {
- return params.sensorBounds.contains(event.rawX.toInt(), event.rawY.toInt())
- }
-
- fun checkPoint(event: MotionEvent, point: Point): Boolean {
- // Calculate if sensor point is within ellipse
- // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
- // yS))^2 / b^2) <= 1
- val a: Float = cos(event.orientation) * (point.x - event.rawX)
- val b: Float = sin(event.orientation) * (point.y - event.rawY)
- val c: Float = sin(event.orientation) * (point.x - event.rawX)
- val d: Float = cos(event.orientation) * (point.y - event.rawY)
- val result =
- (a + b).pow(2) / (event.touchMinor / 2).pow(2) +
- (c - d).pow(2) / (event.touchMajor / 2).pow(2)
-
- return result <= 1
- }
-
- fun show(requestId: Long) {
- if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
- return
- }
-
- this.requestId = requestId
- fgExecutor.execute {
- if (overlayView == null && alternateTouchProvider.isPresent) {
- UdfpsOverlayView(context, null).let {
- it.overlayParams = params
- it.setUdfpsDisplayMode(
- UdfpsDisplayMode(context, execution, authController, udfpsLogger)
- )
- it.setOnTouchListener { _, event -> onTouch(event) }
- it.sensorPoints = points
- it.debugOverlay =
- Settings.Global.getInt(
- context.contentResolver,
- SETTING_OVERLAY_DEBUG,
- 0 /* def */
- ) != 0
- overlayView = it
- }
- windowManager.addView(overlayView, coreLayoutParams)
- isShowing = true
- }
- }
- }
-
- fun hide() {
- if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
- return
- }
-
- fgExecutor.execute {
- if (overlayView != null && isShowing && alternateTouchProvider.isPresent) {
- if (processedMotionEvent) {
- biometricExecutor.execute {
- alternateTouchProvider
- .map(Provider<AlternateUdfpsTouchProvider>::get)
- .get()
- .onPointerUp(requestId)
- }
- fgExecutor.execute {
- if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
- keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
- }
- }
- }
-
- if (overlayView!!.isDisplayConfigured) {
- overlayView!!.unconfigureDisplay()
- }
-
- overlayView?.apply {
- windowManager.removeView(this)
- setOnTouchListener(null)
- }
-
- isShowing = false
- overlayView = null
- processedMotionEvent = false
- }
- }
- }
-
- @Override
- override fun start() {
- fingerprintManager?.addAuthenticatorsRegisteredCallback(
- object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- override fun onAllAuthenticatorsRegistered(
- sensors: List<FingerprintSensorPropertiesInternal>
- ) {
- handler.post { handleAllFingerprintAuthenticatorsRegistered(sensors) }
- }
- }
- )
-
- fingerprintManager?.setUdfpsOverlay(
- object : IUdfpsOverlay.Stub() {
- override fun show(
- requestId: Long,
- sensorId: Int,
- @BiometricOverlayConstants.ShowReason reason: Int
- ) = show(requestId)
-
- override fun hide(sensorId: Int) = hide()
- }
- )
- }
-
- private fun handleAllFingerprintAuthenticatorsRegistered(
- sensors: List<FingerprintSensorPropertiesInternal>
- ) {
- for (props in sensors) {
- if (props.isAnyUdfpsType) {
- udfpsProps.add(props)
- }
- }
-
- // Setup param size
- if (udfpsProps.isNotEmpty()) {
- params =
- UdfpsOverlayParams(
- sensorBounds = udfpsProps[0].location.rect,
- overlayBounds = Rect(0, size.height() / 2, size.width(), size.height()),
- naturalDisplayWidth = size.width(),
- naturalDisplayHeight = size.height(),
- scaleFactor = 1f
- )
-
- val sensorX = params.sensorBounds.centerX()
- val sensorY = params.sensorBounds.centerY()
- val cornerOffset: Int = params.sensorBounds.width() / 4
- val sideOffset: Int = params.sensorBounds.width() / 3
-
- points =
- arrayOf(
- Point(sensorX - cornerOffset, sensorY - cornerOffset),
- Point(sensorX, sensorY - sideOffset),
- Point(sensorX + cornerOffset, sensorY - cornerOffset),
- Point(sensorX - sideOffset, sensorY),
- Point(sensorX, sensorY),
- Point(sensorX + sideOffset, sensorY),
- Point(sensorX - cornerOffset, sensorY + cornerOffset),
- Point(sensorX, sensorY + sideOffset),
- Point(sensorX + cornerOffset, sensorY + cornerOffset)
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
deleted file mode 100644
index 28ca41d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2022 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.biometrics
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Point
-import android.graphics.RectF
-import android.util.AttributeSet
-import android.view.MotionEvent
-import android.widget.FrameLayout
-import com.android.settingslib.udfps.UdfpsOverlayParams
-
-private const val TAG = "UdfpsOverlayView"
-private const val POINT_SIZE = 10f
-
-class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
- var overlayParams = UdfpsOverlayParams()
- private var mUdfpsDisplayMode: UdfpsDisplayMode? = null
-
- var debugOverlay = false
-
- var overlayPaint = Paint()
- var sensorPaint = Paint()
- var touchPaint = Paint()
- var pointPaint = Paint()
- val centerPaint = Paint()
-
- var oval = RectF()
-
- /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
- var isDisplayConfigured: Boolean = false
- private set
-
- var touchX: Float = 0f
- var touchY: Float = 0f
- var touchMinor: Float = 0f
- var touchMajor: Float = 0f
- var touchOrientation: Double = 0.0
-
- var sensorPoints: Array<Point>? = null
-
- init {
- this.setWillNotDraw(false)
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
-
- overlayPaint.color = Color.argb(100, 255, 0, 0)
- overlayPaint.style = Paint.Style.FILL
-
- touchPaint.color = Color.argb(200, 255, 255, 255)
- touchPaint.style = Paint.Style.FILL
-
- sensorPaint.color = Color.argb(150, 134, 204, 255)
- sensorPaint.style = Paint.Style.FILL
-
- pointPaint.color = Color.WHITE
- pointPaint.style = Paint.Style.FILL
- }
-
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
-
- if (debugOverlay) {
- // Draw overlay and sensor bounds
- canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
- canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
- }
-
- // Draw sensor circle
- canvas.drawCircle(
- overlayParams.sensorBounds.exactCenterX(),
- overlayParams.sensorBounds.exactCenterY(),
- overlayParams.sensorBounds.width().toFloat() / 2,
- centerPaint
- )
-
- if (debugOverlay) {
- // Draw Points
- sensorPoints?.forEach {
- canvas.drawCircle(it.x.toFloat(), it.y.toFloat(), POINT_SIZE, pointPaint)
- }
-
- // Draw touch oval
- canvas.save()
- canvas.rotate(Math.toDegrees(touchOrientation).toFloat(), touchX, touchY)
-
- oval.setEmpty()
- oval.set(
- touchX - touchMinor / 2,
- touchY + touchMajor / 2,
- touchX + touchMinor / 2,
- touchY - touchMajor / 2
- )
-
- canvas.drawOval(oval, touchPaint)
-
- // Draw center point
- canvas.drawCircle(touchX, touchY, POINT_SIZE, centerPaint)
- canvas.restore()
- }
- }
-
- fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) {
- mUdfpsDisplayMode = udfpsDisplayMode
- }
-
- fun configureDisplay(onDisplayConfigured: Runnable) {
- isDisplayConfigured = true
- mUdfpsDisplayMode?.enable(onDisplayConfigured)
- }
-
- fun unconfigureDisplay() {
- isDisplayConfigured = false
- mUdfpsDisplayMode?.disable(null /* onDisabled */)
- }
-
- fun processMotionEvent(event: MotionEvent) {
- touchX = event.rawX
- touchY = event.rawY
- touchMinor = event.touchMinor
- touchMajor = event.touchMajor
- touchOrientation = event.orientation.toDouble()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index fca4cf9..e3dbcb5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -50,8 +50,7 @@
*/
@SysUISingleton
class UdfpsShell @Inject constructor(
- commandRegistry: CommandRegistry,
- private val udfpsOverlay: UdfpsOverlay
+ commandRegistry: CommandRegistry
) : Command {
/**
@@ -69,10 +68,6 @@
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.size == 1 && args[0] == "hide") {
hideOverlay()
- } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "show") {
- showUdfpsOverlay()
- } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "hide") {
- hideUdfpsOverlay()
} else if (args.size == 2 && args[0] == "show") {
showOverlay(getEnrollmentReason(args[1]))
} else if (args.size == 1 && args[0] == "onUiReady") {
@@ -131,16 +126,6 @@
)
}
- private fun showUdfpsOverlay() {
- Log.v(TAG, "showUdfpsOverlay")
- udfpsOverlay.show(REQUEST_ID)
- }
-
- private fun hideUdfpsOverlay() {
- Log.v(TAG, "hideUdfpsOverlay")
- udfpsOverlay.hide()
- }
-
private fun hideOverlay() {
udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
index 653c12e..25b1e3a 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
@@ -16,8 +16,15 @@
package com.android.systemui.bluetooth;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,11 +35,18 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.MediaOutputConstants;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.media.controls.util.MediaDataUtils;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
/**
* Dialog for showing le audio broadcasting dialog.
*/
@@ -40,17 +54,91 @@
private static final String TAG = "BroadcastDialog";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int HANDLE_BROADCAST_FAILED_DELAY = 3000;
+
+ private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private Context mContext;
private UiEventLogger mUiEventLogger;
@VisibleForTesting
protected View mDialogView;
private MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private BroadcastSender mBroadcastSender;
private String mCurrentBroadcastApp;
private String mOutputPackageName;
+ private Executor mExecutor;
+ private boolean mShouldLaunchLeBroadcastDialog;
+ private Button mSwitchBroadcast;
+
+ private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ new BluetoothLeBroadcast.Callback() {
+ @Override
+ public void onBroadcastStarted(int reason, int broadcastId) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStarted(), reason = " + reason
+ + ", broadcastId = " + broadcastId);
+ }
+ mMainThreadHandler.post(() -> handleLeBroadcastStarted());
+ }
+
+ @Override
+ public void onBroadcastStartFailed(int reason) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
+ }
+ mMainThreadHandler.postDelayed(() -> handleLeBroadcastStartFailed(),
+ HANDLE_BROADCAST_FAILED_DELAY);
+ }
+
+ @Override
+ public void onBroadcastMetadataChanged(int broadcastId,
+ @NonNull BluetoothLeBroadcastMetadata metadata) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId
+ + ", metadata = " + metadata);
+ }
+ mMainThreadHandler.post(() -> handleLeBroadcastMetadataChanged());
+ }
+
+ @Override
+ public void onBroadcastStopped(int reason, int broadcastId) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStopped(), reason = " + reason
+ + ", broadcastId = " + broadcastId);
+ }
+ mMainThreadHandler.post(() -> handleLeBroadcastStopped());
+ }
+
+ @Override
+ public void onBroadcastStopFailed(int reason) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+ }
+ mMainThreadHandler.postDelayed(() -> handleLeBroadcastStopFailed(),
+ HANDLE_BROADCAST_FAILED_DELAY);
+ }
+
+ @Override
+ public void onBroadcastUpdated(int reason, int broadcastId) {
+ }
+
+ @Override
+ public void onBroadcastUpdateFailed(int reason, int broadcastId) {
+ }
+
+ @Override
+ public void onPlaybackStarted(int reason, int broadcastId) {
+ }
+
+ @Override
+ public void onPlaybackStopped(int reason, int broadcastId) {
+ }
+ };
public BroadcastDialog(Context context, MediaOutputDialogFactory mediaOutputDialogFactory,
- String currentBroadcastApp, String outputPkgName, UiEventLogger uiEventLogger) {
+ LocalBluetoothManager localBluetoothManager, String currentBroadcastApp,
+ String outputPkgName, UiEventLogger uiEventLogger, BroadcastSender broadcastSender) {
super(context);
if (DEBUG) {
Log.d(TAG, "Init BroadcastDialog");
@@ -58,9 +146,18 @@
mContext = getContext();
mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mLocalBluetoothManager = localBluetoothManager;
mCurrentBroadcastApp = currentBroadcastApp;
mOutputPackageName = outputPkgName;
mUiEventLogger = uiEventLogger;
+ mExecutor = Executors.newSingleThreadExecutor();
+ mBroadcastSender = broadcastSender;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ registerBroadcastCallBack(mExecutor, mBroadcastCallback);
}
@Override
@@ -84,11 +181,12 @@
subTitle.setText(mContext.getString(
R.string.bt_le_audio_broadcast_dialog_sub_title, switchBroadcastApp));
- Button switchBroadcast = mDialogView.requireViewById(R.id.switch_broadcast);
+ mSwitchBroadcast = mDialogView.requireViewById(R.id.switch_broadcast);
Button changeOutput = mDialogView.requireViewById(R.id.change_output);
Button cancelBtn = mDialogView.requireViewById(R.id.cancel);
- switchBroadcast.setText(mContext.getString(
- R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp));
+ mSwitchBroadcast.setText(mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
+ mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
changeOutput.setOnClickListener((view) -> {
mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
dismiss();
@@ -102,6 +200,79 @@
}
@Override
+ public void onStop() {
+ super.onStop();
+ unregisterBroadcastCallBack(mBroadcastCallback);
+ }
+
+ void refreshSwitchBroadcastButton() {
+ String switchBroadcastApp = MediaDataUtils.getAppLabel(mContext, mOutputPackageName,
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name));
+ mSwitchBroadcast.setText(mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
+ mSwitchBroadcast.setEnabled(true);
+ }
+
+ private void startSwitchBroadcast() {
+ if (DEBUG) {
+ Log.d(TAG, "startSwitchBroadcast");
+ }
+ mSwitchBroadcast.setText(R.string.media_output_broadcast_starting);
+ mSwitchBroadcast.setEnabled(false);
+ //Stop the current Broadcast
+ if (!stopBluetoothLeBroadcast()) {
+ handleLeBroadcastStopFailed();
+ return;
+ }
+ }
+
+ private void registerBroadcastCallBack(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothLeBroadcast.Callback callback) {
+ LocalBluetoothLeBroadcast broadcast =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (broadcast == null) {
+ Log.d(TAG, "The broadcast profile is null");
+ return;
+ }
+ broadcast.registerServiceCallBack(executor, callback);
+ }
+
+ private void unregisterBroadcastCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
+ LocalBluetoothLeBroadcast broadcast =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (broadcast == null) {
+ Log.d(TAG, "The broadcast profile is null");
+ return;
+ }
+ broadcast.unregisterServiceCallBack(callback);
+ }
+
+ boolean startBluetoothLeBroadcast() {
+ LocalBluetoothLeBroadcast broadcast =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (broadcast == null) {
+ Log.d(TAG, "The broadcast profile is null");
+ return false;
+ }
+ String switchBroadcastApp = MediaDataUtils.getAppLabel(mContext, mOutputPackageName,
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name));
+ broadcast.startBroadcast(switchBroadcastApp, /*language*/ null);
+ return true;
+ }
+
+ boolean stopBluetoothLeBroadcast() {
+ LocalBluetoothLeBroadcast broadcast =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (broadcast == null) {
+ Log.d(TAG, "The broadcast profile is null");
+ return false;
+ }
+ broadcast.stopLatestBroadcast();
+ return true;
+ }
+
+ @Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (!hasFocus && isShowing()) {
@@ -125,4 +296,45 @@
}
}
+ void handleLeBroadcastStarted() {
+ // Waiting for the onBroadcastMetadataChanged. The UI launchs the broadcast dialog when
+ // the metadata is ready.
+ mShouldLaunchLeBroadcastDialog = true;
+ }
+
+ private void handleLeBroadcastStartFailed() {
+ mSwitchBroadcast.setText(R.string.media_output_broadcast_start_failed);
+ mSwitchBroadcast.setEnabled(false);
+ refreshSwitchBroadcastButton();
+ }
+
+ void handleLeBroadcastMetadataChanged() {
+ if (mShouldLaunchLeBroadcastDialog) {
+ startLeBroadcastDialog();
+ mShouldLaunchLeBroadcastDialog = false;
+ }
+ }
+
+ @VisibleForTesting
+ void handleLeBroadcastStopped() {
+ mShouldLaunchLeBroadcastDialog = false;
+ if (!startBluetoothLeBroadcast()) {
+ handleLeBroadcastStartFailed();
+ return;
+ }
+ }
+
+ private void handleLeBroadcastStopFailed() {
+ mSwitchBroadcast.setText(R.string.media_output_broadcast_start_failed);
+ mSwitchBroadcast.setEnabled(false);
+ refreshSwitchBroadcastButton();
+ }
+
+ private void startLeBroadcastDialog() {
+ mBroadcastSender.sendBroadcast(new Intent()
+ .setPackage(mContext.getPackageName())
+ .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG)
+ .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, mOutputPackageName));
+ dismiss();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
index 1b699e8..17bf1a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
@@ -16,11 +16,14 @@
package com.android.systemui.bluetooth;
+import android.annotation.Nullable;
import android.content.Context;
import android.view.View;
import com.android.internal.logging.UiEventLogger;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -36,15 +39,21 @@
private UiEventLogger mUiEventLogger;
private DialogLaunchAnimator mDialogLaunchAnimator;
private MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final LocalBluetoothManager mLocalBluetoothManager;
+ private BroadcastSender mBroadcastSender;
@Inject
public BroadcastDialogController(Context context, UiEventLogger uiEventLogger,
DialogLaunchAnimator dialogLaunchAnimator,
- MediaOutputDialogFactory mediaOutputDialogFactory) {
+ MediaOutputDialogFactory mediaOutputDialogFactory,
+ @Nullable LocalBluetoothManager localBluetoothManager,
+ BroadcastSender broadcastSender) {
mContext = context;
mUiEventLogger = uiEventLogger;
mDialogLaunchAnimator = dialogLaunchAnimator;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mLocalBluetoothManager = localBluetoothManager;
+ mBroadcastSender = broadcastSender;
}
/** Creates a [BroadcastDialog] for the user to switch broadcast or change the output device
@@ -55,7 +64,8 @@
public void createBroadcastDialog(String currentBroadcastAppName, String outputPkgName,
boolean aboveStatusBar, View view) {
BroadcastDialog broadcastDialog = new BroadcastDialog(mContext, mMediaOutputDialogFactory,
- currentBroadcastAppName, outputPkgName, mUiEventLogger);
+ mLocalBluetoothManager, currentBroadcastAppName, outputPkgName, mUiEventLogger,
+ mBroadcastSender);
if (view != null) {
mDialogLaunchAnimator.showFromView(broadcastDialog, view);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
index 0ed7d27..e9daa46 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
@@ -41,6 +41,7 @@
}
mCopiedToast = Toast.makeText(mContext,
R.string.clipboard_overlay_text_copied, Toast.LENGTH_SHORT);
+ mCopiedToast.addCallback(this);
mCopiedToast.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
index 5dabbbb..6a6c3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -16,10 +16,10 @@
package com.android.systemui.common.shared.model
-import androidx.annotation.AttrRes
+import androidx.annotation.ColorRes
/** Models an icon with a specific tint. */
data class TintedIcon(
val icon: Icon,
- @AttrRes val tintAttr: Int?,
+ @ColorRes val tint: Int?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
index dea8cfd..bcc5932 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -17,15 +17,14 @@
package com.android.systemui.common.ui.binder
import android.widget.ImageView
-import com.android.settingslib.Utils
import com.android.systemui.common.shared.model.TintedIcon
object TintedIconViewBinder {
/**
* Binds the given tinted icon to the view.
*
- * [TintedIcon.tintAttr] will always be applied, meaning that if it is null, then the tint
- * *will* be reset to null.
+ * [TintedIcon.tint] will always be applied, meaning that if it is null, then the tint *will* be
+ * reset to null.
*/
fun bind(
tintedIcon: TintedIcon,
@@ -33,8 +32,8 @@
) {
IconViewBinder.bind(tintedIcon.icon, view)
view.imageTintList =
- if (tintedIcon.tintAttr != null) {
- Utils.getColorAttr(view.context, tintedIcon.tintAttr)
+ if (tintedIcon.tint != null) {
+ view.resources.getColorStateList(tintedIcon.tint, view.context.theme)
} else {
null
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index ee12db8..868e527 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -608,6 +608,7 @@
if (items.size == 1) {
spinner.setBackground(null)
anchor.setOnClickListener(null)
+ anchor.isClickable = false
return
} else {
spinner.background = parent.context.resources
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index b86d419..9bf6b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -25,7 +25,6 @@
import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.biometrics.UdfpsOverlay
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
@@ -229,12 +228,6 @@
@ClassKey(KeyguardLiftController::class)
abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
- /** Inject into UdfpsOverlay. */
- @Binds
- @IntoMap
- @ClassKey(UdfpsOverlay::class)
- abstract fun bindUdfpsOverlay(sysui: UdfpsOverlay): CoreStartable
-
/** Inject into MediaTttSenderCoordinator. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index fc3263f..f0aefb5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -398,7 +398,8 @@
}
if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
|| mState == State.DOZE_AOD || mState == State.DOZE
- || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) {
+ || mState == State.DOZE_AOD_DOCKED || mState == State.DOZE_SUSPEND_TRIGGERS)
+ && requestedState == State.DOZE_PULSE_DONE) {
Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
return mState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
index 055cd52..7f567aa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
@@ -23,6 +23,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback;
import com.android.systemui.dreams.conditions.DreamCondition;
+import com.android.systemui.flags.RestartDozeListener;
import com.android.systemui.shared.condition.Monitor;
import com.android.systemui.util.condition.ConditionalCoreStartable;
@@ -39,17 +40,19 @@
private final Monitor mConditionMonitor;
private final DreamCondition mDreamCondition;
private final DreamStatusBarStateCallback mCallback;
+ private RestartDozeListener mRestartDozeListener;
@Inject
public DreamMonitor(Monitor monitor, DreamCondition dreamCondition,
@Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor,
- DreamStatusBarStateCallback callback) {
+ DreamStatusBarStateCallback callback,
+ RestartDozeListener restartDozeListener) {
super(pretextMonitor);
mConditionMonitor = monitor;
mDreamCondition = dreamCondition;
mCallback = callback;
-
+ mRestartDozeListener = restartDozeListener;
}
@Override
@@ -61,5 +64,8 @@
mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
.addCondition(mDreamCondition)
.build());
+
+ mRestartDozeListener.init();
+ mRestartDozeListener.maybeRestartSleep();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index b7f6a70..7790986 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -27,6 +27,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.policy.CallbackController;
import java.util.ArrayList;
@@ -104,12 +106,24 @@
private final Collection<Complication> mComplications = new HashSet();
+ private final FeatureFlags mFeatureFlags;
+
+ private final int mSupportedTypes;
+
@VisibleForTesting
@Inject
public DreamOverlayStateController(@Main Executor executor,
- @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled) {
+ @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
+ FeatureFlags featureFlags) {
mExecutor = executor;
mOverlayEnabled = overlayEnabled;
+ mFeatureFlags = featureFlags;
+ if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
+ mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
+ | Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+ } else {
+ mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
+ }
if (DEBUG) {
Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
}
@@ -181,7 +195,7 @@
if (mShouldShowComplications) {
return (requiredTypes & getAvailableComplicationTypes()) == requiredTypes;
}
- return requiredTypes == Complication.COMPLICATION_TYPE_NONE;
+ return (requiredTypes & mSupportedTypes) == requiredTypes;
})
.collect(Collectors.toCollection(HashSet::new))
: mComplications);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
index 2befce7..5bbfbda 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.dreams.conditions;
+import android.app.DreamManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -30,6 +31,7 @@
*/
public class DreamCondition extends Condition {
private final Context mContext;
+ private final DreamManager mDreamManager;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -39,8 +41,10 @@
};
@Inject
- public DreamCondition(Context context) {
+ public DreamCondition(Context context,
+ DreamManager dreamManager) {
mContext = context;
+ mDreamManager = dreamManager;
}
private void processIntent(Intent intent) {
@@ -62,8 +66,8 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_DREAMING_STARTED);
filter.addAction(Intent.ACTION_DREAMING_STOPPED);
- final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter);
- processIntent(stickyIntent);
+ mContext.registerReceiver(mReceiver, filter);
+ updateCondition(mDreamManager.isDreaming());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 06ca0ad..28c45b8 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -22,7 +22,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.InitializationChecker
-import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -38,8 +37,6 @@
private val featureFlags: FeatureFlagsDebug,
private val broadcastSender: BroadcastSender,
private val initializationChecker: InitializationChecker,
- private val restartDozeListener: RestartDozeListener,
- private val delayableExecutor: DelayableExecutor
) : CoreStartable {
init {
@@ -55,9 +52,6 @@
// protected broadcast should only be sent for the main process
val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
broadcastSender.sendBroadcast(intent)
-
- restartDozeListener.init()
- delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
index 133e67f..f97112d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -18,8 +18,6 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.InitializationChecker
-import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -31,9 +29,6 @@
constructor(
dumpManager: DumpManager,
featureFlags: FeatureFlags,
- private val initializationChecker: InitializationChecker,
- private val restartDozeListener: RestartDozeListener,
- private val delayableExecutor: DelayableExecutor
) : CoreStartable {
init {
@@ -42,12 +37,7 @@
}
}
- override fun start() {
- if (initializationChecker.initializeComponents()) {
- restartDozeListener.init()
- delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000)
- }
- }
+ override fun start() {}
}
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 71c92d2..4dd3a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -226,7 +226,12 @@
/** Whether to inflate the bouncer view on a background thread. */
// TODO(b/272091103): Tracking Bug
@JvmField
- val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true)
+ val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = false)
+
+ /** Whether to inflate the bouncer view on a background thread. */
+ // TODO(b/273341787): Tracking Bug
+ @JvmField
+ val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@@ -246,20 +251,12 @@
// TODO(b/270223352): Tracking Bug
@JvmField
val HIDE_SMARTSPACE_ON_DREAM_OVERLAY =
- unreleasedFlag(
- 404,
- "hide_smartspace_on_dream_overlay",
- teamfood = true
- )
+ releasedFlag(404, "hide_smartspace_on_dream_overlay")
// TODO(b/271460958): Tracking Bug
@JvmField
val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY =
- unreleasedFlag(
- 405,
- "show_weather_complication_on_dream_overlay",
- teamfood = true
- )
+ releasedFlag(405, "show_weather_complication_on_dream_overlay")
// 500 - quick settings
@@ -410,7 +407,7 @@
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
// TODO(b/270882464): Tracking Bug
- val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true)
+ val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
// TODO(b/265045965): Tracking Bug
val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
@@ -421,6 +418,11 @@
1004,
"enable_low_light_clock_undocked", teamfood = true)
+ // TODO(b/273509374): Tracking Bug
+ @JvmField
+ val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = unreleasedFlag(1006,
+ "always_show_home_controls_on_dreams")
+
// 1100 - windowing
@Keep
@JvmField
@@ -512,7 +514,7 @@
@JvmField
val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
sysPropBooleanFlag(
- 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = false)
+ 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = true)
// 1200 - predictive back
@Keep
@@ -678,6 +680,12 @@
val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
unreleasedFlag(2700, "enable_dark_vignette_when_folding")
+ // TODO(b/265764985): Tracking Bug
+ @Keep
+ @JvmField
+ val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS =
+ unreleasedFlag(2701, "enable_unfold_status_bar_animations")
+
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
index bd74f4e..dc0de2c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt
@@ -20,7 +20,9 @@
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -33,18 +35,19 @@
private val statusBarStateController: StatusBarStateController,
private val powerManager: PowerManager,
private val systemClock: SystemClock,
+ @Background val bgExecutor: DelayableExecutor,
) {
companion object {
- @VisibleForTesting val RESTART_NAP_KEY = "restart_nap_after_start"
+ @VisibleForTesting val RESTART_SLEEP_KEY = "restart_nap_after_start"
}
private var inited = false
val listener =
object : StatusBarStateController.StateListener {
- override fun onDreamingChanged(isDreaming: Boolean) {
- settings.putBool(RESTART_NAP_KEY, isDreaming)
+ override fun onDozingChanged(isDozing: Boolean) {
+ storeSleepState(isDozing)
}
}
@@ -62,11 +65,23 @@
}
fun maybeRestartSleep() {
- if (settings.getBool(RESTART_NAP_KEY, false)) {
- Log.d("RestartDozeListener", "Restarting sleep state")
- powerManager.wakeUp(systemClock.uptimeMillis())
- powerManager.goToSleep(systemClock.uptimeMillis())
- settings.putBool(RESTART_NAP_KEY, false)
- }
+ bgExecutor.executeDelayed(
+ {
+ if (settings.getBool(RESTART_SLEEP_KEY, false)) {
+ Log.d("RestartDozeListener", "Restarting sleep state")
+ powerManager.wakeUp(
+ systemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "RestartDozeListener"
+ )
+ powerManager.goToSleep(systemClock.uptimeMillis())
+ }
+ },
+ 1000
+ )
+ }
+
+ private fun storeSleepState(sleeping: Boolean) {
+ bgExecutor.execute { settings.putBool(RESTART_SLEEP_KEY, sleeping) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 3e52ff2..9ab2e99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -22,6 +22,7 @@
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
+import android.os.DeadObjectException
import android.os.Handler
import android.os.PowerManager
import android.os.RemoteException
@@ -524,10 +525,22 @@
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
- launcherUnlockController?.playUnlockAnimation(
- true,
- UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY,
- 0 /* startDelay */)
+ try {
+ launcherUnlockController?.playUnlockAnimation(
+ true,
+ UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY,
+ 0 /* startDelay */)
+ } catch (e: DeadObjectException) {
+ // Hello! If you are here investigating a bug where Launcher is blank (no icons)
+ // then the below assumption about Launcher's destruction was incorrect. This
+ // would mean prepareToUnlock was called (blanking Launcher in preparation for
+ // the beginning of the unlock animation), but then somehow we were unable to
+ // call playUnlockAnimation to animate the icons back in.
+ Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ "Catching exception as this should mean Launcher is in the process " +
+ "of being destroyed, but the IPC to System UI telling us hasn't " +
+ "arrived yet.")
+ }
launcherPreparedForUnlock = false
} else {
@@ -604,11 +617,23 @@
private fun unlockToLauncherWithInWindowAnimations() {
setSurfaceBehindAppearAmount(1f)
- // Begin the animation, waiting for the shade to animate out.
- launcherUnlockController?.playUnlockAnimation(
- true /* unlocked */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
- CANNED_UNLOCK_START_DELAY /* startDelay */)
+ try {
+ // Begin the animation, waiting for the shade to animate out.
+ launcherUnlockController?.playUnlockAnimation(
+ true /* unlocked */,
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
+ CANNED_UNLOCK_START_DELAY /* startDelay */)
+ } catch (e: DeadObjectException) {
+ // Hello! If you are here investigating a bug where Launcher is blank (no icons)
+ // then the below assumption about Launcher's destruction was incorrect. This
+ // would mean prepareToUnlock was called (blanking Launcher in preparation for
+ // the beginning of the unlock animation), but then somehow we were unable to
+ // call playUnlockAnimation to animate the icons back in.
+ Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ "Catching exception as this should mean Launcher is in the process " +
+ "of being destroyed, but the IPC to System UI telling us hasn't " +
+ "arrived yet.")
+ }
launcherPreparedForUnlock = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index d745a19..aad4a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -34,6 +35,7 @@
class AlternateBouncerInteractor
@Inject
constructor(
+ private val statusBarStateController: StatusBarStateController,
private val keyguardStateController: KeyguardStateController,
private val bouncerRepository: KeyguardBouncerRepository,
private val biometricSettingsRepository: BiometricSettingsRepository,
@@ -49,6 +51,17 @@
var receivedDownTouch = false
val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+ private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+ object : KeyguardStateController.Callback {
+ override fun onUnlockedChanged() {
+ maybeHide()
+ }
+ }
+
+ init {
+ keyguardStateController.addCallback(keyguardStateControllerCallback)
+ }
+
/**
* Sets the correct bouncer states to show the alternate bouncer if it can show.
*
@@ -109,7 +122,8 @@
biometricSettingsRepository.isStrongBiometricAllowed.value &&
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
!deviceEntryFingerprintAuthRepository.isLockedOut.value &&
- !keyguardStateController.isUnlocked
+ !keyguardStateController.isUnlocked &&
+ !statusBarStateController.isDozing
} else {
legacyAlternateBouncer != null &&
keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
@@ -129,6 +143,12 @@
}
}
+ private fun maybeHide() {
+ if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) {
+ hide()
+ }
+ }
+
companion object {
private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
private const val NOT_VISIBLE = -1L
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index b10aa90..e65c8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -303,6 +303,10 @@
/** Tell the bouncer to start the pre hide animation. */
fun startDisappearAnimation(runnable: Runnable) {
+ if (willRunDismissFromKeyguard()) {
+ runnable.run()
+ return
+ }
val finishRunnable = Runnable {
runnable.run()
repository.setPrimaryStartDisappearAnimation(null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index b23247c..df93d23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -81,9 +81,7 @@
)
.map {
if (willRunDismissFromKeyguard) {
- ScrimAlpha(
- notificationsAlpha = 1f,
- )
+ ScrimAlpha()
} else if (leaveShadeOpen) {
ScrimAlpha(
behindAlpha = 1f,
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 889adc7..5704f88 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -160,6 +160,14 @@
return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
}
+ /** Provides a logging buffer for logs related to Quick Settings configuration. */
+ @Provides
+ @SysUISingleton
+ @QSConfigLog
+ public static LogBuffer provideQSConfigLogBuffer(LogBufferFactory factory) {
+ return factory.create("QSConfigLog", 100 /* maxSize */, true /* systrace */);
+ }
+
/** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
new file mode 100644
index 0000000..295bf88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.plugins.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for QS configuration changed messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface QSConfigLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 9c7b48d..f08b9769 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -109,10 +109,11 @@
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : Dumpable {
/** The current width of the carousel */
- private var currentCarouselWidth: Int = 0
+ var currentCarouselWidth: Int = 0
+ private set
/** The current height of the carousel */
- @VisibleForTesting var currentCarouselHeight: Int = 0
+ private var currentCarouselHeight: Int = 0
/** Are we currently showing only active players */
private var currentlyShowingOnlyActive: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 15d999a..cb1f12cf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -32,6 +32,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
@@ -54,6 +56,7 @@
import android.os.Trace;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -468,6 +471,7 @@
TransitionLayout recommendations = vh.getRecommendations();
mMediaViewController.attach(recommendations, MediaViewController.TYPE.RECOMMENDATION);
+ mMediaViewController.configurationChangeListener = this::updateRecommendationsVisibility;
mRecommendationViewHolder.getRecommendations().setOnLongClickListener(v -> {
if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
@@ -1364,6 +1368,7 @@
boolean hasTitle = false;
boolean hasSubtitle = false;
+ int fittedRecsNum = getNumberOfFittedRecommendations();
for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
SmartspaceAction recommendation = recommendations.get(itemIndex);
@@ -1444,12 +1449,20 @@
// If there's no subtitles and/or titles for any of the albums, hide those views.
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
final boolean titlesVisible = hasTitle;
final boolean subtitlesVisible = hasSubtitle;
- mRecommendationViewHolder.getMediaTitles().forEach((titleView) ->
- setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible));
- mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) ->
- setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible));
+ mRecommendationViewHolder.getMediaTitles().forEach((titleView) -> {
+ setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible);
+ setVisibleAndAlpha(collapsedSet, titleView.getId(), titlesVisible);
+ });
+ mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) -> {
+ setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible);
+ setVisibleAndAlpha(collapsedSet, subtitleView.getId(), subtitlesVisible);
+ });
+
+ // Media covers visibility.
+ setMediaCoversVisibility(fittedRecsNum);
// Guts
Runnable onDismissClickedRunnable = () -> {
@@ -1486,6 +1499,51 @@
Trace.endSection();
}
+ private Unit updateRecommendationsVisibility() {
+ int fittedRecsNum = getNumberOfFittedRecommendations();
+ setMediaCoversVisibility(fittedRecsNum);
+ return Unit.INSTANCE;
+ }
+
+ private void setMediaCoversVisibility(int fittedRecsNum) {
+ ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
+ List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
+ // Hide media cover that cannot fit in the recommendation card.
+ for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
+ setVisibleAndAlpha(expandedSet, mediaCoverContainers.get(itemIndex).getId(),
+ itemIndex < fittedRecsNum);
+ setVisibleAndAlpha(collapsedSet, mediaCoverContainers.get(itemIndex).getId(),
+ itemIndex < fittedRecsNum);
+ }
+ }
+
+ @VisibleForTesting
+ protected int getNumberOfFittedRecommendations() {
+ Resources res = mContext.getResources();
+ Configuration config = res.getConfiguration();
+ int defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp);
+ int recCoverWidth = res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
+ + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2;
+
+ // On landscape, media controls should take half of the screen width.
+ int displayAvailableDpWidth = config.screenWidthDp;
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ displayAvailableDpWidth = displayAvailableDpWidth / 2;
+ }
+ int fittedNum;
+ if (displayAvailableDpWidth > defaultDpWidth) {
+ int recCoverDefaultWidth = res.getDimensionPixelSize(
+ R.dimen.qs_media_rec_default_width);
+ fittedNum = recCoverDefaultWidth / recCoverWidth;
+ } else {
+ int displayAvailableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ displayAvailableDpWidth, res.getDisplayMetrics());
+ fittedNum = displayAvailableWidth / recCoverWidth;
+ }
+ return Math.min(fittedNum, NUM_REQUIRED_RECOMMENDATIONS);
+ }
+
private void fetchAndUpdateRecommendationColors(Drawable appIcon) {
mBackgroundExecutor.execute(() -> {
ColorScheme colorScheme = new ColorScheme(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 7a1302c..cd51d92 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -96,6 +96,7 @@
/** A listener when the current dimensions of the player change */
lateinit var sizeChangedListener: () -> Unit
+ lateinit var configurationChangeListener: () -> Unit
private var firstRefresh: Boolean = true
@VisibleForTesting private var transitionLayout: TransitionLayout? = null
private val layoutController = TransitionLayoutController()
@@ -195,6 +196,10 @@
)
}
}
+ if (this@MediaViewController::configurationChangeListener.isInitialized) {
+ configurationChangeListener.invoke()
+ refreshState()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index b71a9193..9928c4f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,10 +16,6 @@
package com.android.systemui.media.dialog;
-import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
-import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
-import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
-
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
@@ -344,7 +340,7 @@
updateDeviceStatusIcon(deviceStatusIcon);
mStatusIcon.setVisibility(View.VISIBLE);
}
- updateTwoLineLayoutContentAlpha(
+ updateSingleLineLayoutContentAlpha(
updateClickActionBasedOnSelectionBehavior(device)
? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
} else {
@@ -368,6 +364,12 @@
mStatusIcon.setAlpha(alphaValue);
}
+ private void updateSingleLineLayoutContentAlpha(float alphaValue) {
+ mTitleIcon.setAlpha(alphaValue);
+ mTitleText.setAlpha(alphaValue);
+ mStatusIcon.setAlpha(alphaValue);
+ }
+
private void updateEndClickAreaAsSessionEditing(MediaDevice device) {
mEndClickIcon.setOnClickListener(null);
mEndTouchArea.setOnClickListener(null);
@@ -535,11 +537,12 @@
@DoNotInline
static Drawable getDeviceStatusIconBasedOnSelectionBehavior(MediaDevice device,
Context context) {
- switch (device.getSubtext()) {
- case SUBTEXT_AD_ROUTING_DISALLOWED:
- case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED:
+ switch (device.getSelectionBehavior()) {
+ case SELECTION_BEHAVIOR_NONE:
return context.getDrawable(R.drawable.media_output_status_failed);
- case SUBTEXT_SUBSCRIPTION_REQUIRED:
+ case SELECTION_BEHAVIOR_TRANSFER:
+ return null;
+ case SELECTION_BEHAVIOR_GO_TO_APP:
return context.getDrawable(R.drawable.media_output_status_help);
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index f76f049..f92a5ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -192,8 +192,11 @@
mSubTitleText.setTextColor(mController.getColorItemContent());
mTwoLineTitleText.setTextColor(mController.getColorItemContent());
if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.setOnClickListener(null);
mVolumeValueText.setTextColor(mController.getColorItemContent());
+ mTitleIcon.setOnTouchListener(((v, event) -> {
+ mSeekBar.dispatchTouchEvent(event);
+ return false;
+ }));
}
mSeekBar.setProgressTintList(
ColorStateList.valueOf(mController.getColorSeekbarProgress()));
@@ -444,9 +447,6 @@
}
void updateIconAreaClickListener(View.OnClickListener listener) {
- if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.setOnClickListener(listener);
- }
mTitleIcon.setOnClickListener(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 2aedd36..f3f17d1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -395,7 +395,6 @@
launchIntent.putExtra(EXTRA_ROUTE_ID, routeId);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mCallback.dismissDialog();
- mContext.startActivity(launchIntent);
mActivityStarter.startActivity(launchIntent, true, controller);
}
}
@@ -996,7 +995,7 @@
mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
- mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
+ dialog.show();
}
String getBroadcastName() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 760a42c..132bf99 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -62,7 +62,8 @@
private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
if (!packageName.isNullOrEmpty()) {
- mediaOutputBroadcastDialogFactory.create(packageName, false)
+ mediaOutputBroadcastDialogFactory.create(
+ packageName, aboveStatusBar = true, view = null)
} else if (DEBUG) {
Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
index 253c3c7..be5d607 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
@@ -26,6 +26,7 @@
*/
public class MediaOutputSeekbar extends SeekBar {
private static final int SCALE_SIZE = 1000;
+ private static final int INITIAL_PROGRESS = 500;
public static final int VOLUME_PERCENTAGE_SCALE_SIZE = 100000;
public MediaOutputSeekbar(Context context, AttributeSet attrs) {
@@ -38,7 +39,7 @@
}
static int scaleVolumeToProgress(int volume) {
- return volume * SCALE_SIZE;
+ return volume == 0 ? 0 : INITIAL_PROGRESS + volume * SCALE_SIZE;
}
int getVolume() {
@@ -46,7 +47,7 @@
}
void setVolume(int volume) {
- setProgress(volume * SCALE_SIZE, true);
+ setProgress(scaleVolumeToProgress(volume), true);
}
void setMaxVolume(int maxVolume) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index ee93c37..dbc2a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,12 +19,13 @@
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import androidx.annotation.AttrRes
+import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
@@ -78,7 +79,7 @@
return IconInfo(
contentDescription,
MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)),
- tintAttr = null,
+ tint = null,
isAppIcon = true
)
} catch (e: PackageManager.NameNotFoundException) {
@@ -96,7 +97,7 @@
)
},
MediaTttIcon.Resource(R.drawable.ic_cast),
- tintAttr = android.R.attr.textColorPrimary,
+ tint = DEFAULT_ICON_TINT,
isAppIcon = false
)
}
@@ -107,7 +108,7 @@
data class IconInfo(
val contentDescription: ContentDescription,
val icon: MediaTttIcon,
- @AttrRes val tintAttr: Int?,
+ @ColorRes val tint: Int?,
/**
* True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
*/
@@ -120,7 +121,7 @@
is MediaTttIcon.Loaded -> Icon.Loaded(icon.drawable, contentDescription)
is MediaTttIcon.Resource -> Icon.Resource(icon.res, contentDescription)
}
- return TintedIcon(iconOutput, tintAttr)
+ return TintedIcon(iconOutput, tint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 9bccb7d..89f66b7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -19,12 +19,11 @@
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
+import android.view.WindowInsets.Type
import android.view.WindowManager
-import com.android.internal.R as AndroidR
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
-import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -62,17 +61,12 @@
val width = windowMetrics.bounds.width()
var height = maximumWindowHeight
- // TODO(b/271410803): Read isTransientTaskbar from Launcher
val isLargeScreen = isLargeScreen(context)
- val isTransientTaskbar =
- QuickStepContract.isGesturalMode(
- context.resources.getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode
- )
- )
- if (isLargeScreen && !isTransientTaskbar) {
+ if (isLargeScreen) {
val taskbarSize =
- context.resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
+ windowManager.currentWindowMetrics.windowInsets
+ .getInsets(Type.tappableElement())
+ .bottom
height -= taskbarSize
}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
index b9f6d83..ebb8639 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.shared.math.isZero
import com.android.systemui.multishade.shared.model.ProxiedInputModel
import com.android.systemui.multishade.shared.model.ShadeConfig
import com.android.systemui.multishade.shared.model.ShadeId
@@ -63,6 +64,10 @@
}
}
+ /** Whether any shade is expanded, even a little bit. */
+ val isAnyShadeExpanded: Flow<Boolean> =
+ maxShadeExpansion.map { maxExpansion -> !maxExpansion.isZero() }.distinctUntilChanged()
+
/**
* A _processed_ version of the proxied input flow.
*
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
new file mode 100644
index 0000000..ff7c901
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 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.multishade.domain.interactor
+
+import android.content.Context
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Encapsulates business logic to handle [MotionEvent]-based user input.
+ *
+ * This class is meant purely for the legacy `View`-based system to be able to pass `MotionEvent`s
+ * into the newer multi-shade framework for processing.
+ */
+class MultiShadeMotionEventInteractor
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Application private val applicationScope: CoroutineScope,
+ private val interactor: MultiShadeInteractor,
+) {
+
+ private val isAnyShadeExpanded: StateFlow<Boolean> =
+ interactor.isAnyShadeExpanded.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ private var interactionState: InteractionState? = null
+
+ /**
+ * Returns `true` if the given [MotionEvent] and the rest of events in this gesture should be
+ * passed to this interactor's [onTouchEvent] method.
+ *
+ * Note: the caller should continue to pass [MotionEvent] instances into this method, even if it
+ * returns `false` as the gesture may be intercepted mid-stream.
+ */
+ fun shouldIntercept(event: MotionEvent): Boolean {
+ if (isAnyShadeExpanded.value) {
+ // If any shade is expanded, we assume that touch handling outside the shades is handled
+ // by the scrim that appears behind the shades. No need to intercept anything here.
+ return false
+ }
+
+ return when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ // Record where the pointer was placed and which pointer it was.
+ interactionState =
+ InteractionState(
+ initialX = event.x,
+ initialY = event.y,
+ currentY = event.y,
+ pointerId = event.getPointerId(0),
+ isDraggingHorizontally = false,
+ isDraggingVertically = false,
+ )
+
+ false
+ }
+ MotionEvent.ACTION_MOVE -> {
+ interactionState?.let {
+ val pointerIndex = event.findPointerIndex(it.pointerId)
+ val currentX = event.getX(pointerIndex)
+ val currentY = event.getY(pointerIndex)
+ if (!it.isDraggingHorizontally && !it.isDraggingVertically) {
+ val xDistanceTravelled = abs(currentX - it.initialX)
+ val yDistanceTravelled = abs(currentY - it.initialY)
+ val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
+ interactionState =
+ when {
+ yDistanceTravelled > touchSlop ->
+ it.copy(isDraggingVertically = true)
+ xDistanceTravelled > touchSlop ->
+ it.copy(isDraggingHorizontally = true)
+ else -> interactionState
+ }
+ }
+ }
+
+ // We want to intercept the rest of the gesture if we're dragging.
+ interactionState.isDraggingVertically()
+ }
+ MotionEvent.ACTION_UP,
+ MotionEvent.ACTION_CANCEL ->
+ // Make sure that we intercept the up or cancel if we're dragging, to handle drag
+ // end and cancel.
+ interactionState.isDraggingVertically()
+ else -> false
+ }
+ }
+
+ /**
+ * Notifies that a [MotionEvent] in a series of events of a gesture that was intercepted due to
+ * the result of [shouldIntercept] has been received.
+ *
+ * @param event The [MotionEvent] to handle.
+ * @param viewWidthPx The width of the view, in pixels.
+ * @return `true` if the event was consumed, `false` otherwise.
+ */
+ fun onTouchEvent(event: MotionEvent, viewWidthPx: Int): Boolean {
+ return when (event.actionMasked) {
+ MotionEvent.ACTION_MOVE -> {
+ interactionState?.let {
+ if (it.isDraggingVertically) {
+ val pointerIndex = event.findPointerIndex(it.pointerId)
+ val previousY = it.currentY
+ val currentY = event.getY(pointerIndex)
+ interactionState =
+ it.copy(
+ currentY = currentY,
+ )
+
+ val yDragAmountPx = currentY - previousY
+ if (yDragAmountPx != 0f) {
+ interactor.sendProxiedInput(
+ ProxiedInputModel.OnDrag(
+ xFraction = event.x / viewWidthPx,
+ yDragAmountPx = yDragAmountPx,
+ )
+ )
+ }
+ }
+ }
+
+ true
+ }
+ MotionEvent.ACTION_UP -> {
+ if (interactionState.isDraggingVertically()) {
+ // We finished dragging. Record that so the multi-shade framework can issue a
+ // fling, if the velocity reached in the drag was high enough, for example.
+ interactor.sendProxiedInput(ProxiedInputModel.OnDragEnd)
+ }
+
+ interactionState = null
+ true
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ if (interactionState.isDraggingVertically()) {
+ // Our drag gesture was canceled by the system. This happens primarily in one of
+ // two occasions: (a) the parent view has decided to intercept the gesture
+ // itself and/or route it to a different child view or (b) the pointer has
+ // traveled beyond the bounds of our view and/or the touch display. Either way,
+ // we pass the cancellation event to the multi-shade framework to record it.
+ // Doing that allows the multi-shade framework to know that the gesture ended to
+ // allow new gestures to be accepted.
+ interactor.sendProxiedInput(ProxiedInputModel.OnDragCancel)
+ }
+
+ interactionState = null
+ true
+ }
+ else -> false
+ }
+ }
+
+ private data class InteractionState(
+ val initialX: Float,
+ val initialY: Float,
+ val currentY: Float,
+ val pointerId: Int,
+ val isDraggingHorizontally: Boolean,
+ val isDraggingVertically: Boolean,
+ )
+
+ private fun InteractionState?.isDraggingVertically(): Boolean {
+ return this?.isDraggingVertically == true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt
new file mode 100644
index 0000000..c2eaf72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.multishade.shared.math
+
+import androidx.annotation.VisibleForTesting
+import kotlin.math.abs
+
+/** Returns `true` if this [Float] is within [epsilon] of `0`. */
+fun Float.isZero(epsilon: Float = EPSILON): Boolean {
+ return abs(this) < epsilon
+}
+
+@VisibleForTesting private const val EPSILON = 0.0001f
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
index ce6ab97..ed92c54 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
@@ -26,7 +26,6 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -87,10 +86,7 @@
when (shadeConfig) {
// In the dual shade configuration, the scrim is enabled when the expansion is
// greater than zero on any one of the shades.
- is ShadeConfig.DualShadeConfig ->
- interactor.maxShadeExpansion
- .map { expansion -> expansion > 0 }
- .distinctUntilChanged()
+ is ShadeConfig.DualShadeConfig -> interactor.isAnyShadeExpanded
// No scrim in the single shade configuration.
is ShadeConfig.SingleShadeConfig -> flowOf(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index e7bb6dc..0d5a3fd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -58,12 +58,14 @@
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
+private const val MIN_DURATION_FLING_ANIMATION = 160L
private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L
private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L
private const val FAILSAFE_DELAY_MS = 350L
-private const val POP_ON_FLING_DELAY = 140L
+private const val POP_ON_FLING_DELAY = 50L
+private const val POP_ON_FLING_SCALE = 3f
internal val VIBRATE_ACTIVATED_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
@@ -230,7 +232,12 @@
updateArrowState(GestureState.GONE)
}
- private val playAnimationThenSetGoneOnAlphaEnd = Runnable { playAnimationThenSetGoneEnd() }
+ private val onAlphaEndSetGoneStateListener = DelayedOnAnimationEndListener(mainHandler, 0L) {
+ updateRestingArrowDimens()
+ if (!mView.addAnimationEndListener(mView.backgroundAlpha, onEndSetGoneStateListener)) {
+ scheduleFailsafe()
+ }
+ }
// Minimum of the screen's width or the predefined threshold
private var fullyStretchedThreshold = 0f
@@ -357,7 +364,7 @@
mView.cancelAnimations()
mainHandler.removeCallbacks(onEndSetCommittedStateListener.runnable)
mainHandler.removeCallbacks(onEndSetGoneStateListener.runnable)
- mainHandler.removeCallbacks(playAnimationThenSetGoneOnAlphaEnd)
+ mainHandler.removeCallbacks(onAlphaEndSetGoneStateListener.runnable)
}
/**
@@ -509,7 +516,7 @@
val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
val rubberbandAmount = 15f
val yProgress = MathUtils.saturate(yTranslation / (maxYOffset * rubberbandAmount))
- val yPosition = params.translationInterpolator.getInterpolation(yProgress) *
+ val yPosition = params.verticalTranslationInterpolator.getInterpolation(yProgress) *
maxYOffset *
sign(yOffset)
mView.animateVertically(yPosition)
@@ -520,10 +527,9 @@
* the arrow is fully stretched (between 0.0 - 1.0f)
*/
private fun fullScreenProgress(xTranslation: Float): Float {
- return MathUtils.saturate(
- (xTranslation - previousXTranslationOnActiveOffset) /
- (fullyStretchedThreshold - previousXTranslationOnActiveOffset)
- )
+ val progress = abs((xTranslation - previousXTranslationOnActiveOffset) /
+ (fullyStretchedThreshold - previousXTranslationOnActiveOffset))
+ return MathUtils.saturate(progress)
}
/**
@@ -540,7 +546,7 @@
private fun stretchActiveBackIndicator(progress: Float) {
mView.setStretch(
- horizontalTranslationStretchAmount = params.translationInterpolator
+ horizontalTranslationStretchAmount = params.horizontalTranslationInterpolator
.getInterpolation(progress),
arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
backgroundWidthStretchAmount = params.activeWidthInterpolator
@@ -639,20 +645,6 @@
return flingDistance > minFlingDistance && isPastFlingVelocityThreshold
}
- private fun playHorizontalAnimationThen(onEnd: DelayedOnAnimationEndListener) {
- updateRestingArrowDimens()
- if (!mView.addAnimationEndListener(mView.horizontalTranslation, onEnd)) {
- scheduleFailsafe()
- }
- }
-
- private fun playAnimationThenSetGoneEnd() {
- updateRestingArrowDimens()
- if (!mView.addAnimationEndListener(mView.backgroundAlpha, onEndSetGoneStateListener)) {
- scheduleFailsafe()
- }
- }
-
private fun playWithBackgroundWidthAnimation(
onEnd: DelayedOnAnimationEndListener,
delay: Long = 0L
@@ -912,18 +904,25 @@
updateRestingArrowDimens()
}
GestureState.FLUNG -> {
- mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(1.9f) }
- playHorizontalAnimationThen(onEndSetCommittedStateListener)
+ mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(POP_ON_FLING_SCALE) }
+ updateRestingArrowDimens()
+ mainHandler.postDelayed(onEndSetCommittedStateListener.runnable,
+ MIN_DURATION_FLING_ANIMATION)
}
GestureState.COMMITTED -> {
+ // In most cases, animating between states is handled via `updateRestingArrowDimens`
+ // which plays an animation immediately upon state change. Some animations however
+ // occur after a delay upon state change and these animations may be independent
+ // or non-sequential from the state change animation. `postDelayed` is used to
+ // manually play these kinds of animations in parallel.
if (previousState == GestureState.FLUNG) {
- playAnimationThenSetGoneEnd()
+ updateRestingArrowDimens()
+ mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
+ MIN_DURATION_COMMITTED_ANIMATION)
} else {
- mView.popScale(3f)
- mainHandler.postDelayed(
- playAnimationThenSetGoneOnAlphaEnd,
- MIN_DURATION_COMMITTED_ANIMATION
- )
+ mView.popScale(POP_ON_FLING_SCALE)
+ mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
+ MIN_DURATION_COMMITTED_ANIMATION)
}
}
GestureState.CANCELLED -> {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index cfcc671..84d23c6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -18,7 +18,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -244,7 +244,7 @@
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
- private boolean mIsTrackpadGestureBackEnabled;
+ private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsButtonForceVisible;
private InputMonitor mInputMonitor;
@@ -590,7 +590,7 @@
// Add a nav bar panel window
mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
- mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled(
+ mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
Flags.TRACKPAD_GESTURE_FEATURES);
resetEdgeBackPlugin();
mPluginManager.addPluginListener(
@@ -883,7 +883,7 @@
}
private void onMotionEvent(MotionEvent ev) {
- boolean isTrackpadEvent = isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev);
+ boolean isTrackpadEvent = isTrackpadThreeFingerSwipe(mIsTrackpadGestureFeaturesEnabled, ev);
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
if (DEBUG_MISSING_GESTURE) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 3dc6d2f..35b6c15 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -2,6 +2,7 @@
import android.content.res.Resources
import android.util.TypedValue
+import androidx.core.animation.Interpolator
import androidx.core.animation.PathInterpolator
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.R
@@ -77,21 +78,23 @@
var swipeProgressThreshold: Float = 0f
private set
- lateinit var entryWidthInterpolator: PathInterpolator
+ lateinit var entryWidthInterpolator: Interpolator
private set
- lateinit var entryWidthTowardsEdgeInterpolator: PathInterpolator
+ lateinit var entryWidthTowardsEdgeInterpolator: Interpolator
private set
- lateinit var activeWidthInterpolator: PathInterpolator
+ lateinit var activeWidthInterpolator: Interpolator
private set
- lateinit var arrowAngleInterpolator: PathInterpolator
+ lateinit var arrowAngleInterpolator: Interpolator
private set
- lateinit var translationInterpolator: PathInterpolator
+ lateinit var horizontalTranslationInterpolator: Interpolator
private set
- lateinit var farCornerInterpolator: PathInterpolator
+ lateinit var verticalTranslationInterpolator: Interpolator
private set
- lateinit var edgeCornerInterpolator: PathInterpolator
+ lateinit var farCornerInterpolator: Interpolator
private set
- lateinit var heightInterpolator: PathInterpolator
+ lateinit var edgeCornerInterpolator: Interpolator
+ private set
+ lateinit var heightInterpolator: Interpolator
private set
init {
@@ -125,9 +128,10 @@
entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
- activeWidthInterpolator = PathInterpolator(.7f, .06f, .34f, .97f)
+ activeWidthInterpolator = PathInterpolator(.56f, -0.39f, .18f, 1.46f)
arrowAngleInterpolator = entryWidthInterpolator
- translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
+ horizontalTranslationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
+ verticalTranslationInterpolator = PathInterpolator(.5f, 1.15f, .41f, .94f)
farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
@@ -147,7 +151,7 @@
scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
- verticalTranslationSpring = createSpring(10000f, 0.9f),
+ verticalTranslationSpring = createSpring(30000f, 1f),
scaleSpring = createSpring(120f, 0.8f),
arrowDimens = ArrowDimens(
length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
@@ -174,7 +178,6 @@
height = getDimen(R.dimen.navigation_edge_entry_background_height),
edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners),
- alphaSpring = createSpring(1100f, 1f),
widthSpring = createSpring(450f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(300f, 0.5f),
@@ -269,10 +272,10 @@
heightSpring = flungCommittedHeightSpring,
edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
farCornerRadiusSpring = flungCommittedFarCornerSpring,
- alphaSpring = createSpring(1100f, 1f),
+ alphaSpring = createSpring(1400f, 1f),
),
scale = 0.85f,
- scaleSpring = createSpring(1150f, 1f),
+ scaleSpring = createSpring(6000f, 1f),
)
flungIndicator = committedIndicator.copy(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 1345c9b..9e2b6d3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -22,9 +22,10 @@
public final class Utilities {
- public static boolean isTrackpadMotionEvent(boolean isTrackpadGestureBackEnabled,
+ public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
MotionEvent event) {
- return isTrackpadGestureBackEnabled
- && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+ return isTrackpadGestureFeaturesEnabled
+ && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ && event.getPointerCount() == 3;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt b/packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt
new file mode 100644
index 0000000..5d03218
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.notetask
+
+/**
+ * Marks declarations that are **internal** in note task API, which means that should not be used
+ * outside of `com.android.systemui.notetask`.
+ */
+@Retention(value = AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.TYPEALIAS,
+ AnnotationTarget.PROPERTY
+)
+@RequiresOptIn(
+ level = RequiresOptIn.Level.ERROR,
+ message = "This is an internal API, do not it outside `com.android.systemui.notetask`",
+)
+internal annotation class InternalNoteTaskApi
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index e74d78d..93ed859 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -14,18 +14,21 @@
* limitations under the License.
*/
+@file:OptIn(InternalNoteTaskApi::class)
+
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
+import android.app.role.OnRoleHoldersChangedListener
+import android.app.role.RoleManager
+import android.app.role.RoleManager.ROLE_NOTES
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
-import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
import android.os.Build
import android.os.UserHandle
import android.os.UserManager
@@ -33,6 +36,8 @@
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
+import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
+import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
@@ -55,6 +60,8 @@
@Inject
constructor(
private val context: Context,
+ private val roleManager: RoleManager,
+ private val shortcutManager: ShortcutManager,
private val resolver: NoteTaskInfoResolver,
private val eventLogger: NoteTaskEventLogger,
private val optionalBubbles: Optional<Bubbles>,
@@ -133,12 +140,13 @@
infoReference.set(info)
// TODO(b/266686199): We should handle when app not available. For now, we log.
- val intent = createNoteIntent(info)
+ val intent = createNoteTaskIntent(info)
try {
logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" }
when (info.launchMode) {
is NoteTaskLaunchMode.AppBubble -> {
- bubbles.showOrHideAppBubble(intent, userTracker.userHandle)
+ // TODO: provide app bubble icon
+ bubbles.showOrHideAppBubble(intent, userTracker.userHandle, null /* icon */)
// App bubble logging happens on `onBubbleExpandChanged`.
logDebug { "onShowNoteTask - opened as app bubble: $info" }
}
@@ -181,30 +189,71 @@
logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
}
+ /**
+ * Updates all [NoteTaskController] related information, including but not exclusively the
+ * widget shortcut created by the [user] - by default it will use the current user.
+ *
+ * Keep in mind the shortcut API has a
+ * [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting)
+ * and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the
+ * function during System UI initialization.
+ */
+ fun updateNoteTaskAsUser(user: UserHandle) {
+ val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
+ val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
+
+ setNoteTaskShortcutEnabled(hasNotesRoleHolder)
+
+ if (hasNotesRoleHolder) {
+ shortcutManager.enableShortcuts(listOf(SHORTCUT_ID))
+ val updatedShortcut = roleManager.createNoteShortcutInfoAsUser(context, user)
+ shortcutManager.updateShortcuts(listOf(updatedShortcut))
+ } else {
+ shortcutManager.disableShortcuts(listOf(SHORTCUT_ID))
+ }
+ }
+
+ /** @see OnRoleHoldersChangedListener */
+ fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
+ if (roleName == ROLE_NOTES) updateNoteTaskAsUser(user)
+ }
+
companion object {
val TAG = NoteTaskController::class.simpleName.orEmpty()
+
+ const val SHORTCUT_ID = "note_task_shortcut_id"
+
+ /**
+ * Shortcut extra which can point to a package name and can be used to indicate an alternate
+ * badge info. Launcher only reads this if the shortcut comes from a system app.
+ *
+ * Duplicated from [com.android.launcher3.icons.IconCache].
+ *
+ * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
+ */
+ const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package"
}
}
-private fun createNoteIntent(info: NoteTaskInfo): Intent =
+/** Creates an [Intent] for [ROLE_NOTES]. */
+private fun createNoteTaskIntent(info: NoteTaskInfo): Intent =
Intent(Intent.ACTION_CREATE_NOTE).apply {
setPackage(info.packageName)
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
- // was used to start it.
+ // was used to start the note task.
putExtra(Intent.EXTRA_USE_STYLUS_MODE, true)
- addFlags(FLAG_ACTIVITY_NEW_TASK)
- // We should ensure the note experience can be open both as a full screen (lock screen)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // We should ensure the note experience can be opened both as a full screen (lockscreen)
// and inside the app bubble (contextual). These additional flags will do that.
if (info.launchMode == NoteTaskLaunchMode.Activity) {
- addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
- addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
}
}
-private inline fun logDebug(message: () -> String) {
- if (Build.IS_DEBUGGABLE) {
- Log.d(NoteTaskController.TAG, message())
- }
+/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
+private inline fun Any.logDebug(message: () -> String) {
+ if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index 8ecf081..616f9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -14,13 +14,17 @@
* limitations under the License.
*/
+@file:OptIn(InternalNoteTaskApi::class)
+
package com.android.systemui.notetask
import android.app.role.RoleManager
+import android.app.role.RoleManager.ROLE_NOTES
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.os.UserHandle
import android.util.Log
+import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
@@ -36,10 +40,9 @@
entryPoint: NoteTaskEntryPoint? = null,
isKeyguardLocked: Boolean = false,
): NoteTaskInfo? {
- // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
val user = userTracker.userHandle
- val packageName =
- roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()
+
+ val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
if (packageName.isNullOrEmpty()) return null
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index fb3c0cb..04ed08b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -15,11 +15,15 @@
*/
package com.android.systemui.notetask
+import android.app.role.RoleManager
+import android.os.UserHandle
import android.view.KeyEvent
import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.CommandQueue
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
+import java.util.concurrent.Executor
import javax.inject.Inject
/** Class responsible to "glue" all note task dependencies. */
@@ -27,8 +31,10 @@
@Inject
constructor(
private val controller: NoteTaskController,
+ private val roleManager: RoleManager,
private val commandQueue: CommandQueue,
private val optionalBubbles: Optional<Bubbles>,
+ @Background private val backgroundExecutor: Executor,
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
@@ -43,11 +49,15 @@
}
fun initialize() {
- controller.setNoteTaskShortcutEnabled(isEnabled)
-
// Guard against feature not being enabled or mandatory dependencies aren't available.
if (!isEnabled || optionalBubbles.isEmpty) return
+ controller.setNoteTaskShortcutEnabled(true)
commandQueue.addCallback(callbacks)
+ roleManager.addOnRoleHoldersChangedListenerAsUser(
+ backgroundExecutor,
+ controller::onRoleHoldersChanged,
+ UserHandle.ALL,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
new file mode 100644
index 0000000..441b9f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.notetask
+
+import android.app.role.RoleManager
+import android.app.role.RoleManager.ROLE_NOTES
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Icon
+import android.os.PersistableBundle
+import android.os.UserHandle
+import com.android.systemui.R
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+
+/** Extension functions for [RoleManager] used **internally** by note task. */
+@InternalNoteTaskApi
+internal object NoteTaskRoleManagerExt {
+
+ /**
+ * Gets package name of the default (first) app holding the [role]. If none, returns either an
+ * empty string or null.
+ */
+ fun RoleManager.getDefaultRoleHolderAsUser(role: String, user: UserHandle): String? =
+ getRoleHoldersAsUser(role, user).firstOrNull()
+
+ /** Creates a [ShortcutInfo] for [ROLE_NOTES]. */
+ fun RoleManager.createNoteShortcutInfoAsUser(
+ context: Context,
+ user: UserHandle,
+ ): ShortcutInfo {
+ val extras = PersistableBundle()
+ getDefaultRoleHolderAsUser(ROLE_NOTES, user)?.let { packageName ->
+ // Set custom app badge using the icon from ROLES_NOTES default app.
+ extras.putString(NoteTaskController.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, packageName)
+ }
+
+ val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
+
+ return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
+ .setIntent(LaunchNoteTaskActivity.newIntent(context = context))
+ .setShortLabel(context.getString(R.string.note_task_button_label))
+ .setLongLived(true)
+ .setIcon(icon)
+ .setExtras(extras)
+ .build()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
index 5c59532..0cfb0a5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
+@file:OptIn(InternalNoteTaskApi::class)
+
package com.android.systemui.notetask.shortcut
import android.app.Activity
import android.app.role.RoleManager
-import android.content.Intent
+import android.content.pm.ShortcutManager
import android.os.Bundle
-import android.os.PersistableBundle
import androidx.activity.ComponentActivity
-import androidx.annotation.DrawableRes
-import androidx.core.content.pm.ShortcutInfoCompat
-import androidx.core.content.pm.ShortcutManagerCompat
-import androidx.core.graphics.drawable.IconCompat
-import com.android.systemui.R
+import com.android.systemui.notetask.InternalNoteTaskApi
+import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
import javax.inject.Inject
/**
@@ -42,62 +40,16 @@
@Inject
constructor(
private val roleManager: RoleManager,
+ private val shortcutManager: ShortcutManager,
) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val intent =
- createShortcutIntent(
- id = SHORTCUT_ID,
- shortLabel = getString(R.string.note_task_button_label),
- intent = LaunchNoteTaskActivity.newIntent(context = this),
- iconResource = R.drawable.ic_note_task_shortcut_widget,
- )
- setResult(Activity.RESULT_OK, intent)
+ val shortcutInfo = roleManager.createNoteShortcutInfoAsUser(context = this, user)
+ val shortcutIntent = shortcutManager.createShortcutResultIntent(shortcutInfo)
+ setResult(Activity.RESULT_OK, shortcutIntent)
finish()
}
-
- private fun createShortcutIntent(
- id: String,
- shortLabel: String,
- intent: Intent,
- @DrawableRes iconResource: Int,
- ): Intent {
- val extras = PersistableBundle()
-
- roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()?.let { name ->
- extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, name)
- }
-
- val shortcutInfo =
- ShortcutInfoCompat.Builder(this, id)
- .setIntent(intent)
- .setShortLabel(shortLabel)
- .setLongLived(true)
- .setIcon(IconCompat.createWithResource(this, iconResource))
- .setExtras(extras)
- .build()
-
- return ShortcutManagerCompat.createShortcutResultIntent(
- this,
- shortcutInfo,
- )
- }
-
- private companion object {
- private const val SHORTCUT_ID = "note-task-shortcut-id"
-
- /**
- * Shortcut extra which can point to a package name and can be used to indicate an alternate
- * badge info. Launcher only reads this if the shortcut comes from a system app.
- *
- * Duplicated from [com.android.launcher3.icons.IconCache].
- *
- * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
- */
- private const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE =
- "extra_shortcut_badge_override_package"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2668d2e..fdab9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -91,16 +91,19 @@
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
- mQSLogger.logOnConfigurationChanged(
- /* lastOrientation= */ mLastOrientation,
- /* newOrientation= */ newConfig.orientation,
- /* containerName= */ mView.getDumpableTag());
-
- boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
+ final boolean previousSplitShadeState = mShouldUseSplitNotificationShade;
+ final int previousOrientation = mLastOrientation;
mShouldUseSplitNotificationShade =
- LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
+ LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
mLastOrientation = newConfig.orientation;
+ mQSLogger.logOnConfigurationChanged(
+ /* oldOrientation= */ previousOrientation,
+ /* newOrientation= */ mLastOrientation,
+ /* oldShouldUseSplitShade= */ previousSplitShadeState,
+ /* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade,
+ /* containerName= */ mView.getDumpableTag());
+
switchTileLayoutIfNeeded();
onConfigurationChanged();
if (previousSplitShadeState != mShouldUseSplitNotificationShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 2083cc7..5e4f531 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -136,9 +136,8 @@
mServices.remove(tile);
mTokenMap.remove(service.getToken());
mTiles.remove(tile.getComponent());
- final String slot = tile.getComponent().getClassName();
- // TileServices doesn't know how to add more than 1 icon per slot, so remove all
- mMainHandler.post(() -> mStatusBarIconController.removeAllIconsForSlot(slot));
+ final String slot = getStatusBarIconSlotName(tile.getComponent());
+ mMainHandler.post(() -> mStatusBarIconController.removeIconForTile(slot));
}
}
@@ -312,12 +311,11 @@
? new StatusBarIcon(userHandle, packageName, icon, 0, 0,
contentDescription)
: null;
+ final String slot = getStatusBarIconSlotName(componentName);
mMainHandler.post(new Runnable() {
@Override
public void run() {
- StatusBarIconController iconController = mStatusBarIconController;
- iconController.setIcon(componentName.getClassName(), statusIcon);
- iconController.setExternalIcon(componentName.getClassName());
+ mStatusBarIconController.setIconFromTile(slot, statusIcon);
}
});
}
@@ -377,6 +375,12 @@
mCommandQueue.removeCallback(mRequestListeningCallback);
}
+ /** Returns the slot name that should be used when adding or removing status bar icons. */
+ private String getStatusBarIconSlotName(ComponentName componentName) {
+ return componentName.getClassName();
+ }
+
+
private final CommandQueue.Callbacks mRequestListeningCallback = new CommandQueue.Callbacks() {
@Override
public void requestTileServiceListeningState(@NonNull ComponentName componentName) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 23c41db..5b461a6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -16,8 +16,12 @@
package com.android.systemui.qs.logging
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.content.res.Configuration.Orientation
import android.service.quicksettings.Tile
import android.view.View
+import com.android.systemui.log.dagger.QSConfigLog
import com.android.systemui.log.dagger.QSLog
import com.android.systemui.plugins.log.ConstantStringsLogger
import com.android.systemui.plugins.log.ConstantStringsLoggerImpl
@@ -32,8 +36,12 @@
private const val TAG = "QSLog"
-class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) :
- ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
+class QSLogger
+@Inject
+constructor(
+ @QSLog private val buffer: LogBuffer,
+ @QSConfigLog private val configChangedBuffer: LogBuffer,
+) : ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
fun logException(@CompileTimeConstant logMsg: String, ex: Exception) {
buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
@@ -264,19 +272,28 @@
}
fun logOnConfigurationChanged(
- lastOrientation: Int,
- newOrientation: Int,
+ @Orientation oldOrientation: Int,
+ @Orientation newOrientation: Int,
+ newShouldUseSplitShade: Boolean,
+ oldShouldUseSplitShade: Boolean,
containerName: String
) {
- buffer.log(
+ configChangedBuffer.log(
TAG,
DEBUG,
{
str1 = containerName
- int1 = lastOrientation
+ int1 = oldOrientation
int2 = newOrientation
+ bool1 = oldShouldUseSplitShade
+ bool2 = newShouldUseSplitShade
},
- { "configuration change: $str1 orientation was $int1, now $int2" }
+ {
+ "config change: " +
+ "$str1 orientation=${toOrientationString(int2)} " +
+ "(was ${toOrientationString(int1)}), " +
+ "splitShade=$bool2 (was $bool1)"
+ }
)
}
@@ -353,3 +370,11 @@
}
}
}
+
+private inline fun toOrientationString(@Orientation orientation: Int): String {
+ return when (orientation) {
+ ORIENTATION_LANDSCAPE -> "land"
+ ORIENTATION_PORTRAIT -> "port"
+ else -> "undefined"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index df1c8df..08fe270 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -18,24 +18,26 @@
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
import android.widget.Switch;
-import androidx.annotation.Nullable;
-
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
@@ -50,6 +52,7 @@
import com.android.systemui.statusbar.policy.BluetoothController;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -60,8 +63,14 @@
private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
+ private static final String TAG = BluetoothTile.class.getSimpleName();
+
private final BluetoothController mController;
+ private CachedBluetoothDevice mMetadataRegisteredDevice = null;
+
+ private final Executor mExecutor;
+
@Inject
public BluetoothTile(
QSHost host,
@@ -78,6 +87,7 @@
statusBarStateController, activityStarter, qsLogger);
mController = bluetoothController;
mController.observe(getLifecycle(), mCallback);
+ mExecutor = new HandlerExecutor(mainHandler);
}
@Override
@@ -117,6 +127,15 @@
}
@Override
+ protected void handleSetListening(boolean listening) {
+ super.handleSetListening(listening);
+
+ if (!listening) {
+ stopListeningToStaleDeviceMetadata();
+ }
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_BLUETOOTH);
final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
@@ -125,6 +144,9 @@
final boolean connecting = mController.isBluetoothConnecting();
state.isTransient = transientEnabling || connecting ||
mController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
+ if (!enabled || !connected || state.isTransient) {
+ stopListeningToStaleDeviceMetadata();
+ }
state.dualTarget = true;
state.value = enabled;
if (state.slash == null) {
@@ -187,23 +209,32 @@
List<CachedBluetoothDevice> connectedDevices = mController.getConnectedDevices();
if (enabled && connected && !connectedDevices.isEmpty()) {
if (connectedDevices.size() > 1) {
+ stopListeningToStaleDeviceMetadata();
return icuMessageFormat(mContext.getResources(),
R.string.quick_settings_hotspot_secondary_label_num_devices,
connectedDevices.size());
}
- CachedBluetoothDevice lastDevice = connectedDevices.get(0);
- final int batteryLevel = lastDevice.getBatteryLevel();
+ CachedBluetoothDevice device = connectedDevices.get(0);
+
+ // Use battery level provided by FastPair metadata if available.
+ // If not, fallback to the default battery level from bluetooth.
+ int batteryLevel = getMetadataBatteryLevel(device);
+ if (batteryLevel > BluetoothUtils.META_INT_ERROR) {
+ listenToMetadata(device);
+ } else {
+ stopListeningToStaleDeviceMetadata();
+ batteryLevel = device.getBatteryLevel();
+ }
if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
return mContext.getString(
R.string.quick_settings_bluetooth_secondary_label_battery_level,
Utils.formatPercentage(batteryLevel));
-
} else {
- final BluetoothClass bluetoothClass = lastDevice.getBtClass();
+ final BluetoothClass bluetoothClass = device.getBtClass();
if (bluetoothClass != null) {
- if (lastDevice.isHearingAidDevice()) {
+ if (device.isHearingAidDevice()) {
return mContext.getString(
R.string.quick_settings_bluetooth_secondary_label_hearing_aids);
} else if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
@@ -233,6 +264,36 @@
return mController.isBluetoothSupported();
}
+ private int getMetadataBatteryLevel(CachedBluetoothDevice device) {
+ return BluetoothUtils.getIntMetaData(device.getDevice(),
+ BluetoothDevice.METADATA_MAIN_BATTERY);
+ }
+
+ private void listenToMetadata(CachedBluetoothDevice cachedDevice) {
+ if (cachedDevice == mMetadataRegisteredDevice) return;
+ stopListeningToStaleDeviceMetadata();
+ try {
+ mController.addOnMetadataChangedListener(cachedDevice,
+ mExecutor,
+ mMetadataChangedListener);
+ mMetadataRegisteredDevice = cachedDevice;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Battery metadata listener already registered for device.");
+ }
+ }
+
+ private void stopListeningToStaleDeviceMetadata() {
+ if (mMetadataRegisteredDevice == null) return;
+ try {
+ mController.removeOnMetadataChangedListener(
+ mMetadataRegisteredDevice,
+ mMetadataChangedListener);
+ mMetadataRegisteredDevice = null;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Battery metadata listener already unregistered for device.");
+ }
+ }
+
private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
@Override
public void onBluetoothStateChange(boolean enabled) {
@@ -244,4 +305,9 @@
refreshState();
}
};
+
+ private final BluetoothAdapter.OnMetadataChangedListener mMetadataChangedListener =
+ (device, key, value) -> {
+ if (key == BluetoothDevice.METADATA_MAIN_BATTERY) refreshState();
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index aa2ea0b6..75d0172 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -35,6 +35,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.graph.SignalDrawable;
@@ -174,6 +175,15 @@
@Nullable
String mEthernetContentDescription;
+ public void copyTo(EthernetCallbackInfo ethernetCallbackInfo) {
+ if (ethernetCallbackInfo == null) {
+ throw new IllegalArgumentException();
+ }
+ ethernetCallbackInfo.mConnected = this.mConnected;
+ ethernetCallbackInfo.mEthernetSignalIconId = this.mEthernetSignalIconId;
+ ethernetCallbackInfo.mEthernetContentDescription = this.mEthernetContentDescription;
+ }
+
@Override
public String toString() {
return new StringBuilder("EthernetCallbackInfo[")
@@ -200,6 +210,23 @@
boolean mNoValidatedNetwork;
boolean mNoNetworksAvailable;
+ public void copyTo(WifiCallbackInfo wifiCallbackInfo) {
+ if (wifiCallbackInfo == null) {
+ throw new IllegalArgumentException();
+ }
+ wifiCallbackInfo.mAirplaneModeEnabled = this.mAirplaneModeEnabled;
+ wifiCallbackInfo.mEnabled = this.mEnabled;
+ wifiCallbackInfo.mConnected = this.mConnected;
+ wifiCallbackInfo.mWifiSignalIconId = this.mWifiSignalIconId;
+ wifiCallbackInfo.mSsid = this.mSsid;
+ wifiCallbackInfo.mWifiSignalContentDescription = this.mWifiSignalContentDescription;
+ wifiCallbackInfo.mIsTransient = this.mIsTransient;
+ wifiCallbackInfo.mStatusLabel = this.mStatusLabel;
+ wifiCallbackInfo.mNoDefaultNetwork = this.mNoDefaultNetwork;
+ wifiCallbackInfo.mNoValidatedNetwork = this.mNoValidatedNetwork;
+ wifiCallbackInfo.mNoNetworksAvailable = this.mNoNetworksAvailable;
+ }
+
@Override
public String toString() {
return new StringBuilder("WifiCallbackInfo[")
@@ -232,6 +259,23 @@
boolean mNoValidatedNetwork;
boolean mNoNetworksAvailable;
+ public void copyTo(CellularCallbackInfo cellularCallbackInfo) {
+ if (cellularCallbackInfo == null) {
+ throw new IllegalArgumentException();
+ }
+ cellularCallbackInfo.mAirplaneModeEnabled = this.mAirplaneModeEnabled;
+ cellularCallbackInfo.mDataSubscriptionName = this.mDataSubscriptionName;
+ cellularCallbackInfo.mDataContentDescription = this.mDataContentDescription;
+ cellularCallbackInfo.mMobileSignalIconId = this.mMobileSignalIconId;
+ cellularCallbackInfo.mQsTypeIcon = this.mQsTypeIcon;
+ cellularCallbackInfo.mNoSim = this.mNoSim;
+ cellularCallbackInfo.mRoaming = this.mRoaming;
+ cellularCallbackInfo.mMultipleSubs = this.mMultipleSubs;
+ cellularCallbackInfo.mNoDefaultNetwork = this.mNoDefaultNetwork;
+ cellularCallbackInfo.mNoValidatedNetwork = this.mNoValidatedNetwork;
+ cellularCallbackInfo.mNoNetworksAvailable = this.mNoNetworksAvailable;
+ }
+
@Override
public String toString() {
return new StringBuilder("CellularCallbackInfo[")
@@ -251,8 +295,11 @@
}
protected final class InternetSignalCallback implements SignalCallback {
+ @GuardedBy("mWifiInfo")
final WifiCallbackInfo mWifiInfo = new WifiCallbackInfo();
+ @GuardedBy("mCellularInfo")
final CellularCallbackInfo mCellularInfo = new CellularCallbackInfo();
+ @GuardedBy("mEthernetInfo")
final EthernetCallbackInfo mEthernetInfo = new EthernetCallbackInfo();
@@ -261,19 +308,23 @@
if (DEBUG) {
Log.d(TAG, "setWifiIndicators: " + indicators);
}
- mWifiInfo.mEnabled = indicators.enabled;
- mWifiInfo.mSsid = indicators.description;
- mWifiInfo.mIsTransient = indicators.isTransient;
- mWifiInfo.mStatusLabel = indicators.statusLabel;
+ synchronized (mWifiInfo) {
+ mWifiInfo.mEnabled = indicators.enabled;
+ mWifiInfo.mSsid = indicators.description;
+ mWifiInfo.mIsTransient = indicators.isTransient;
+ mWifiInfo.mStatusLabel = indicators.statusLabel;
+ if (indicators.qsIcon != null) {
+ mWifiInfo.mConnected = indicators.qsIcon.visible;
+ mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
+ mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
+ } else {
+ mWifiInfo.mConnected = false;
+ mWifiInfo.mWifiSignalIconId = 0;
+ mWifiInfo.mWifiSignalContentDescription = null;
+ }
+ }
if (indicators.qsIcon != null) {
- mWifiInfo.mConnected = indicators.qsIcon.visible;
- mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
- mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
refreshState(mWifiInfo);
- } else {
- mWifiInfo.mConnected = false;
- mWifiInfo.mWifiSignalIconId = 0;
- mWifiInfo.mWifiSignalContentDescription = null;
}
}
@@ -286,14 +337,16 @@
// Not data sim, don't display.
return;
}
- mCellularInfo.mDataSubscriptionName = indicators.qsDescription == null
+ synchronized (mCellularInfo) {
+ mCellularInfo.mDataSubscriptionName = indicators.qsDescription == null
? mController.getMobileDataNetworkName() : indicators.qsDescription;
- mCellularInfo.mDataContentDescription = indicators.qsDescription != null
+ mCellularInfo.mDataContentDescription = indicators.qsDescription != null
? indicators.typeContentDescriptionHtml : null;
- mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
- mCellularInfo.mQsTypeIcon = indicators.qsType;
- mCellularInfo.mRoaming = indicators.roaming;
- mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
+ mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
+ mCellularInfo.mQsTypeIcon = indicators.qsType;
+ mCellularInfo.mRoaming = indicators.roaming;
+ mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
+ }
refreshState(mCellularInfo);
}
@@ -303,9 +356,11 @@
Log.d(TAG, "setEthernetIndicators: "
+ "icon = " + (icon == null ? "" : icon.toString()));
}
- mEthernetInfo.mConnected = icon.visible;
- mEthernetInfo.mEthernetSignalIconId = icon.icon;
- mEthernetInfo.mEthernetContentDescription = icon.contentDescription;
+ synchronized (mEthernetInfo) {
+ mEthernetInfo.mConnected = icon.visible;
+ mEthernetInfo.mEthernetSignalIconId = icon.icon;
+ mEthernetInfo.mEthernetContentDescription = icon.contentDescription;
+ }
if (icon.visible) {
refreshState(mEthernetInfo);
}
@@ -318,11 +373,13 @@
+ "show = " + show + ","
+ "simDetected = " + simDetected);
}
- mCellularInfo.mNoSim = show;
- if (mCellularInfo.mNoSim) {
- // Make sure signal gets cleared out when no sims.
- mCellularInfo.mMobileSignalIconId = 0;
- mCellularInfo.mQsTypeIcon = 0;
+ synchronized (mCellularInfo) {
+ mCellularInfo.mNoSim = show;
+ if (mCellularInfo.mNoSim) {
+ // Make sure signal gets cleared out when no sims.
+ mCellularInfo.mMobileSignalIconId = 0;
+ mCellularInfo.mQsTypeIcon = 0;
+ }
}
}
@@ -335,8 +392,12 @@
if (mCellularInfo.mAirplaneModeEnabled == icon.visible) {
return;
}
- mCellularInfo.mAirplaneModeEnabled = icon.visible;
- mWifiInfo.mAirplaneModeEnabled = icon.visible;
+ synchronized (mCellularInfo) {
+ mCellularInfo.mAirplaneModeEnabled = icon.visible;
+ }
+ synchronized (mWifiInfo) {
+ mWifiInfo.mAirplaneModeEnabled = icon.visible;
+ }
if (!mSignalCallback.mEthernetInfo.mConnected) {
// Always use mWifiInfo to refresh the Internet Tile if airplane mode is enabled,
// because Internet Tile will show different information depending on whether WiFi
@@ -363,12 +424,16 @@
+ "noValidatedNetwork = " + noValidatedNetwork + ","
+ "noNetworksAvailable = " + noNetworksAvailable);
}
- mCellularInfo.mNoDefaultNetwork = noDefaultNetwork;
- mCellularInfo.mNoValidatedNetwork = noValidatedNetwork;
- mCellularInfo.mNoNetworksAvailable = noNetworksAvailable;
- mWifiInfo.mNoDefaultNetwork = noDefaultNetwork;
- mWifiInfo.mNoValidatedNetwork = noValidatedNetwork;
- mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
+ synchronized (mCellularInfo) {
+ mCellularInfo.mNoDefaultNetwork = noDefaultNetwork;
+ mCellularInfo.mNoValidatedNetwork = noValidatedNetwork;
+ mCellularInfo.mNoNetworksAvailable = noNetworksAvailable;
+ }
+ synchronized (mWifiInfo) {
+ mWifiInfo.mNoDefaultNetwork = noDefaultNetwork;
+ mWifiInfo.mNoValidatedNetwork = noValidatedNetwork;
+ mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
+ }
if (!noDefaultNetwork) {
return;
}
@@ -403,11 +468,23 @@
// arg = null, in this case the last updated CellularCallbackInfo or WifiCallbackInfo
// should be used to refresh the tile.
if (mLastTileState == LAST_STATE_CELLULAR) {
- handleUpdateCellularState(state, mSignalCallback.mCellularInfo);
+ CellularCallbackInfo cellularInfo = new CellularCallbackInfo();
+ synchronized (mSignalCallback.mCellularInfo) {
+ mSignalCallback.mCellularInfo.copyTo(cellularInfo);
+ }
+ handleUpdateCellularState(state, cellularInfo);
} else if (mLastTileState == LAST_STATE_WIFI) {
- handleUpdateWifiState(state, mSignalCallback.mWifiInfo);
+ WifiCallbackInfo mifiInfo = new WifiCallbackInfo();
+ synchronized (mSignalCallback.mWifiInfo) {
+ mSignalCallback.mWifiInfo.copyTo(mifiInfo);
+ }
+ handleUpdateWifiState(state, mifiInfo);
} else if (mLastTileState == LAST_STATE_ETHERNET) {
- handleUpdateEthernetState(state, mSignalCallback.mEthernetInfo);
+ EthernetCallbackInfo ethernetInfo = new EthernetCallbackInfo();
+ synchronized (mSignalCallback.mEthernetInfo) {
+ mSignalCallback.mEthernetInfo.copyTo(ethernetInfo);
+ }
+ handleUpdateEthernetState(state, ethernetInfo);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 557e95c..7ad594e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -115,6 +115,8 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -613,11 +615,13 @@
// Note that this may block if the sound is still being loaded (very unlikely) but we can't
// reliably release in the background because the service is being destroyed.
try {
- MediaPlayer player = mCameraSound.get();
+ MediaPlayer player = mCameraSound.get(1, TimeUnit.SECONDS);
if (player != null) {
player.release();
}
- } catch (InterruptedException | ExecutionException e) {
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ mCameraSound.cancel(true);
+ Log.w(TAG, "Error releasing shutter sound", e);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
index 48aa60f..253f07d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
@@ -17,6 +17,8 @@
package com.android.systemui.screenshot
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
+import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
import android.view.Display
import android.view.IWindowManager
import android.view.ViewGroup
@@ -45,7 +47,7 @@
// Convert component names to app names.
return components.map {
packageManager
- .getActivityInfo(it, PackageManager.ComponentInfoFlags.of(0))
+ .getActivityInfo(it, ComponentInfoFlags.of(MATCH_DISABLED_COMPONENTS.toLong()))
.loadLabel(packageManager)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 236213c..798c490 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -19,6 +19,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.os.UserManager
@@ -53,12 +54,9 @@
var badgedIcon: Drawable? = null
var label: CharSequence? = null
val fileManager = fileManagerComponentName()
+ ?: return WorkProfileFirstRunData(defaultFileAppName(), null)
try {
- val info =
- packageManager.getActivityInfo(
- fileManager,
- PackageManager.ComponentInfoFlags.of(0)
- )
+ val info = packageManager.getActivityInfo(fileManager, ComponentInfoFlags.of(0L))
val icon = packageManager.getActivityIcon(fileManager)
badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle)
label = info.loadLabel(packageManager)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1c3e011..827a4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -159,6 +159,7 @@
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
import com.android.systemui.plugins.qs.QS;
@@ -978,6 +979,7 @@
onTrackingStopped(false);
instantCollapse();
} else {
+ mView.animate().cancel();
mView.animate()
.alpha(0f)
.setStartDelay(0)
@@ -1592,10 +1594,9 @@
transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- boolean customClockAnimation =
- mKeyguardStatusViewController.getClockAnimations() != null
- && mKeyguardStatusViewController.getClockAnimations()
- .getHasCustomPositionUpdatedAnimation();
+ ClockAnimations clockAnims = mKeyguardStatusViewController.getClockAnimations();
+ boolean customClockAnimation = clockAnims != null
+ && clockAnims.getHasCustomPositionUpdatedAnimation();
if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
// Find the clock, so we can exclude it from this transition.
@@ -2897,15 +2898,7 @@
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
- NotificationPanelViewController.this);
- }
-
- public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
- if (pickedChild != null) {
- updateTrackingHeadsUp(pickedChild);
- mExpandingFromHeadsUp = true;
- }
- // otherwise we update the state when the expansion is finished
+ new HeadsUpNotificationViewControllerImpl());
}
private void onClosingFinished() {
@@ -2953,7 +2946,8 @@
}
/** Called when a HUN is dragged up or down to indicate the starting height for shade motion. */
- public void setHeadsUpDraggingStartingHeight(int startHeight) {
+ @VisibleForTesting
+ void setHeadsUpDraggingStartingHeight(int startHeight) {
mHeadsUpStartHeight = startHeight;
float scrimMinFraction;
if (mSplitShadeEnabled) {
@@ -2987,10 +2981,6 @@
mScrimController.setPanelScrimMinFraction(mMinFraction);
}
- public void clearNotificationEffects() {
- mCentralSurfaces.clearNotificationEffects();
- }
-
private boolean isPanelVisibleBecauseOfHeadsUp() {
return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
&& mBarState == StatusBarState.SHADE;
@@ -3160,7 +3150,9 @@
*/
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
Runnable cancelAction) {
- mView.animate()
+ final ViewPropertyAnimator viewAnimator = mView.animate();
+ viewAnimator.cancel();
+ viewAnimator
.translationX(0)
.alpha(1f)
.setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
@@ -3179,9 +3171,14 @@
@Override
public void onAnimationEnd(Animator animation) {
endAction.run();
+
+ viewAnimator.setListener(null);
+ viewAnimator.setUpdateListener(null);
}
- }).setUpdateListener(anim -> mKeyguardStatusViewController.animateFoldToAod(
- anim.getAnimatedFraction())).start();
+ })
+ .setUpdateListener(anim ->
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()))
+ .start();
}
/** Cancels fold to AOD transition and resets view state. */
@@ -3380,6 +3377,7 @@
}
public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
+ mView.animate().cancel();
return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
endAction);
@@ -3629,7 +3627,13 @@
: (mKeyguardStateController.canDismissLockScreen()
? UNLOCK : BOUNCER_UNLOCK);
- fling(vel, expand, isFalseTouch(x, y, interactionType));
+ // don't fling while in keyguard to avoid jump in shade expand animation;
+ // touch has been intercepted already so flinging here is redundant
+ if (mBarState == KEYGUARD && mExpandedFraction >= 1.0) {
+ mShadeLog.d("NPVC endMotionEvent - skipping fling on keyguard");
+ } else {
+ fling(vel, expand, isFalseTouch(x, y, interactionType));
+ }
onTrackingStopped(expand);
mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
if (mUpdateFlingOnLayout) {
@@ -5112,17 +5116,26 @@
captureValues(transitionValues);
}
+ @Nullable
@Override
- public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
- TransitionValues endValues) {
+ public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+ @Nullable TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
- anim.addUpdateListener(
- animation -> mController.getClockAnimations().onPositionUpdated(
- from, to, animation.getAnimatedFraction()));
+ anim.addUpdateListener(animation -> {
+ ClockAnimations clockAnims = mController.getClockAnimations();
+ if (clockAnims == null) {
+ return;
+ }
+
+ clockAnims.onPositionUpdated(from, to, animation.getAnimatedFraction());
+ });
return anim;
}
@@ -5133,6 +5146,33 @@
}
}
+ private final class HeadsUpNotificationViewControllerImpl implements
+ HeadsUpTouchHelper.HeadsUpNotificationViewController {
+ @Override
+ public void setHeadsUpDraggingStartingHeight(int startHeight) {
+ NotificationPanelViewController.this.setHeadsUpDraggingStartingHeight(startHeight);
+ }
+
+ @Override
+ public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
+ if (pickedChild != null) {
+ updateTrackingHeadsUp(pickedChild);
+ mExpandingFromHeadsUp = true;
+ }
+ // otherwise we update the state when the expansion is finished
+ }
+
+ @Override
+ public void startExpand(float x, float y, boolean startTracking, float expandedHeight) {
+ startExpandMotion(x, y, startTracking, expandedHeight);
+ }
+
+ @Override
+ public void clearNotificationEffects() {
+ mCentralSurfaces.clearNotificationEffects();
+ }
+ }
+
private final class ShadeAccessibilityDelegate extends AccessibilityDelegate {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5f6f158..0318fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -32,6 +32,8 @@
import android.view.ViewGroup;
import android.view.ViewStub;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.keyguard.LockIconViewController;
@@ -50,6 +52,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
+import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor;
import com.android.systemui.multishade.ui.view.MultiShadeView;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -118,6 +121,7 @@
step.getTransitionState() == TransitionState.RUNNING;
};
private final SystemClock mClock;
+ private final @Nullable MultiShadeMotionEventInteractor mMultiShadeMotionEventInteractor;
@Inject
public NotificationShadeWindowViewController(
@@ -145,7 +149,8 @@
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
FeatureFlags featureFlags,
Provider<MultiShadeInteractor> multiShadeInteractorProvider,
- SystemClock clock) {
+ SystemClock clock,
+ Provider<MultiShadeMotionEventInteractor> multiShadeMotionEventInteractorProvider) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -180,22 +185,18 @@
mClock = clock;
if (ComposeFacade.INSTANCE.isComposeAvailable()
&& featureFlags.isEnabled(Flags.DUAL_SHADE)) {
+ mMultiShadeMotionEventInteractor = multiShadeMotionEventInteractorProvider.get();
final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub);
if (multiShadeViewStub != null) {
final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate();
multiShadeView.init(multiShadeInteractorProvider.get(), clock);
}
+ } else {
+ mMultiShadeMotionEventInteractor = null;
}
}
/**
- * @return Location where to place the KeyguardBouncer
- */
- public ViewGroup getBouncerContainer() {
- return mView.findViewById(R.id.keyguard_bouncer_container);
- }
-
- /**
* @return Location where to place the KeyguardMessageArea
*/
public AuthKeyguardMessageArea getKeyguardMessageArea() {
@@ -349,16 +350,17 @@
return true;
}
- boolean intercept = false;
- if (mNotificationPanelViewController.isFullyExpanded()
+ if (mMultiShadeMotionEventInteractor != null) {
+ // This interactor is not null only if the dual shade feature is enabled.
+ return mMultiShadeMotionEventInteractor.shouldIntercept(ev);
+ } else if (mNotificationPanelViewController.isFullyExpanded()
&& mDragDownHelper.isDragDownEnabled()
&& !mService.isBouncerShowing()
&& !mStatusBarStateController.isDozing()) {
- intercept = mDragDownHelper.onInterceptTouchEvent(ev);
+ return mDragDownHelper.onInterceptTouchEvent(ev);
+ } else {
+ return false;
}
-
- return intercept;
-
}
@Override
@@ -381,13 +383,20 @@
return true;
}
- if ((mDragDownHelper.isDragDownEnabled() && !handled)
- || mDragDownHelper.isDraggingDown()) {
- // we still want to finish our drag down gesture when locking the screen
- handled = mDragDownHelper.onTouchEvent(ev);
+ if (handled) {
+ return true;
}
- return handled;
+ if (mMultiShadeMotionEventInteractor != null) {
+ // This interactor is not null only if the dual shade feature is enabled.
+ return mMultiShadeMotionEventInteractor.onTouchEvent(ev, mView.getWidth());
+ } else if (mDragDownHelper.isDragDownEnabled()
+ || mDragDownHelper.isDraggingDown()) {
+ // we still want to finish our drag down gesture when locking the screen
+ return mDragDownHelper.onTouchEvent(ev);
+ } else {
+ return false;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 5adb58b..63179da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -77,12 +77,6 @@
*/
public static void fadeOut(View view, float fadeOutAmount, boolean remap) {
view.animate().cancel();
-
- // Don't fade out if already not visible.
- if (view.getAlpha() == 0.0f) {
- return;
- }
-
if (fadeOutAmount == 1.0f && view.getVisibility() != View.GONE) {
view.setVisibility(View.INVISIBLE);
} else if (view.getVisibility() == View.INVISIBLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 779be2b..fda2277 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -28,7 +28,6 @@
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
-import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
@@ -99,6 +98,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -146,6 +146,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AuthController mAuthController;
private final KeyguardLogger mKeyguardLogger;
+ private final UserTracker mUserTracker;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -251,7 +252,8 @@
FaceHelpMessageDeferral faceHelpMessageDeferral,
KeyguardLogger keyguardLogger,
AlternateBouncerInteractor alternateBouncerInteractor,
- AlarmManager alarmManager
+ AlarmManager alarmManager,
+ UserTracker userTracker
) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
@@ -275,6 +277,7 @@
mKeyguardLogger = keyguardLogger;
mScreenLifecycle.addObserver(mScreenObserver);
mAlternateBouncerInteractor = alternateBouncerInteractor;
+ mUserTracker = userTracker;
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
@@ -475,6 +478,10 @@
}
}
+ private int getCurrentUser() {
+ return mUserTracker.getUserId();
+ }
+
private void updateLockScreenOwnerInfo() {
// Check device owner info on a bg thread.
// It makes multiple IPCs that could block the thread it's run on.
@@ -1166,8 +1173,7 @@
mContext.getString(R.string.keyguard_unlock)
);
} else if (fpAuthFailed
- && mKeyguardUpdateMonitor.getUserHasTrust(
- KeyguardUpdateMonitor.getCurrentUser())) {
+ && mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())) {
showBiometricMessage(
getTrustGrantedIndication(),
mContext.getString(R.string.keyguard_unlock)
@@ -1421,7 +1427,7 @@
private boolean canUnlockWithFingerprint() {
return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser());
+ getCurrentUser());
}
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 5ba8801..bfb6416 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -94,7 +94,11 @@
/**
* No conditions blocking FSI launch.
*/
- FSI_EXPECTED_NOT_TO_HUN(true);
+ FSI_EXPECTED_NOT_TO_HUN(true),
+ /**
+ * The notification is coming from a suspended packages, so FSI is suppressed.
+ */
+ NO_FSI_SUSPENDED(false);
public final boolean shouldLaunch;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 6f4eed3..4aaa7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -28,7 +28,6 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.SystemProperties;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
@@ -274,6 +273,12 @@
suppressedByDND);
}
+ // Notification is coming from a suspended package, block FSI
+ if (entry.getRanking().isSuspended()) {
+ return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_SUSPENDED,
+ suppressedByDND);
+ }
+
// If the screen is off, then launch the FullScreenIntent
if (!mPowerManager.isInteractive()) {
return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a529da5..a38f527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -47,7 +47,6 @@
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
-import android.util.Property;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -336,8 +335,8 @@
};
private boolean mKeepInParentForDismissAnimation;
private boolean mRemoved;
- private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
- new FloatProperty<ExpandableNotificationRow>("translate") {
+ public static final FloatProperty<ExpandableNotificationRow> TRANSLATE_CONTENT =
+ new FloatProperty<>("translate") {
@Override
public void setValue(ExpandableNotificationRow object, float value) {
object.setTranslation(value);
@@ -348,6 +347,7 @@
return object.getTranslation();
}
};
+
private OnClickListener mOnClickListener;
private OnDragSuccessListener mOnDragSuccessListener;
private boolean mHeadsupDisappearRunning;
@@ -2177,6 +2177,13 @@
return translateAnim;
}
+ /** Cancels the ongoing translate animation if there is any. */
+ public void cancelTranslateAnimation() {
+ if (mTranslateAnim != null) {
+ mTranslateAnim.cancel();
+ }
+ }
+
void ensureGutsInflated() {
if (mGuts == null) {
mGutsStub.inflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index c6f56d4..b476b68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -135,11 +135,15 @@
@Override
protected void onChildSnappedBack(View animView, float targetLeft) {
+ super.onChildSnappedBack(animView, targetLeft);
+
final NotificationMenuRowPlugin menuRow = getCurrentMenuRow();
if (menuRow != null && targetLeft == 0) {
menuRow.resetMenu();
clearCurrentMenuRow();
}
+
+ InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_SWIPE);
}
@Override
@@ -348,18 +352,13 @@
super.dismissChild(view, velocity, useAccelerateInterpolator);
}
- @Override
- protected void onSnapChildWithAnimationFinished() {
- InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_ROW_SWIPE);
- }
-
@VisibleForTesting
protected void superSnapChild(final View animView, final float targetLeft, float velocity) {
super.snapChild(animView, targetLeft, velocity);
}
@Override
- public void snapChild(final View animView, final float targetLeft, float velocity) {
+ protected void snapChild(final View animView, final float targetLeft, float velocity) {
superSnapChild(animView, targetLeft, velocity);
mCallback.onDragCancelled(animView);
if (targetLeft == 0) {
@@ -380,20 +379,18 @@
}
}
+ @Override
@VisibleForTesting
- protected Animator superGetViewTranslationAnimator(View v, float target,
+ protected Animator getViewTranslationAnimator(View view, float target,
ValueAnimator.AnimatorUpdateListener listener) {
- return super.getViewTranslationAnimator(v, target, listener);
+ return super.getViewTranslationAnimator(view, target, listener);
}
@Override
- public Animator getViewTranslationAnimator(View v, float target,
+ @VisibleForTesting
+ protected Animator createTranslationAnimation(View view, float newPos,
ValueAnimator.AnimatorUpdateListener listener) {
- if (v instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
- } else {
- return superGetViewTranslationAnimator(v, target, listener);
- }
+ return super.createTranslationAnimation(view, newPos, listener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 55fa479..7f8c135 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -217,8 +217,6 @@
NotificationPanelViewController getNotificationPanelViewController();
- ViewGroup getBouncerContainer();
-
/** Get the Keyguard Message Area that displays auth messages. */
AuthKeyguardMessageArea getKeyguardMessageArea();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5e3c1c5..2f40487 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1722,11 +1722,6 @@
}
@Override
- public ViewGroup getBouncerContainer() {
- return mNotificationShadeWindowViewController.getBouncerContainer();
- }
-
- @Override
public AuthKeyguardMessageArea getKeyguardMessageArea() {
return mNotificationShadeWindowViewController.getKeyguardMessageArea();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 90d0b69..16c2e36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -21,7 +21,6 @@
import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -31,21 +30,21 @@
*/
public class HeadsUpTouchHelper implements Gefingerpoken {
- private HeadsUpManagerPhone mHeadsUpManager;
- private Callback mCallback;
+ private final HeadsUpManagerPhone mHeadsUpManager;
+ private final Callback mCallback;
private int mTrackingPointer;
- private float mTouchSlop;
+ private final float mTouchSlop;
private float mInitialTouchX;
private float mInitialTouchY;
private boolean mTouchingHeadsUpView;
private boolean mTrackingHeadsUp;
private boolean mCollapseSnoozes;
- private NotificationPanelViewController mPanel;
+ private final HeadsUpNotificationViewController mPanel;
private ExpandableNotificationRow mPickedChild;
public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
Callback callback,
- NotificationPanelViewController notificationPanelView) {
+ HeadsUpNotificationViewController notificationPanelView) {
mHeadsUpManager = headsUpManager;
mCallback = callback;
mPanel = notificationPanelView;
@@ -116,7 +115,7 @@
int startHeight = (int) (mPickedChild.getActualHeight()
+ mPickedChild.getTranslationY());
mPanel.setHeadsUpDraggingStartingHeight(startHeight);
- mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight);
+ mPanel.startExpand(x, y, true /* startTracking */, startHeight);
// This call needs to be after the expansion start otherwise we will get a
// flicker of one frame as it's not expanded yet.
mHeadsUpManager.unpinAll(true);
@@ -181,4 +180,19 @@
boolean isExpanded();
Context getContext();
}
+
+ /** The controller for a view that houses heads up notifications. */
+ public interface HeadsUpNotificationViewController {
+ /** Called when a HUN is dragged to indicate the starting height for shade motion. */
+ void setHeadsUpDraggingStartingHeight(int startHeight);
+
+ /** Sets notification that is being expanded. */
+ void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow);
+
+ /** Called when a MotionEvent is about to trigger expansion. */
+ void startExpand(float newX, float newY, boolean startTracking, float expandedHeight);
+
+ /** Clear any effects that were added for the expansion. */
+ void clearNotificationEffects();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 9a5d1b5..62d302f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -26,6 +26,8 @@
import android.view.ViewTreeObserver
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
@@ -215,6 +217,7 @@
private val unfoldComponent: Optional<SysUIUnfoldComponent>,
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
+ private val featureFlags: FeatureFlags,
private val userChipViewModel: StatusBarUserChipViewModel,
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
@@ -224,17 +227,25 @@
) {
fun create(
view: PhoneStatusBarView
- ) =
- PhoneStatusBarViewController(
- view,
- progressProvider.getOrNull(),
- centralSurfaces,
- shadeController,
- shadeLogger,
- unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
- userChipViewModel,
- viewUtil,
- configurationController
+ ): PhoneStatusBarViewController {
+ val statusBarMoveFromCenterAnimationController =
+ if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
+ unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
+ } else {
+ null
+ }
+
+ return PhoneStatusBarViewController(
+ view,
+ progressProvider.getOrNull(),
+ centralSurfaces,
+ shadeController,
+ shadeLogger,
+ statusBarMoveFromCenterAnimationController,
+ userChipViewModel,
+ viewUtil,
+ configurationController
)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 04cc8ce..30d2295 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -81,28 +81,22 @@
void refreshIconGroup(IconManager iconManager);
/**
- * Adds or updates an icon for a given slot for a **tile service icon**.
+ * Adds or updates an icon that comes from an active tile service.
*
- * TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method
- * much more clearly distinct from that method.
+ * If the icon is null, the icon will be removed.
*/
- void setExternalIcon(String slot);
+ void setIconFromTile(String slot, @Nullable StatusBarIcon icon);
+
+ /** Removes an icon that had come from an active tile service. */
+ void removeIconForTile(String slot);
/**
* Adds or updates an icon for the given slot for **internal system icons**.
*
- * TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the
- * {@link #setIcon(String, StatusBarIcon)} method.
+ * TODO(b/265307726): Re-name this to `setInternalIcon`.
*/
void setIcon(String slot, int resourceId, CharSequence contentDescription);
- /**
- * Adds or updates an icon for the given slot for an **externally-provided icon**.
- *
- * TODO(b/265307726): Rename to `setExternalIcon` or something similar.
- */
- void setIcon(String slot, StatusBarIcon icon);
-
/** */
void setWifiIcon(String slot, WifiIconState state);
@@ -152,15 +146,10 @@
*/
void removeIcon(String slot, int tag);
- /** */
- void removeAllIconsForSlot(String slot);
-
/**
- * Removes all the icons for the given slot.
- *
- * Only use this for icons that have come from **an external process**.
+ * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`.
*/
- void removeAllIconsForExternalSlot(String slot);
+ void removeAllIconsForSlot(String slot);
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
@@ -618,13 +607,6 @@
mGroup.removeAllViews();
}
- protected void onIconExternal(int viewIndex, int height) {
- ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex);
- imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
- imageView.setAdjustViewBounds(true);
- setHeightAndCenter(imageView, height);
- }
-
protected void onDensityOrFontScaleChanged() {
for (int i = 0; i < mGroup.getChildCount(); i++) {
View child = mGroup.getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 0727c5a..3a18423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -32,7 +32,6 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Dumpable;
-import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -62,7 +61,7 @@
*/
@SysUISingleton
public class StatusBarIconControllerImpl implements Tunable,
- ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
+ ConfigurationListener, Dumpable, StatusBarIconController, DemoMode {
private static final String TAG = "StatusBarIconController";
// Use this suffix to prevent external icon slot names from unintentionally overriding our
@@ -93,7 +92,7 @@
mStatusBarPipelineFlags = statusBarPipelineFlags;
configurationController.addCallback(this);
- commandQueue.addCallback(this);
+ commandQueue.addCallback(mCommandQueueCallbacks);
tunerService.addTunable(this, ICON_HIDE_LIST);
demoModeController.addCallback(this);
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -350,26 +349,35 @@
}
}
- @Override
- public void setExternalIcon(String slot) {
- String slotName = createExternalSlotName(slot);
- int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0);
- int height = mContext.getResources().getDimensionPixelSize(
- R.dimen.status_bar_icon_drawing_size);
- mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
- }
-
- // Override for *both* CommandQueue.Callbacks AND StatusBarIconController.
- // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
- // differentiate between those callback methods and StatusBarIconController methods.
- @Override
- public void setIcon(String slot, StatusBarIcon icon) {
- String slotName = createExternalSlotName(slot);
- if (icon == null) {
- removeAllIconsForSlot(slotName);
- return;
+ private final CommandQueue.Callbacks mCommandQueueCallbacks = new CommandQueue.Callbacks() {
+ @Override
+ public void setIcon(String slot, StatusBarIcon icon) {
+ // Icons that come from CommandQueue are from external services.
+ setExternalIcon(slot, icon);
}
+ @Override
+ public void removeIcon(String slot) {
+ removeAllIconsForExternalSlot(slot);
+ }
+ };
+
+ @Override
+ public void setIconFromTile(String slot, StatusBarIcon icon) {
+ setExternalIcon(slot, icon);
+ }
+
+ @Override
+ public void removeIconForTile(String slot) {
+ removeAllIconsForExternalSlot(slot);
+ }
+
+ private void setExternalIcon(String slot, StatusBarIcon icon) {
+ if (icon == null) {
+ removeAllIconsForExternalSlot(slot);
+ return;
+ }
+ String slotName = createExternalSlotName(slot);
StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
setIcon(slotName, holder);
}
@@ -417,14 +425,6 @@
}
}
- // CommandQueue.Callbacks override
- // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to
- // differentiate between those callback methods and StatusBarIconController methods.
- @Override
- public void removeIcon(String slot) {
- removeAllIconsForExternalSlot(slot);
- }
-
/** */
@Override
public void removeIcon(String slot, int tag) {
@@ -444,8 +444,7 @@
mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
}
- @Override
- public void removeAllIconsForExternalSlot(String slotName) {
+ private void removeAllIconsForExternalSlot(String slotName) {
removeAllIconsForSlot(createExternalSlotName(slotName));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 06d0758..f06b5db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -381,7 +381,6 @@
mCentralSurfaces = centralSurfaces;
mBiometricUnlockController = biometricUnlockController;
- ViewGroup container = mCentralSurfaces.getBouncerContainer();
mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
mNotificationPanelViewController = notificationPanelViewController;
if (shadeExpansionStateManager != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index ed978c3..24ddded 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -453,9 +453,6 @@
protected int adjustDisableFlags(int state) {
boolean headsUpVisible =
mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
- if (headsUpVisible) {
- state |= DISABLE_CLOCK;
- }
if (!mKeyguardStateController.isLaunchTransitionFadingAway()
&& !mKeyguardStateController.isKeyguardFadingAway()
@@ -473,6 +470,13 @@
state |= DISABLE_ONGOING_CALL_CHIP;
}
+ if (headsUpVisible) {
+ // Disable everything on the left side of the status bar, since the app name for the
+ // heads up notification appears there instead.
+ state |= DISABLE_CLOCK;
+ state |= DISABLE_ONGOING_CALL_CHIP;
+ }
+
return state;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 4156fc1..73bf188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -179,6 +179,10 @@
fun logDefaultMobileIconGroup(group: SignalIcon.MobileIconGroup) {
buffer.log(TAG, LogLevel.INFO, { str1 = group.name }, { "defaultMobileIconGroup: $str1" })
}
+
+ fun logOnSubscriptionsChanged() {
+ buffer.log(TAG, LogLevel.INFO, {}, { "onSubscriptionsChanged" })
+ }
}
private const val TAG = "MobileInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f866d65..d0c6215 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -105,10 +105,14 @@
* The reason we need to do this is because TelephonyManager limits the number of registered
* listeners per-process, so we don't want to create a new listener for every callback.
*
- * A note on the design for back pressure here: We use the [coalesce] operator here to change
- * the backpressure strategy to store exactly the last callback event of _each type_ here, as
- * opposed to the default strategy which is to drop the oldest event (regardless of type). This
- * means that we should never miss any single event as long as the flow has been started.
+ * A note on the design for back pressure here: We don't control _which_ telephony callback
+ * comes in first, since we register every relevant bit of information as a batch. E.g., if a
+ * downstream starts collecting on a field which is backed by
+ * [TelephonyCallback.ServiceStateListener], it's not possible for us to guarantee that _that_
+ * callback comes in -- the first callback could very well be
+ * [TelephonyCallback.DataActivityListener], which would promptly be dropped if we didn't keep
+ * it tracked. We use the [scan] operator here to track the most recent callback of _each type_
+ * here. See [TelephonyCallbackState] to see how the callbacks are stored.
*/
private val callbackEvents: StateFlow<TelephonyCallbackState> = run {
val initial = TelephonyCallbackState()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index b7da3f2..991b786 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -129,6 +129,7 @@
val callback =
object : SubscriptionManager.OnSubscriptionsChangedListener() {
override fun onSubscriptionsChanged() {
+ logger.logOnSubscriptionsChanged()
trySend(Unit)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 2ee5232..654ba04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -57,6 +57,8 @@
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.concurrent.GuardedBy;
+
/**
* Default implementation of a {@link BatteryController}. This controller monitors for battery
* level change events that are broadcasted by the system.
@@ -94,7 +96,10 @@
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
+ @GuardedBy("mEstimateLock")
private Estimate mEstimate;
+ private final Object mEstimateLock = new Object();
+
private boolean mFetchingEstimate = false;
// Use AtomicReference because we may request it from a different thread
@@ -321,7 +326,7 @@
@Nullable
private String generateTimeRemainingString() {
- synchronized (mFetchCallbacks) {
+ synchronized (mEstimateLock) {
if (mEstimate == null) {
return null;
}
@@ -340,7 +345,7 @@
mFetchingEstimate = true;
mBgHandler.post(() -> {
// Only fetch the estimate if they are enabled
- synchronized (mFetchCallbacks) {
+ synchronized (mEstimateLock) {
mEstimate = null;
if (mEstimates.isHybridNotificationEnabled()) {
updateEstimate();
@@ -363,6 +368,7 @@
}
@WorkerThread
+ @GuardedBy("mEstimateLock")
private void updateEstimate() {
Assert.isNotMainThread();
// if the estimate has been cached we can just use that, otherwise get a new one and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 0c5b851..3429e25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,12 +16,15 @@
package com.android.systemui.statusbar.policy;
+import android.bluetooth.BluetoothAdapter;
+
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.BluetoothController.Callback;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.Executor;
public interface BluetoothController extends CallbackController<Callback>, Dumpable {
boolean isBluetoothSupported();
@@ -44,6 +47,11 @@
int getBondState(CachedBluetoothDevice device);
List<CachedBluetoothDevice> getConnectedDevices();
+ void addOnMetadataChangedListener(CachedBluetoothDevice device, Executor executor,
+ BluetoothAdapter.OnMetadataChangedListener listener);
+ void removeOnMetadataChangedListener(CachedBluetoothDevice device,
+ BluetoothAdapter.OnMetadataChangedListener listener);
+
public interface Callback {
void onBluetoothStateChange(boolean enabled);
void onBluetoothDevicesChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index acdf0d2..c804fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -48,6 +48,7 @@
import java.util.Collection;
import java.util.List;
import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -78,6 +79,7 @@
private final H mHandler;
private int mState;
+ private final BluetoothAdapter mAdapter;
/**
*/
@Inject
@@ -88,7 +90,8 @@
BluetoothLogger logger,
@Background Looper bgLooper,
@Main Looper mainLooper,
- @Nullable LocalBluetoothManager localBluetoothManager) {
+ @Nullable LocalBluetoothManager localBluetoothManager,
+ @Nullable BluetoothAdapter bluetoothAdapter) {
mDumpManager = dumpManager;
mLogger = logger;
mLocalBluetoothManager = localBluetoothManager;
@@ -103,6 +106,7 @@
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mCurrentUser = userTracker.getUserId();
mDumpManager.registerDumpable(TAG, this);
+ mAdapter = bluetoothAdapter;
}
@Override
@@ -412,6 +416,30 @@
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
+ public void addOnMetadataChangedListener(
+ @NonNull CachedBluetoothDevice cachedDevice,
+ Executor executor,
+ BluetoothAdapter.OnMetadataChangedListener listener
+ ) {
+ if (mAdapter == null) return;
+ mAdapter.addOnMetadataChangedListener(
+ cachedDevice.getDevice(),
+ executor,
+ listener
+ );
+ }
+
+ public void removeOnMetadataChangedListener(
+ @NonNull CachedBluetoothDevice cachedDevice,
+ BluetoothAdapter.OnMetadataChangedListener listener
+ ) {
+ if (mAdapter == null) return;
+ mAdapter.removeOnMetadataChangedListener(
+ cachedDevice.getDevice(),
+ listener
+ );
+ }
+
private ActuallyCachedState getCachedState(CachedBluetoothDevice device) {
ActuallyCachedState state = mCachedState.get(device);
if (state == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 805368c..f1269f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -398,6 +398,7 @@
pw.println(" mFaceAuthEnabled: " + mFaceAuthEnabled);
pw.println(" isKeyguardFadingAway: " + isKeyguardFadingAway());
pw.println(" isKeyguardGoingAway: " + isKeyguardGoingAway());
+ pw.println(" isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway());
}
private class UpdateMonitorCallback extends KeyguardUpdateMonitorCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 9952cfd..3805019 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -261,22 +261,26 @@
private fun trackAndLogUsiSession(deviceId: Int, batteryStateValid: Boolean) {
// TODO(b/268618918) handle cases where an invalid battery callback from a previous stylus
// is sent after the actual valid callback
+ val hasBtConnection = if (inputDeviceBtSessionIdMap.isEmpty()) 0 else 1
+
if (batteryStateValid && usiSessionId == null) {
logDebug { "USI battery newly present, entering new USI session: $deviceId" }
usiSessionId = instanceIdSequence.newInstanceId()
- uiEventLogger.logWithInstanceId(
+ uiEventLogger.logWithInstanceIdAndPosition(
StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
0,
null,
- usiSessionId
+ usiSessionId,
+ hasBtConnection,
)
} else if (!batteryStateValid && usiSessionId != null) {
logDebug { "USI battery newly absent, exiting USI session: $deviceId" }
- uiEventLogger.logWithInstanceId(
+ uiEventLogger.logWithInstanceIdAndPosition(
StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
0,
null,
- usiSessionId
+ usiSessionId,
+ hasBtConnection,
)
usiSessionId = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 125cc76..6e58f22 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,7 +18,8 @@
import android.os.VibrationEffect
import android.view.View
-import androidx.annotation.AttrRes
+import androidx.annotation.ColorRes
+import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.temporarydisplay.TemporaryViewInfo
@@ -48,7 +49,7 @@
override val priority: ViewPriority,
) : TemporaryViewInfo() {
companion object {
- @AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
+ @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 6ef828f..9d8c4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -87,6 +87,7 @@
private var isFolded: Boolean = false
private var isUnfoldHandled: Boolean = true
private var overlayAddReason: AddOverlayReason? = null
+ private var isTouchBlocked: Boolean = true
private var currentRotation: Int = context.display!!.rotation
@@ -254,7 +255,15 @@
params.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
params.fitInsetsTypes = 0
- params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+
+ val touchFlags =
+ if (isTouchBlocked) {
+ // Touchable by default, so it will block the touches
+ 0
+ } else {
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ }
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or touchFlags
params.setTrustedOverlay()
val packageName: String = context.opPackageName
@@ -263,6 +272,24 @@
return params
}
+ private fun updateTouchBlockIfNeeded(progress: Float) {
+ // When unfolding unblock touches a bit earlier than the animation end as the
+ // interpolation has a long tail of very slight movement at the end which should not
+ // affect much the usage of the device
+ val shouldBlockTouches =
+ if (overlayAddReason == UNFOLD) {
+ progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS
+ } else {
+ true
+ }
+
+ if (isTouchBlocked != shouldBlockTouches) {
+ isTouchBlocked = shouldBlockTouches
+
+ traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
+ }
+ }
+
private fun createLightRevealEffect(): LightRevealEffect {
val isVerticalFold =
currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180
@@ -289,7 +316,10 @@
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
- executeInBackground { scrimView?.revealAmount = calculateRevealAmount(progress) }
+ executeInBackground {
+ scrimView?.revealAmount = calculateRevealAmount(progress)
+ updateTouchBlockIfNeeded(progress)
+ }
}
override fun onTransitionFinished() {
@@ -361,5 +391,7 @@
// constants for revealAmount.
const val TRANSPARENT = 1f
const val BLACK = 0f
+
+ private const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
index 460b7d9..a5828c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java
@@ -23,6 +23,8 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import java.util.HashSet;
+
import javax.inject.Inject;
/**
@@ -37,6 +39,7 @@
private final ThresholdSensor[] mPostureToPrimaryProxSensorMap;
private final ThresholdSensor[] mPostureToSecondaryProxSensorMap;
+ private final HashSet<Listener> mListenersRegisteredWhenProxUnavailable = new HashSet<>();
private final DevicePostureController mDevicePostureController;
@Inject
@@ -69,6 +72,25 @@
mDevicePostureController.removeCallback(mDevicePostureCallback);
}
+ @Override
+ public void register(ThresholdSensor.Listener listener) {
+ if (!isLoaded()) {
+ logDebug("No prox sensor when registering listener=" + listener);
+ mListenersRegisteredWhenProxUnavailable.add(listener);
+ }
+
+ super.register(listener);
+ }
+
+ @Override
+ public void unregister(ThresholdSensor.Listener listener) {
+ if (mListenersRegisteredWhenProxUnavailable.remove(listener)) {
+ logDebug("Removing listener from mListenersRegisteredWhenProxUnavailable "
+ + listener);
+ }
+ super.unregister(listener);
+ }
+
private void chooseSensors() {
if (mDevicePosture >= mPostureToPrimaryProxSensorMap.length
|| mDevicePosture >= mPostureToSecondaryProxSensorMap.length) {
@@ -98,6 +120,14 @@
mInitializedListeners = false;
registerInternal();
+
+ final Listener[] listenersToReregister =
+ mListenersRegisteredWhenProxUnavailable.toArray(new Listener[0]);
+ mListenersRegisteredWhenProxUnavailable.clear();
+ for (Listener listener : listenersToReregister) {
+ logDebug("Re-register listener " + listener);
+ register(listener);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl
new file mode 100644
index 0000000..aa7ef57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl
@@ -0,0 +1,7 @@
+package com.android.systemui.wallet.controller;
+
+import android.service.quickaccesswallet.WalletCard;
+
+interface IWalletCardsUpdatedListener {
+ void registerNewWalletCards(in List<WalletCard> cards);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl
new file mode 100644
index 0000000..eebbdfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletContextualLocationsService.aidl
@@ -0,0 +1,9 @@
+package com.android.systemui.wallet.controller;
+
+import com.android.systemui.wallet.controller.IWalletCardsUpdatedListener;
+
+interface IWalletContextualLocationsService {
+ void addWalletCardsUpdatedListener(in IWalletCardsUpdatedListener listener);
+
+ void onWalletContextualLocationsStateUpdated(in List<String> storeLocations);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 2ef3511..ce2d15f 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:sharedUserId="android.uid.system"
- package="com.android.systemui" >
+ package="com.android.systemui.tests" >
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -64,7 +64,7 @@
</intent-filter>
</receiver>
- <activity android:name=".wmshell.BubblesTestActivity"
+ <activity android:name="com.android.systemui.wmshell.BubblesTestActivity"
android:allowEmbedded="true"
android:documentLaunchMode="always"
android:excludeFromRecents="true"
@@ -88,7 +88,7 @@
android:excludeFromRecents="true"
/>
- <activity android:name=".settings.brightness.BrightnessDialogTest$TestDialog"
+ <activity android:name="com.android.systemui.settings.brightness.BrightnessDialogTest$TestDialog"
android:exported="false"
android:excludeFromRecents="true"
/>
@@ -115,19 +115,19 @@
android:exported="false" />
<!-- started from UsbDeviceSettingsManager -->
- <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
+ <activity android:name="com.android.systemui.usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
android:exported="false"
android:theme="@style/Theme.SystemUI.Dialog.Alert"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true" />
- <activity android:name=".user.CreateUserActivityTest$CreateUserActivityTestable"
+ <activity android:name="com.android.systemui.user.CreateUserActivityTest$CreateUserActivityTestable"
android:exported="false"
android:theme="@style/Theme.SystemUI.Dialog.Alert"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true" />
- <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
+ <activity android:name="com.android.systemui.sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
android:exported="false"
android:theme="@style/Theme.SystemUI.Dialog.Alert"
android:finishOnCloseSystemDialogs="true"
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index 0369d5b..ec6c421 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -59,7 +59,7 @@
private static final String TAG = "AAA++VerifyTest";
- private static final Class[] BASE_CLS_WHITELIST = {
+ private static final Class[] BASE_CLS_TO_INCLUDE = {
SysuiTestCase.class,
SysuiBaseFragmentTest.class,
};
@@ -81,7 +81,7 @@
if (!isTestClass(cls)) continue;
boolean hasParent = false;
- for (Class<?> parent : BASE_CLS_WHITELIST) {
+ for (Class<?> parent : BASE_CLS_TO_INCLUDE) {
if (parent.isAssignableFrom(cls)) {
hasParent = true;
break;
@@ -131,13 +131,13 @@
// with the main process dependency graph because it will not exist
// at runtime and could lead to incorrect tests which assume
// the main SystemUI process. Therefore, exclude this package
- // from the base class whitelist.
+ // from the base class allowlist.
filter.add(s -> !s.startsWith("com.android.systemui.screenshot"));
return filter;
}
private String getClsStr() {
- return TextUtils.join(",", Arrays.asList(BASE_CLS_WHITELIST)
+ return TextUtils.join(",", Arrays.asList(BASE_CLS_TO_INCLUDE)
.stream().map(cls -> cls.getSimpleName()).toArray());
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index a5f90f8..b15ac39 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
@@ -365,6 +366,12 @@
assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility());
}
+ @Test
+ public void testGetClockAnimations_nullClock_returnsNull() {
+ when(mClockEventController.getClock()).thenReturn(null);
+ assertNull(mController.getClockAnimations());
+ }
+
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index f966eb3..b73330f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -196,6 +196,7 @@
.thenReturn(mKeyguardMessageAreaController);
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
SecurityMode.Password, mLockPatternUtils, null,
@@ -554,6 +555,22 @@
}
@Test
+ public void testSecurityCallbackFinish() {
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUserUnlocked(0)).thenReturn(true);
+ mKeyguardSecurityContainerController.finish(true, 0);
+ verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void testSecurityCallbackFinish_cannotDismissLockScreenAndNotStrongAuth() {
+ when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true);
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ mKeyguardSecurityContainerController.finish(false, 0);
+ verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
+ }
+
+ @Test
public void testOnStartingToHide() {
mKeyguardSecurityContainerController.onStartingToHide();
verify(mInputViewController).onStartingToHide();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 86ba30c..fb21db7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2256,6 +2256,26 @@
}
@Test
+ public void assistantVisible_requestActiveUnlock() {
+ // GIVEN active unlock requests from the assistant are allowed
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT)).thenReturn(true);
+
+ // GIVEN should trigger active unlock
+ keyguardIsVisible();
+ keyguardNotGoingAway();
+ statusBarShadeIsNotLocked();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // WHEN the assistant is visible
+ mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+ // THEN request unlock with keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
+ @Test
public void fingerprintFailure_requestActiveUnlock_dismissKeyguard()
throws RemoteException {
// GIVEN shouldTriggerActiveUnlock
@@ -2489,6 +2509,57 @@
}
@Test
+ public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ keyguardIsVisible();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on wakeup
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
+ .thenReturn(true);
+
+ // GIVEN an unfold should force dismiss the keyguard
+ when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(true);
+
+ // WHEN device posture changes to unfold
+ deviceInPostureStateOpened();
+ mTestableLooper.processAllMessages();
+
+ // THEN request unlock with a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
+
+ @Test
+ public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE
+ keyguardIsVisible();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on wakeup
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
+ .thenReturn(true);
+
+ // GIVEN an unfold should NOT force dismiss the keyguard
+ when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(false);
+
+ // WHEN device posture changes to unfold
+ deviceInPostureStateOpened();
+ mTestableLooper.processAllMessages();
+
+ // THEN request unlock WITHOUT a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(false));
+ }
+
+ @Test
public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() {
ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor =
ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
new file mode 100644
index 0000000..6ddba0b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.biometrics
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.ShadeExpansionStateManager
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
+
+ private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ private lateinit var detector: AuthDialogPanelInteractionDetector
+
+ @Mock private lateinit var action: Runnable
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Before
+ fun setUp() {
+ shadeExpansionStateManager = ShadeExpansionStateManager()
+ detector =
+ AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor)
+ }
+
+ @Test
+ fun testEnableDetector_shouldPostRunnable() {
+ detector.enable(action)
+ // simulate notification expand
+ shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+ verify(action, timeout(5000).times(1)).run()
+ }
+
+ @Test
+ fun testEnableDetector_shouldNotPostRunnable() {
+ var detector =
+ AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor)
+ detector.enable(action)
+ detector.disable()
+ shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+ verifyZeroInteractions(action)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 21191db..0ab675c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -62,6 +62,7 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,7 +99,6 @@
@JvmField @Rule var rule = MockitoJUnit.rule()
- @Mock lateinit var keyguardStateController: KeyguardStateController
@Mock lateinit var layoutInflater: LayoutInflater
@Mock lateinit var fingerprintManager: FingerprintManager
@Mock lateinit var windowManager: WindowManager
@@ -138,7 +138,8 @@
keyguardBouncerRepository = FakeKeyguardBouncerRepository()
alternateBouncerInteractor =
AlternateBouncerInteractor(
- keyguardStateController,
+ mock(StatusBarStateController::class.java),
+ mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
FakeBiometricSettingsRepository(),
FakeDeviceEntryFingerprintAuthRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 786cb01..cefa9b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -93,6 +94,7 @@
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
+ mock(StatusBarStateController::class.java),
mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
mock(BiometricSettingsRepository::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
index 5c09240..c2a129b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
@@ -49,7 +49,6 @@
private lateinit var udfpsShell: UdfpsShell
@Mock lateinit var commandRegistry: CommandRegistry
- @Mock lateinit var udfpsOverlay: UdfpsOverlay
@Mock lateinit var udfpsOverlayController: UdfpsOverlayController
@Captor private lateinit var motionEvent: ArgumentCaptor<MotionEvent>
@@ -60,7 +59,7 @@
fun setup() {
whenEver(udfpsOverlayController.sensorBounds).thenReturn(sensorBounds)
- udfpsShell = UdfpsShell(commandRegistry, udfpsOverlay)
+ udfpsShell = UdfpsShell(commandRegistry)
udfpsShell.udfpsOverlayController = udfpsOverlayController
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
index 9d16185..6fa1916 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
@@ -18,7 +18,11 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -29,7 +33,11 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -47,6 +55,12 @@
private static final String CURRENT_BROADCAST_APP = "Music";
private static final String SWITCH_APP = "System UI";
private static final String TEST_PACKAGE = "com.android.systemui";
+ private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
+ private final LocalBluetoothProfileManager mLocalBluetoothProfileManager = mock(
+ LocalBluetoothProfileManager.class);
+ private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock(
+ LocalBluetoothLeBroadcast.class);
+ private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private BroadcastDialog mBroadcastDialog;
private View mDialogView;
private TextView mTitle;
@@ -57,9 +71,11 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class),
- CURRENT_BROADCAST_APP, TEST_PACKAGE, mock(UiEventLogger.class));
-
+ mLocalBluetoothManager, CURRENT_BROADCAST_APP, TEST_PACKAGE,
+ mock(UiEventLogger.class), mBroadcastSender);
mBroadcastDialog.show();
mDialogView = mBroadcastDialog.mDialogView;
}
@@ -100,4 +116,61 @@
assertThat(mBroadcastDialog.isShowing()).isFalse();
}
+
+ @Test
+ public void onClick_withSwitchBroadcast_stopCurrentBroadcast() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast);
+ mSwitchBroadcastAppButton.performClick();
+
+ verify(mLocalBluetoothLeBroadcast).stopLatestBroadcast();
+ }
+
+ @Test
+ public void onClick_withSwitchBroadcast_stopCurrentBroadcastFailed() {
+ mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast);
+ mSwitchBroadcastAppButton.performClick();
+
+ assertThat(mSwitchBroadcastAppButton.getText().toString()).isEqualTo(
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_switch_app, SWITCH_APP));
+ }
+
+ @Test
+ public void handleLeBroadcastStopped_withBroadcastProfileNull_doRefreshButton() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+ mSwitchBroadcastAppButton = mDialogView.requireViewById(R.id.switch_broadcast);
+
+ mBroadcastDialog.handleLeBroadcastStopped();
+
+ assertThat(mSwitchBroadcastAppButton.getText().toString()).isEqualTo(
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_switch_app, SWITCH_APP));
+ }
+
+ @Test
+ public void handleLeBroadcastStopped_withBroadcastProfile_doStartBroadcast() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+
+ mBroadcastDialog.handleLeBroadcastStopped();
+
+ verify(mLocalBluetoothLeBroadcast).startBroadcast(eq(SWITCH_APP), any());
+ }
+
+ @Test
+ public void handleLeBroadcastMetadataChanged_withNotLaunchFlag_doNotDismissDialog() {
+
+ mBroadcastDialog.handleLeBroadcastMetadataChanged();
+
+ assertThat(mBroadcastDialog.isShowing()).isTrue();
+ }
+
+ @Test
+ public void handleLeBroadcastMetadataChanged_withLaunchFlag_dismissDialog() {
+
+ mBroadcastDialog.handleLeBroadcastStarted();
+ mBroadcastDialog.handleLeBroadcastMetadataChanged();
+
+ assertThat(mBroadcastDialog.isShowing()).isFalse();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 10757ae..5b3e518 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -201,6 +201,56 @@
}
@Test
+ fun testSingleAppHeaderIsNotClickable() {
+ mockLayoutInflater()
+ val packageName = "pkg"
+ `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
+ val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
+ val serviceInfo = setUpPanel(panel)
+
+ underTest.show(parent, {}, context)
+
+ val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+ verify(controlsListingController).addCallback(capture(captor))
+
+ captor.value.onServicesUpdated(listOf(serviceInfo))
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ val header: View = parent.requireViewById(R.id.controls_header)
+ assertThat(header.isClickable).isFalse()
+ assertThat(header.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun testMultipleAppHeaderIsClickable() {
+ mockLayoutInflater()
+ val packageName1 = "pkg"
+ val panel1 = SelectedItem.PanelItem("App name 1", ComponentName(packageName1, "cls"))
+ val serviceInfo1 = setUpPanel(panel1)
+
+ val packageName2 = "pkg"
+ val panel2 = SelectedItem.PanelItem("App name 2", ComponentName(packageName2, "cls"))
+ val serviceInfo2 = setUpPanel(panel2)
+
+ `when`(authorizedPanelsRepository.getAuthorizedPanels())
+ .thenReturn(setOf(packageName1, packageName2))
+
+ underTest.show(parent, {}, context)
+
+ val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+ verify(controlsListingController).addCallback(capture(captor))
+
+ captor.value.onServicesUpdated(listOf(serviceInfo1, serviceInfo2))
+ FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+ val header: View = parent.requireViewById(R.id.controls_header)
+ assertThat(header.isClickable).isTrue()
+ assertThat(header.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
fun testPanelControllerStartActivityWithCorrectArguments() {
mockLayoutInflater()
val packageName = "pkg"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index a636b7f..b87647e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -408,6 +408,17 @@
}
@Test
+ public void testPulsing_dozeSuspendTriggers_pulseDone_doesntCrash() {
+ mMachine.requestState(INITIALIZED);
+
+ mMachine.requestState(DOZE);
+ mMachine.requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
+ mMachine.requestState(DOZE_PULSING);
+ mMachine.requestState(DOZE_SUSPEND_TRIGGERS);
+ mMachine.requestState(DOZE_PULSE_DONE);
+ }
+
+ @Test
public void testSuppressingPulse_doesntCrash() {
mMachine.requestState(INITIALIZED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 0f25764..b88dbe6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -32,6 +32,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -53,17 +55,21 @@
@Mock
Complication mComplication;
+ @Mock
+ private FeatureFlags mFeatureFlags;
+
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+
+ when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(false);
}
@Test
public void testStateChange_overlayActive() {
- final DreamOverlayStateController stateController = new DreamOverlayStateController(
- mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addCallback(mCallback);
stateController.setOverlayActive(true);
mExecutor.runAllReady();
@@ -84,8 +90,7 @@
@Test
public void testCallback() {
- final DreamOverlayStateController stateController = new DreamOverlayStateController(
- mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addCallback(mCallback);
// Add complication and verify callback is notified.
@@ -110,8 +115,7 @@
@Test
public void testNotifyOnCallbackAdd() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addComplication(mComplication);
mExecutor.runAllReady();
@@ -124,8 +128,7 @@
@Test
public void testNotifyOnCallbackAddOverlayDisabled() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, false);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(false);
stateController.addComplication(mComplication);
mExecutor.runAllReady();
@@ -139,8 +142,7 @@
@Test
public void testComplicationFilteringWhenShouldShowComplications() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.setShouldShowComplications(true);
final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -179,8 +181,7 @@
@Test
public void testComplicationFilteringWhenShouldHideComplications() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.setShouldShowComplications(true);
final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -226,8 +227,7 @@
@Test
public void testComplicationWithNoTypeNotFiltered() {
final Complication complication = Mockito.mock(Complication.class);
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addComplication(complication);
mExecutor.runAllReady();
assertThat(stateController.getComplications(true).contains(complication))
@@ -236,8 +236,7 @@
@Test
public void testNotifyLowLightChanged() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addCallback(mCallback);
mExecutor.runAllReady();
@@ -252,8 +251,7 @@
@Test
public void testNotifyLowLightExit() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addCallback(mCallback);
mExecutor.runAllReady();
@@ -276,8 +274,7 @@
@Test
public void testNotifyEntryAnimationsFinishedChanged() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addCallback(mCallback);
mExecutor.runAllReady();
@@ -292,8 +289,7 @@
@Test
public void testNotifyDreamOverlayStatusBarVisibleChanged() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addCallback(mCallback);
mExecutor.runAllReady();
@@ -308,8 +304,7 @@
@Test
public void testNotifyHasAssistantAttentionChanged() {
- final DreamOverlayStateController stateController =
- new DreamOverlayStateController(mExecutor, true);
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
stateController.addCallback(mCallback);
mExecutor.runAllReady();
@@ -321,4 +316,50 @@
verify(mCallback, times(1)).onStateChanged();
assertThat(stateController.hasAssistantAttention()).isTrue();
}
+
+ @Test
+ public void testShouldShowComplicationsSetToFalse_stillShowsHomeControls_featureEnabled() {
+ when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true);
+
+ final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
+ stateController.setShouldShowComplications(true);
+
+ final Complication homeControlsComplication = Mockito.mock(Complication.class);
+ when(homeControlsComplication.getRequiredTypeAvailability())
+ .thenReturn(Complication.COMPLICATION_TYPE_HOME_CONTROLS);
+
+ stateController.addComplication(homeControlsComplication);
+
+ final DreamOverlayStateController.Callback callback =
+ Mockito.mock(DreamOverlayStateController.Callback.class);
+
+ stateController.setAvailableComplicationTypes(
+ Complication.COMPLICATION_TYPE_HOME_CONTROLS);
+ stateController.addCallback(callback);
+ mExecutor.runAllReady();
+
+ {
+ clearInvocations(callback);
+ stateController.setShouldShowComplications(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onAvailableComplicationTypesChanged();
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(homeControlsComplication)).isTrue();
+ }
+
+ {
+ clearInvocations(callback);
+ stateController.setShouldShowComplications(false);
+ mExecutor.runAllReady();
+
+ verify(callback).onAvailableComplicationTypesChanged();
+ final Collection<Complication> complications = stateController.getComplications();
+ assertThat(complications.contains(homeControlsComplication)).isTrue();
+ }
+ }
+
+ private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
+ return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
index 19347c7..58eb7d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -21,9 +21,11 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.DreamManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -50,6 +52,9 @@
@Mock
Condition.Callback mCallback;
+ @Mock
+ DreamManager mDreamManager;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -59,29 +64,39 @@
* Ensure a dreaming state immediately triggers the condition.
*/
@Test
- public void testInitialState() {
- final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
- when(mContext.registerReceiver(any(), any())).thenReturn(intent);
- final DreamCondition condition = new DreamCondition(mContext);
+ public void testInitialDreamingState() {
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
condition.addCallback(mCallback);
- condition.start();
verify(mCallback).onConditionChanged(eq(condition));
assertThat(condition.isConditionMet()).isTrue();
}
/**
+ * Ensure a non-dreaming state does not trigger the condition.
+ */
+ @Test
+ public void testInitialNonDreamingState() {
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
+ condition.addCallback(mCallback);
+
+ verify(mCallback, never()).onConditionChanged(eq(condition));
+ assertThat(condition.isConditionMet()).isFalse();
+ }
+
+ /**
* Ensure that changing dream state triggers condition.
*/
@Test
public void testChange() {
- final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
- when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent);
- final DreamCondition condition = new DreamCondition(mContext);
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ final DreamCondition condition = new DreamCondition(mContext, mDreamManager);
condition.addCallback(mCallback);
- condition.start();
+ verify(mContext).registerReceiver(receiverCaptor.capture(), any());
clearInvocations(mCallback);
receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED));
verify(mCallback).onConditionChanged(eq(condition));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
index de0e511..e287f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
@@ -20,12 +20,16 @@
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.never
@@ -41,13 +45,14 @@
@Mock lateinit var statusBarStateController: StatusBarStateController
@Mock lateinit var powerManager: PowerManager
val clock = FakeSystemClock()
+ val executor = FakeExecutor(clock)
lateinit var listener: StatusBarStateController.StateListener
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
restartDozeListener =
- RestartDozeListener(settings, statusBarStateController, powerManager, clock)
+ RestartDozeListener(settings, statusBarStateController, powerManager, clock, executor)
val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java)
restartDozeListener.init()
@@ -56,30 +61,37 @@
}
@Test
- fun testStoreDreamState_onDreamingStarted() {
- listener.onDreamingChanged(true)
- assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isTrue()
+ fun testStoreDreamState_onDozingStarted() {
+ listener.onDozingChanged(true)
+ executor.runAllReady()
+ assertThat(settings.getBool(RestartDozeListener.RESTART_SLEEP_KEY)).isTrue()
}
@Test
- fun testStoreDreamState_onDreamingStopped() {
- listener.onDreamingChanged(false)
- assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isFalse()
+ fun testStoreDozeState_onDozingStopped() {
+ listener.onDozingChanged(false)
+ executor.runAllReady()
+ assertThat(settings.getBool(RestartDozeListener.RESTART_SLEEP_KEY)).isFalse()
}
@Test
- fun testRestoreDreamState_dreamingShouldStart() {
- settings.putBool(RestartDozeListener.RESTART_NAP_KEY, true)
+ fun testRestoreDozeState_dozingShouldStart() {
+ settings.putBool(RestartDozeListener.RESTART_SLEEP_KEY, true)
restartDozeListener.maybeRestartSleep()
- verify(powerManager).wakeUp(clock.uptimeMillis())
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(powerManager)
+ .wakeUp(eq(clock.uptimeMillis()), eq(PowerManager.WAKE_REASON_APPLICATION), anyString())
verify(powerManager).goToSleep(clock.uptimeMillis())
}
@Test
- fun testRestoreDreamState_dreamingShouldNot() {
- settings.putBool(RestartDozeListener.RESTART_NAP_KEY, false)
+ fun testRestoreDozeState_dozingShouldNotStart() {
+ settings.putBool(RestartDozeListener.RESTART_SLEEP_KEY, false)
restartDozeListener.maybeRestartSleep()
- verify(powerManager, never()).wakeUp(anyLong())
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString())
verify(powerManager, never()).goToSleep(anyLong())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 1365132..86246f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -39,8 +40,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -52,6 +55,7 @@
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var deviceEntryFingerprintAuthRepository:
FakeDeviceEntryFingerprintAuthRepository
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -73,6 +77,7 @@
featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
underTest =
AlternateBouncerInteractor(
+ statusBarStateController,
keyguardStateController,
bouncerRepository,
biometricSettingsRepository,
@@ -130,6 +135,14 @@
}
@Test
+ fun canShowAlternateBouncerForFingerprint_isDozing() {
+ givenCanShowAlternateBouncer()
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+
+ assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+ }
+
+ @Test
fun show_whenCanShow() {
givenCanShowAlternateBouncer()
@@ -169,6 +182,42 @@
assertFalse(bouncerRepository.alternateBouncerVisible.value)
}
+ @Test
+ fun onUnlockedIsFalse_doesNotHide() {
+ // GIVEN alternate bouncer is showing
+ bouncerRepository.setAlternateVisible(true)
+
+ val keyguardStateControllerCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
+ verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
+
+ // WHEN isUnlocked=false
+ givenCanShowAlternateBouncer()
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
+
+ // THEN the alternate bouncer is still visible
+ assertTrue(bouncerRepository.alternateBouncerVisible.value)
+ }
+
+ @Test
+ fun onUnlockedChangedIsTrue_hide() {
+ // GIVEN alternate bouncer is showing
+ bouncerRepository.setAlternateVisible(true)
+
+ val keyguardStateControllerCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
+ verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
+
+ // WHEN isUnlocked=true
+ givenCanShowAlternateBouncer()
+ whenever(keyguardStateController.isUnlocked).thenReturn(true)
+ keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
+
+ // THEN the alternate bouncer is hidden
+ assertFalse(bouncerRepository.alternateBouncerVisible.value)
+ }
+
private fun givenCanShowAlternateBouncer() {
bouncerRepository.setAlternateBouncerUIAvailable(true)
biometricSettingsRepository.setFingerprintEnrolled(true)
@@ -176,6 +225,7 @@
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
deviceEntryFingerprintAuthRepository.setLockedOut(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ whenever(statusBarStateController.isDozing).thenReturn(false)
}
private fun givenCannotShowAlternateBouncer() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 5ec6283..bdc33f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -50,7 +51,6 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -90,9 +90,9 @@
keyguardUpdateMonitor,
keyguardBypassController,
)
- `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
- `when`(repository.primaryBouncerShow.value).thenReturn(false)
- `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+ whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.primaryBouncerShow.value).thenReturn(false)
+ whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
resources = context.orCreateTestableResources
}
@@ -118,7 +118,7 @@
@Test
fun testShow_keyguardIsDone() {
- `when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+ whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
}
@@ -135,7 +135,7 @@
@Test
fun testExpansion() {
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
underTest.setPanelExpansion(0.6f)
verify(repository).setPanelExpansion(0.6f)
verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
@@ -143,8 +143,8 @@
@Test
fun testExpansion_fullyShown() {
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
underTest.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
@@ -152,8 +152,8 @@
@Test
fun testExpansion_fullyHidden() {
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
- `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
underTest.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setPrimaryShow(false)
verify(falsingCollector).onBouncerHidden()
@@ -163,7 +163,7 @@
@Test
fun testExpansion_startingToHide() {
- `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
underTest.setPanelExpansion(0.1f)
verify(repository).setPrimaryStartingToHide(true)
verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
@@ -228,7 +228,21 @@
}
@Test
- fun testStartDisappearAnimation() {
+ fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
+ whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
+
+ val runnable = mock(Runnable::class.java)
+ underTest.startDisappearAnimation(runnable)
+ // End runnable should run immediately
+ verify(runnable).run()
+ // ... while the disappear animation should never be run
+ verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+ }
+
+ @Test
+ fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
+ whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
+
val runnable = mock(Runnable::class.java)
underTest.startDisappearAnimation(runnable)
verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
@@ -236,45 +250,45 @@
@Test
fun testIsFullShowing() {
- `when`(repository.primaryBouncerShow.value).thenReturn(true)
- `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.primaryBouncerShow.value).thenReturn(true)
+ whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
assertThat(underTest.isFullyShowing()).isTrue()
- `when`(repository.primaryBouncerShow.value).thenReturn(false)
+ whenever(repository.primaryBouncerShow.value).thenReturn(false)
assertThat(underTest.isFullyShowing()).isFalse()
}
@Test
fun testIsScrimmed() {
- `when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
+ whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
assertThat(underTest.isScrimmed()).isTrue()
- `when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
+ whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
assertThat(underTest.isScrimmed()).isFalse()
}
@Test
fun testIsInTransit() {
- `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+ whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
assertThat(underTest.isInTransit()).isTrue()
- `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+ whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
assertThat(underTest.isInTransit()).isFalse()
- `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
assertThat(underTest.isInTransit()).isTrue()
}
@Test
fun testIsAnimatingAway() {
- `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+ whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
assertThat(underTest.isAnimatingAway()).isTrue()
- `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
assertThat(underTest.isAnimatingAway()).isFalse()
}
@Test
fun testWillDismissWithAction() {
- `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
+ whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
assertThat(underTest.willDismissWithAction()).isTrue()
- `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
+ whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
assertThat(underTest.willDismissWithAction()).isFalse()
}
@@ -363,12 +377,13 @@
isUnlockingWithFpAllowed: Boolean,
isAnimatingAway: Boolean
) {
- `when`(repository.primaryBouncerShow.value).thenReturn(isVisible)
+ whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
- `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(fpsDetectionRunning)
- `when`(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+ whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+ .thenReturn(fpsDetectionRunning)
+ whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
.thenReturn(isUnlockingWithFpAllowed)
- `when`(repository.primaryBouncerStartingDisappearAnimation.value)
+ whenever(repository.primaryBouncerStartingDisappearAnimation.value)
.thenReturn(if (isAnimatingAway) Runnable {} else null)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 746f668..98794fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -115,7 +115,7 @@
repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) }
+ values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 55b57f1..543875d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -209,12 +209,6 @@
@Mock private lateinit var coverContainer3: ViewGroup
@Mock private lateinit var recAppIconItem: CachingIconView
@Mock private lateinit var recCardTitle: TextView
- @Mock private lateinit var recProgressBar1: SeekBar
- @Mock private lateinit var recProgressBar2: SeekBar
- @Mock private lateinit var recProgressBar3: SeekBar
- @Mock private lateinit var recSubtitleMock1: TextView
- @Mock private lateinit var recSubtitleMock2: TextView
- @Mock private lateinit var recSubtitleMock3: TextView
@Mock private lateinit var coverItem: ImageView
@Mock private lateinit var matrix: Matrix
private lateinit var coverItem1: ImageView
@@ -226,6 +220,9 @@
private lateinit var recSubtitle1: TextView
private lateinit var recSubtitle2: TextView
private lateinit var recSubtitle3: TextView
+ @Mock private lateinit var recProgressBar1: SeekBar
+ @Mock private lateinit var recProgressBar2: SeekBar
+ @Mock private lateinit var recProgressBar3: SeekBar
private var shouldShowBroadcastButton: Boolean = false
private val fakeFeatureFlag =
FakeFeatureFlags().apply {
@@ -636,10 +633,7 @@
@Test
fun bindAlbumView_setAfterExecutors() {
- val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val canvas = Canvas(bmp)
- canvas.drawColor(Color.RED)
- val albumArt = Icon.createWithBitmap(bmp)
+ val albumArt = getColorIcon(Color.RED)
val state = mediaData.copy(artwork = albumArt)
player.attachPlayer(viewHolder)
@@ -652,15 +646,8 @@
@Test
fun bindAlbumView_bitmapInLaterStates_setAfterExecutors() {
- val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val redCanvas = Canvas(redBmp)
- redCanvas.drawColor(Color.RED)
- val redArt = Icon.createWithBitmap(redBmp)
-
- val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val greenCanvas = Canvas(greenBmp)
- greenCanvas.drawColor(Color.GREEN)
- val greenArt = Icon.createWithBitmap(greenBmp)
+ val redArt = getColorIcon(Color.RED)
+ val greenArt = getColorIcon(Color.GREEN)
val state0 = mediaData.copy(artwork = null)
val state1 = mediaData.copy(artwork = redArt)
@@ -705,18 +692,12 @@
@Test
fun addTwoPlayerGradients_differentStates() {
// Setup redArtwork and its color scheme.
- val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val redCanvas = Canvas(redBmp)
- redCanvas.drawColor(Color.RED)
- val redArt = Icon.createWithBitmap(redBmp)
+ val redArt = getColorIcon(Color.RED)
val redWallpaperColor = player.getWallpaperColor(redArt)
val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
// Setup greenArt and its color scheme.
- val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val greenCanvas = Canvas(greenBmp)
- greenCanvas.drawColor(Color.GREEN)
- val greenArt = Icon.createWithBitmap(greenBmp)
+ val greenArt = getColorIcon(Color.GREEN)
val greenWallpaperColor = player.getWallpaperColor(greenArt)
val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
@@ -2040,12 +2021,12 @@
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("")
+ .setSubtitle("subtitle2")
.setIcon(icon)
.setExtras(Bundle.EMPTY)
.build(),
SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle3")
+ .setSubtitle("")
.setIcon(icon)
.setExtras(Bundle.EMPTY)
.build()
@@ -2125,26 +2106,18 @@
assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
}
@Test
fun bindRecommendation_setAfterExecutors() {
- fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
- whenever(recommendationViewHolder.mediaAppIcons)
- .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
- whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
- whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem, coverItem, coverItem))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
- whenever(coverItem.imageMatrix).thenReturn(matrix)
-
- val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val canvas = Canvas(bmp)
- canvas.drawColor(Color.RED)
- val albumArt = Icon.createWithBitmap(bmp)
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
recommendations =
@@ -2180,21 +2153,9 @@
@Test
fun bindRecommendationWithProgressBars() {
- fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
- whenever(recommendationViewHolder.mediaAppIcons)
- .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
- whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
- whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem, coverItem, coverItem))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
-
- val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val canvas = Canvas(bmp)
- canvas.drawColor(Color.RED)
- val albumArt = Icon.createWithBitmap(bmp)
+ useRealConstraintSets()
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
val bundle =
Bundle().apply {
putInt(
@@ -2232,26 +2193,61 @@
verify(recProgressBar1).visibility = View.VISIBLE
verify(recProgressBar2).visibility = View.GONE
verify(recProgressBar3).visibility = View.GONE
- verify(recSubtitleMock1).visibility = View.GONE
- verify(recSubtitleMock2).visibility = View.VISIBLE
- verify(recSubtitleMock3).visibility = View.VISIBLE
+ assertThat(recSubtitle1.visibility).isEqualTo(View.GONE)
+ assertThat(recSubtitle2.visibility).isEqualTo(View.VISIBLE)
+ assertThat(recSubtitle3.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun bindRecommendation_carouselNotFitThreeRecs() {
+ useRealConstraintSets()
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ // set the screen width less than the width of media controls.
+ player.context.resources.configuration.screenWidthDp = 350
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+
+ assertThat(player.numberOfFittedRecommendations).isEqualTo(2)
+ assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
}
@Test
fun addTwoRecommendationGradients_differentStates() {
// Setup redArtwork and its color scheme.
- val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val redCanvas = Canvas(redBmp)
- redCanvas.drawColor(Color.RED)
- val redArt = Icon.createWithBitmap(redBmp)
+ val redArt = getColorIcon(Color.RED)
val redWallpaperColor = player.getWallpaperColor(redArt)
val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
// Setup greenArt and its color scheme.
- val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
- val greenCanvas = Canvas(greenBmp)
- greenCanvas.drawColor(Color.GREEN)
- val greenArt = Icon.createWithBitmap(greenBmp)
+ val greenArt = getColorIcon(Color.GREEN)
val greenWallpaperColor = player.getWallpaperColor(greenArt)
val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
@@ -2392,6 +2388,34 @@
verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
}
+ private fun setupUpdatedRecommendationViewHolder() {
+ fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+ whenever(recommendationViewHolder.mediaCoverContainers)
+ .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
+ whenever(recommendationViewHolder.mediaCoverItems)
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(recommendationViewHolder.mediaSubtitles)
+ .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
+ whenever(coverItem.imageMatrix).thenReturn(matrix)
+
+ // set ids for recommendation containers
+ whenever(coverContainer1.id).thenReturn(1)
+ whenever(coverContainer2.id).thenReturn(2)
+ whenever(coverContainer3.id).thenReturn(3)
+ }
+
+ private fun getColorIcon(color: Int): Icon {
+ val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bmp)
+ canvas.drawColor(color)
+ return Icon.createWithBitmap(bmp)
+ }
+
private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
withArgCaptor {
verify(seekBarViewModel).setScrubbingChangeListener(capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 3d5dba3..e2cf87a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -92,7 +92,7 @@
verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
verify(mMockMediaOutputBroadcastDialogFactory, times(1))
- .create(getContext().getPackageName(), false, null);
+ .create(getContext().getPackageName(), true, null);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 85e8d07..6c3d6f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -140,6 +141,7 @@
context.getString(R.string.media_transfer_receiver_content_description_unknown_app)
)
assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
+ assertThat(iconInfo.tint).isEqualTo(DEFAULT_ICON_TINT)
}
@Test
@@ -232,40 +234,40 @@
fun iconInfo_toTintedIcon_loaded() {
val contentDescription = ContentDescription.Loaded("test")
val drawable = context.getDrawable(R.drawable.ic_cake)!!
- val tintAttr = android.R.attr.textColorTertiary
+ val tint = R.color.GM2_blue_500
val iconInfo =
IconInfo(
contentDescription,
MediaTttIcon.Loaded(drawable),
- tintAttr,
+ tint,
isAppIcon = false,
)
val tinted = iconInfo.toTintedIcon()
assertThat(tinted.icon).isEqualTo(Icon.Loaded(drawable, contentDescription))
- assertThat(tinted.tintAttr).isEqualTo(tintAttr)
+ assertThat(tinted.tint).isEqualTo(tint)
}
@Test
fun iconInfo_toTintedIcon_resource() {
val contentDescription = ContentDescription.Loaded("test")
val drawableRes = R.drawable.ic_cake
- val tintAttr = android.R.attr.textColorTertiary
+ val tint = R.color.GM2_blue_500
val iconInfo =
IconInfo(
contentDescription,
MediaTttIcon.Resource(drawableRes),
- tintAttr,
+ tint,
isAppIcon = false
)
val tinted = iconInfo.toTintedIcon()
assertThat(tinted.icon).isEqualTo(Icon.Resource(drawableRes, contentDescription))
- assertThat(tinted.tintAttr).isEqualTo(tintAttr)
+ assertThat(tinted.tint).isEqualTo(tint)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index 464acb6..01ffdcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -19,12 +19,14 @@
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
+import android.graphics.Insets
import android.graphics.Rect
import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowMetrics
+import androidx.core.view.WindowInsetsCompat.Type
import androidx.test.filters.SmallTest
-import com.android.internal.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
import com.android.systemui.statusbar.policy.FakeConfigurationController
@@ -94,7 +96,13 @@
}
private fun givenTaskbarSize(size: Int) {
- whenever(resources.getDimensionPixelSize(eq(R.dimen.taskbar_frame_height))).thenReturn(size)
+ val windowInsets =
+ WindowInsets.Builder()
+ .setInsets(Type.tappableElement(), Insets.of(Rect(0, 0, 0, size)))
+ .build()
+ val windowMetrics = WindowMetrics(windowManager.maximumWindowMetrics.bounds, windowInsets)
+ whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
+ whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
}
private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
index 415e68f..bcc99bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
@@ -73,6 +73,28 @@
}
@Test
+ fun isAnyShadeExpanded() =
+ testScope.runTest {
+ val underTest = create()
+ val isAnyShadeExpanded: Boolean? by collectLastValue(underTest.isAnyShadeExpanded)
+ assertWithMessage("isAnyShadeExpanded must start with false!")
+ .that(isAnyShadeExpanded)
+ .isFalse()
+
+ underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
+ assertThat(isAnyShadeExpanded).isTrue()
+
+ underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
+ assertThat(isAnyShadeExpanded).isTrue()
+
+ underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
+ assertThat(isAnyShadeExpanded).isTrue()
+
+ underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
+ assertThat(isAnyShadeExpanded).isFalse()
+ }
+
+ @Test
fun isVisible_dualShadeConfig() =
testScope.runTest {
overrideResource(R.bool.dual_shade_enabled, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
new file mode 100644
index 0000000..f807146c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2023 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.multishade.domain.interactor
+
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.shared.model.ProxiedInputModel
+import com.android.systemui.multishade.shared.model.ShadeId
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: MultiShadeMotionEventInteractor
+
+ private lateinit var testScope: TestScope
+ private lateinit var motionEvents: MutableSet<MotionEvent>
+ private lateinit var repository: MultiShadeRepository
+ private lateinit var interactor: MultiShadeInteractor
+ private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
+
+ @Before
+ fun setUp() {
+ testScope = TestScope()
+ motionEvents = mutableSetOf()
+
+ val inputProxy = MultiShadeInputProxy()
+ repository =
+ MultiShadeRepository(
+ applicationContext = context,
+ inputProxy = inputProxy,
+ )
+ interactor =
+ MultiShadeInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository = repository,
+ inputProxy = inputProxy,
+ )
+ underTest =
+ MultiShadeMotionEventInteractor(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ interactor = interactor,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ motionEvents.forEach { motionEvent -> motionEvent.recycle() }
+ }
+
+ @Test
+ fun shouldIntercept_initialDown_returnsFalse() =
+ testScope.runTest {
+ assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))).isFalse()
+ }
+
+ @Test
+ fun shouldIntercept_moveBelowTouchSlop_returnsFalse() =
+ testScope.runTest {
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+
+ assertThat(
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ y = touchSlop - 1f,
+ )
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun shouldIntercept_moveAboveTouchSlop_returnsTrue() =
+ testScope.runTest {
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+
+ assertThat(
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ y = touchSlop + 1f,
+ )
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun shouldIntercept_moveAboveTouchSlop_butHorizontalFirst_returnsFalse() =
+ testScope.runTest {
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+
+ assertThat(
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ x = touchSlop + 1f,
+ )
+ )
+ )
+ .isFalse()
+ assertThat(
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ y = touchSlop + 1f,
+ )
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun shouldIntercept_up_afterMovedAboveTouchSlop_returnsTrue() =
+ testScope.runTest {
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f))
+
+ assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isTrue()
+ }
+
+ @Test
+ fun shouldIntercept_cancel_afterMovedAboveTouchSlop_returnsTrue() =
+ testScope.runTest {
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f))
+
+ assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isTrue()
+ }
+
+ @Test
+ fun shouldIntercept_moveAboveTouchSlopAndUp_butShadeExpanded_returnsFalse() =
+ testScope.runTest {
+ repository.setExpansion(ShadeId.LEFT, 0.1f)
+ runCurrent()
+
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+
+ assertThat(
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ y = touchSlop + 1f,
+ )
+ )
+ )
+ .isFalse()
+ assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isFalse()
+ }
+
+ @Test
+ fun shouldIntercept_moveAboveTouchSlopAndCancel_butShadeExpanded_returnsFalse() =
+ testScope.runTest {
+ repository.setExpansion(ShadeId.LEFT, 0.1f)
+ runCurrent()
+
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+
+ assertThat(
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ y = touchSlop + 1f,
+ )
+ )
+ )
+ .isFalse()
+ assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse()
+ }
+
+ @Test
+ fun tap_doesNotSendProxiedInput() =
+ testScope.runTest {
+ val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
+ val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
+ val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
+
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))
+
+ assertThat(leftShadeProxiedInput).isNull()
+ assertThat(rightShadeProxiedInput).isNull()
+ assertThat(singleShadeProxiedInput).isNull()
+ }
+
+ @Test
+ fun dragBelowTouchSlop_doesNotSendProxiedInput() =
+ testScope.runTest {
+ val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
+ val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
+ val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
+
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop - 1f))
+ underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))
+
+ assertThat(leftShadeProxiedInput).isNull()
+ assertThat(rightShadeProxiedInput).isNull()
+ assertThat(singleShadeProxiedInput).isNull()
+ }
+
+ @Test
+ fun dragAboveTouchSlopAndUp() =
+ testScope.runTest {
+ val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
+ val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
+ val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
+
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_DOWN,
+ x = 100f, // left shade
+ )
+ )
+ assertThat(leftShadeProxiedInput).isNull()
+ assertThat(rightShadeProxiedInput).isNull()
+ assertThat(singleShadeProxiedInput).isNull()
+
+ val yDragAmountPx = touchSlop + 1f
+ val moveEvent =
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ x = 100f, // left shade
+ y = yDragAmountPx,
+ )
+ assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
+ underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
+ assertThat(leftShadeProxiedInput)
+ .isEqualTo(
+ ProxiedInputModel.OnDrag(
+ xFraction = 0.1f,
+ yDragAmountPx = yDragAmountPx,
+ )
+ )
+ assertThat(rightShadeProxiedInput).isNull()
+ assertThat(singleShadeProxiedInput).isNull()
+
+ val upEvent = motionEvent(MotionEvent.ACTION_UP)
+ assertThat(underTest.shouldIntercept(upEvent)).isTrue()
+ underTest.onTouchEvent(upEvent, viewWidthPx = 1000)
+ assertThat(leftShadeProxiedInput).isNull()
+ assertThat(rightShadeProxiedInput).isNull()
+ assertThat(singleShadeProxiedInput).isNull()
+ }
+
+ @Test
+ fun dragAboveTouchSlopAndCancel() =
+ testScope.runTest {
+ val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
+ val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
+ val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
+
+ underTest.shouldIntercept(
+ motionEvent(
+ MotionEvent.ACTION_DOWN,
+ x = 900f, // right shade
+ )
+ )
+ assertThat(leftShadeProxiedInput).isNull()
+ assertThat(rightShadeProxiedInput).isNull()
+ assertThat(singleShadeProxiedInput).isNull()
+
+ val yDragAmountPx = touchSlop + 1f
+ val moveEvent =
+ motionEvent(
+ MotionEvent.ACTION_MOVE,
+ x = 900f, // right shade
+ y = yDragAmountPx,
+ )
+ assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
+ underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
+ assertThat(leftShadeProxiedInput).isNull()
+ assertThat(rightShadeProxiedInput)
+ .isEqualTo(
+ ProxiedInputModel.OnDrag(
+ xFraction = 0.9f,
+ yDragAmountPx = yDragAmountPx,
+ )
+ )
+ assertThat(singleShadeProxiedInput).isNull()
+
+ val cancelEvent = motionEvent(MotionEvent.ACTION_CANCEL)
+ assertThat(underTest.shouldIntercept(cancelEvent)).isTrue()
+ underTest.onTouchEvent(cancelEvent, viewWidthPx = 1000)
+ assertThat(leftShadeProxiedInput).isNull()
+ assertThat(rightShadeProxiedInput).isNull()
+ assertThat(singleShadeProxiedInput).isNull()
+ }
+
+ private fun TestScope.motionEvent(
+ action: Int,
+ downTime: Long = currentTime,
+ eventTime: Long = currentTime,
+ x: Float = 0f,
+ y: Float = 0f,
+ ): MotionEvent {
+ val motionEvent = MotionEvent.obtain(downTime, eventTime, action, x, y, 0)
+ motionEvents.add(motionEvent)
+ return motionEvent
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt
new file mode 100644
index 0000000..8935309
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.multishade.shared.math
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class MathTest : SysuiTestCase() {
+
+ @Test
+ fun isZero_zero_true() {
+ assertThat(0f.isZero(epsilon = EPSILON)).isTrue()
+ }
+
+ @Test
+ fun isZero_belowPositiveEpsilon_true() {
+ assertThat((EPSILON * 0.999999f).isZero(epsilon = EPSILON)).isTrue()
+ }
+
+ @Test
+ fun isZero_aboveNegativeEpsilon_true() {
+ assertThat((EPSILON * -0.999999f).isZero(epsilon = EPSILON)).isTrue()
+ }
+
+ @Test
+ fun isZero_positiveEpsilon_false() {
+ assertThat(EPSILON.isZero(epsilon = EPSILON)).isFalse()
+ }
+
+ @Test
+ fun isZero_negativeEpsilon_false() {
+ assertThat((-EPSILON).isZero(epsilon = EPSILON)).isFalse()
+ }
+
+ @Test
+ fun isZero_positive_false() {
+ assertThat(1f.isZero(epsilon = EPSILON)).isFalse()
+ }
+
+ @Test
+ fun isZero_negative_false() {
+ assertThat((-1f).isZero(epsilon = EPSILON)).isFalse()
+ }
+
+ companion object {
+ private const val EPSILON = 0.0001f
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 0a8cd26..e640946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -13,10 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(InternalNoteTaskApi::class)
+
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
+import android.app.role.RoleManager
+import android.app.role.RoleManager.ROLE_NOTES
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -24,12 +28,20 @@
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+import android.content.pm.ShortcutInfo
+import android.content.pm.ShortcutManager
import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
+import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
@@ -46,6 +58,8 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@@ -62,15 +76,17 @@
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var userManager: UserManager
@Mock lateinit var eventLogger: NoteTaskEventLogger
+ @Mock lateinit var roleManager: RoleManager
+ @Mock lateinit var shortcutManager: ShortcutManager
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
private val userTracker: UserTracker = FakeUserTracker()
-
private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(context.getString(R.string.note_task_button_label)).thenReturn(NOTES_SHORT_LABEL)
whenever(context.packageManager).thenReturn(packageManager)
whenever(resolver.resolveInfo(any(), any())).thenReturn(noteTaskInfo)
whenever(userManager.isUserUnlocked).thenReturn(true)
@@ -81,6 +97,8 @@
)
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE)
+ whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
+ .thenReturn(listOf(NOTES_PACKAGE_NAME))
}
private fun createNoteTaskController(
@@ -97,6 +115,8 @@
isEnabled = isEnabled,
devicePolicyManager = devicePolicyManager,
userTracker = userTracker,
+ roleManager = roleManager,
+ shortcutManager = shortcutManager,
)
// region onBubbleExpandChanged
@@ -131,7 +151,7 @@
}
@Test
- fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() {
+ fun onBubbleExpandChanged_expandingAndKeyguardLocked_shouldDoNothing() {
val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true)
createNoteTaskController()
@@ -145,7 +165,7 @@
}
@Test
- fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() {
+ fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_shouldDoNothing() {
val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true)
createNoteTaskController()
@@ -267,7 +287,8 @@
verifyZeroInteractions(context)
val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
+ verify(bubbles)
+ .showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), isNull())
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -331,11 +352,11 @@
verify(context.packageManager)
.setComponentEnabledSetting(
argument.capture(),
- eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ eq(COMPONENT_ENABLED_STATE_ENABLED),
eq(PackageManager.DONT_KILL_APP),
)
- val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
- assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+ assertThat(argument.value.className)
+ .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
}
@Test
@@ -346,11 +367,11 @@
verify(context.packageManager)
.setComponentEnabledSetting(
argument.capture(),
- eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(COMPONENT_ENABLED_STATE_DISABLED),
eq(PackageManager.DONT_KILL_APP),
)
- val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
- assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+ assertThat(argument.value.className)
+ .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
}
// endregion
@@ -401,7 +422,8 @@
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
+ verify(bubbles)
+ .showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), isNull())
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -424,7 +446,8 @@
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
- verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle))
+ verify(bubbles)
+ .showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), isNull())
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -434,7 +457,78 @@
}
// endregion
+ // region updateNoteTaskAsUser
+ @Test
+ fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
+ createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+
+ val actualComponent = argumentCaptor<ComponentName>()
+ verify(context.packageManager)
+ .setComponentEnabledSetting(
+ actualComponent.capture(),
+ eq(COMPONENT_ENABLED_STATE_ENABLED),
+ eq(PackageManager.DONT_KILL_APP),
+ )
+ assertThat(actualComponent.value.className)
+ .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
+ verify(shortcutManager, never()).disableShortcuts(any())
+ verify(shortcutManager).enableShortcuts(listOf(SHORTCUT_ID))
+ val actualShortcuts = argumentCaptor<List<ShortcutInfo>>()
+ verify(shortcutManager).updateShortcuts(actualShortcuts.capture())
+ val actualShortcut = actualShortcuts.value.first()
+ assertThat(actualShortcut.id).isEqualTo(SHORTCUT_ID)
+ assertThat(actualShortcut.intent?.component?.className)
+ .isEqualTo(LaunchNoteTaskActivity::class.java.name)
+ assertThat(actualShortcut.intent?.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+ assertThat(actualShortcut.shortLabel).isEqualTo(NOTES_SHORT_LABEL)
+ assertThat(actualShortcut.isLongLived).isEqualTo(true)
+ assertThat(actualShortcut.icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+ assertThat(actualShortcut.extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
+ .isEqualTo(NOTES_PACKAGE_NAME)
+ }
+
+ @Test
+ fun updateNoteTaskAsUser_noNotesRole_shouldDisableShortcuts() {
+ whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
+ .thenReturn(emptyList())
+
+ createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+
+ val argument = argumentCaptor<ComponentName>()
+ verify(context.packageManager)
+ .setComponentEnabledSetting(
+ argument.capture(),
+ eq(COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP),
+ )
+ assertThat(argument.value.className)
+ .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
+ verify(shortcutManager).disableShortcuts(listOf(SHORTCUT_ID))
+ verify(shortcutManager, never()).enableShortcuts(any())
+ verify(shortcutManager, never()).updateShortcuts(any())
+ }
+
+ @Test
+ fun updateNoteTaskAsUser_flagDisabled_shouldDisableShortcuts() {
+ createNoteTaskController(isEnabled = false).updateNoteTaskAsUser(userTracker.userHandle)
+
+ val argument = argumentCaptor<ComponentName>()
+ verify(context.packageManager)
+ .setComponentEnabledSetting(
+ argument.capture(),
+ eq(COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP),
+ )
+ assertThat(argument.value.className)
+ .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
+ verify(shortcutManager).disableShortcuts(listOf(SHORTCUT_ID))
+ verify(shortcutManager, never()).enableShortcuts(any())
+ verify(shortcutManager, never()).updateShortcuts(any())
+ }
+ // endregion
+
private companion object {
+ const val NOTES_SHORT_LABEL = "Notetaking"
const val NOTES_PACKAGE_NAME = "com.android.note.app"
const val NOTES_UID = 123456
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 46e0278..cd67e8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -15,36 +15,37 @@
*/
package com.android.systemui.notetask
+import android.app.role.RoleManager
import android.test.suitebuilder.annotation.SmallTest
import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
-/**
- * Tests for [NoteTaskController].
- *
- * Build/Install/Run:
- * - atest SystemUITests:NoteTaskInitializerTest
- */
+/** atest SystemUITests:NoteTaskInitializerTest */
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskInitializerTest : SysuiTestCase() {
@Mock lateinit var commandQueue: CommandQueue
@Mock lateinit var bubbles: Bubbles
- @Mock lateinit var noteTaskController: NoteTaskController
+ @Mock lateinit var controller: NoteTaskController
+ @Mock lateinit var roleManager: RoleManager
+ private val clock = FakeSystemClock()
+ private val executor = FakeExecutor(clock)
@Before
fun setUp() {
@@ -56,47 +57,41 @@
bubbles: Bubbles? = this.bubbles,
): NoteTaskInitializer {
return NoteTaskInitializer(
- controller = noteTaskController,
+ controller = controller,
commandQueue = commandQueue,
optionalBubbles = Optional.ofNullable(bubbles),
isEnabled = isEnabled,
+ roleManager = roleManager,
+ backgroundExecutor = executor,
)
}
// region initializer
@Test
- fun initialize_shouldAddCallbacks() {
+ fun initialize() {
createNoteTaskInitializer().initialize()
+ verify(controller).setNoteTaskShortcutEnabled(true)
verify(commandQueue).addCallback(any())
+ verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
}
@Test
- fun initialize_flagDisabled_shouldDoNothing() {
+ fun initialize_flagDisabled() {
createNoteTaskInitializer(isEnabled = false).initialize()
+ verify(controller, never()).setNoteTaskShortcutEnabled(any())
verify(commandQueue, never()).addCallback(any())
+ verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
}
@Test
- fun initialize_bubblesNotPresent_shouldDoNothing() {
+ fun initialize_bubblesNotPresent() {
createNoteTaskInitializer(bubbles = null).initialize()
+ verify(controller, never()).setNoteTaskShortcutEnabled(any())
verify(commandQueue, never()).addCallback(any())
- }
-
- @Test
- fun initialize_flagEnabled_shouldEnableShortcut() {
- createNoteTaskInitializer().initialize()
-
- verify(noteTaskController).setNoteTaskShortcutEnabled(true)
- }
-
- @Test
- fun initialize_flagDisabled_shouldDisableShortcut() {
- createNoteTaskInitializer(isEnabled = false).initialize()
-
- verify(noteTaskController).setNoteTaskShortcutEnabled(false)
+ verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
}
// endregion
@@ -105,14 +100,14 @@
fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)
- verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
+ verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
}
@Test
fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
- verifyZeroInteractions(noteTaskController)
+ verifyZeroInteractions(controller)
}
// endregion
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 75fd000..2e77de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -1,6 +1,6 @@
package com.android.systemui.qs.tiles
-import android.content.Context
+import android.bluetooth.BluetoothDevice
import android.os.Handler
import android.os.Looper
import android.os.UserManager
@@ -10,6 +10,8 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.settingslib.Utils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
@@ -21,14 +23,18 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BluetoothController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -36,21 +42,13 @@
@SmallTest
class BluetoothTileTest : SysuiTestCase() {
- @Mock
- private lateinit var mockContext: Context
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var qsHost: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
private val falsingManager = FalsingManagerFake()
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var bluetoothController: BluetoothController
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var bluetoothController: BluetoothController
private val uiEventLogger = UiEventLoggerFake()
private lateinit var testableLooper: TestableLooper
@@ -61,20 +59,21 @@
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
- Mockito.`when`(qsHost.context).thenReturn(mockContext)
- Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+ whenever(qsHost.context).thenReturn(mContext)
+ whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
- tile = FakeBluetoothTile(
- qsHost,
- testableLooper.looper,
- Handler(testableLooper.looper),
- falsingManager,
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger,
- bluetoothController
- )
+ tile =
+ FakeBluetoothTile(
+ qsHost,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ bluetoothController,
+ )
tile.initialize()
testableLooper.processAllMessages()
@@ -102,7 +101,7 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
}
@Test
@@ -114,7 +113,7 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
}
@Test
@@ -126,7 +125,7 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on))
}
@Test
@@ -138,7 +137,76 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search))
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search))
+ }
+
+ @Test
+ fun testSecondaryLabel_whenBatteryMetadataAvailable_isMetadataBatteryLevelState() {
+ val cachedDevice = mock<CachedBluetoothDevice>()
+ val state = QSTile.BooleanState()
+ listenToDeviceMetadata(state, cachedDevice, 50)
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.secondaryLabel)
+ .isEqualTo(
+ mContext.getString(
+ R.string.quick_settings_bluetooth_secondary_label_battery_level,
+ Utils.formatPercentage(50)
+ )
+ )
+ verify(bluetoothController)
+ .addOnMetadataChangedListener(eq(cachedDevice), any(), any())
+ }
+
+ @Test
+ fun testSecondaryLabel_whenBatteryMetadataUnavailable_isBluetoothBatteryLevelState() {
+ val state = QSTile.BooleanState()
+ val cachedDevice = mock<CachedBluetoothDevice>()
+ listenToDeviceMetadata(state, cachedDevice, 50)
+ val cachedDevice2 = mock<CachedBluetoothDevice>()
+ val btDevice = mock<BluetoothDevice>()
+ whenever(cachedDevice2.device).thenReturn(btDevice)
+ whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(null)
+ whenever(cachedDevice2.batteryLevel).thenReturn(25)
+ addConnectedDevice(cachedDevice2)
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.secondaryLabel)
+ .isEqualTo(
+ mContext.getString(
+ R.string.quick_settings_bluetooth_secondary_label_battery_level,
+ Utils.formatPercentage(25)
+ )
+ )
+ verify(bluetoothController, times(1))
+ .removeOnMetadataChangedListener(eq(cachedDevice), any())
+ }
+
+ @Test
+ fun testMetadataListener_whenDisconnected_isUnregistered() {
+ val state = QSTile.BooleanState()
+ val cachedDevice = mock<CachedBluetoothDevice>()
+ listenToDeviceMetadata(state, cachedDevice, 50)
+ disableBluetooth()
+
+ tile.handleUpdateState(state, null)
+
+ verify(bluetoothController, times(1))
+ .removeOnMetadataChangedListener(eq(cachedDevice), any())
+ }
+
+ @Test
+ fun testMetadataListener_whenTileNotListening_isUnregistered() {
+ val state = QSTile.BooleanState()
+ val cachedDevice = mock<CachedBluetoothDevice>()
+ listenToDeviceMetadata(state, cachedDevice, 50)
+
+ tile.handleSetListening(false)
+
+ verify(bluetoothController, times(1))
+ .removeOnMetadataChangedListener(eq(cachedDevice), any())
}
private class FakeBluetoothTile(
@@ -150,18 +218,19 @@
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
qsLogger: QSLogger,
- bluetoothController: BluetoothController
- ) : BluetoothTile(
- qsHost,
- backgroundLooper,
- mainHandler,
- falsingManager,
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger,
- bluetoothController
- ) {
+ bluetoothController: BluetoothController,
+ ) :
+ BluetoothTile(
+ qsHost,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ bluetoothController,
+ ) {
var restrictionChecked: String? = null
override fun checkIfRestrictionEnforcedByAdminOnly(
@@ -173,25 +242,44 @@
}
fun enableBluetooth() {
- `when`(bluetoothController.isBluetoothEnabled).thenReturn(true)
+ whenever(bluetoothController.isBluetoothEnabled).thenReturn(true)
}
fun disableBluetooth() {
- `when`(bluetoothController.isBluetoothEnabled).thenReturn(false)
+ whenever(bluetoothController.isBluetoothEnabled).thenReturn(false)
}
fun setBluetoothDisconnected() {
- `when`(bluetoothController.isBluetoothConnecting).thenReturn(false)
- `when`(bluetoothController.isBluetoothConnected).thenReturn(false)
+ whenever(bluetoothController.isBluetoothConnecting).thenReturn(false)
+ whenever(bluetoothController.isBluetoothConnected).thenReturn(false)
}
fun setBluetoothConnected() {
- `when`(bluetoothController.isBluetoothConnecting).thenReturn(false)
- `when`(bluetoothController.isBluetoothConnected).thenReturn(true)
+ whenever(bluetoothController.isBluetoothConnecting).thenReturn(false)
+ whenever(bluetoothController.isBluetoothConnected).thenReturn(true)
}
fun setBluetoothConnecting() {
- `when`(bluetoothController.isBluetoothConnected).thenReturn(false)
- `when`(bluetoothController.isBluetoothConnecting).thenReturn(true)
+ whenever(bluetoothController.isBluetoothConnected).thenReturn(false)
+ whenever(bluetoothController.isBluetoothConnecting).thenReturn(true)
+ }
+
+ fun addConnectedDevice(device: CachedBluetoothDevice) {
+ whenever(bluetoothController.connectedDevices).thenReturn(listOf(device))
+ }
+
+ fun listenToDeviceMetadata(
+ state: QSTile.BooleanState,
+ cachedDevice: CachedBluetoothDevice,
+ batteryLevel: Int
+ ) {
+ val btDevice = mock<BluetoothDevice>()
+ whenever(cachedDevice.device).thenReturn(btDevice)
+ whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY))
+ .thenReturn(batteryLevel.toString().toByteArray())
+ enableBluetooth()
+ setBluetoothConnected()
+ addConnectedDevice(cachedDevice)
+ tile.handleUpdateState(state, /* arg= */ null)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
index 1f18d91..08b5d2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
@@ -19,12 +19,14 @@
import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.IWindowManager
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
@@ -32,6 +34,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatcher
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -158,4 +161,56 @@
assertEquals(appName2, list[1])
assertEquals(appName3, list[2])
}
+
+ private fun includesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
+ ComponentInfoFlagMatcher(mask, mask)
+ private fun excludesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) =
+ ComponentInfoFlagMatcher(mask, 0)
+
+ private class ComponentInfoFlagMatcher(
+ @PackageManager.ComponentInfoFlagsBits val mask: Int, val value: Int
+ ): ArgumentMatcher<PackageManager.ComponentInfoFlags> {
+ override fun matches(flags: PackageManager.ComponentInfoFlags): Boolean {
+ return (mask.toLong() and flags.value) == value.toLong()
+ }
+
+ override fun toString(): String{
+ return "mask 0x%08x == 0x%08x".format(mask, value)
+ }
+ }
+
+ @Test
+ fun testMaybeNotifyOfScreenshot_disabledApp() {
+ val data = ScreenshotData.forTesting()
+ data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+
+ val component = ComponentName("package1", "class1")
+ val appName = "app name"
+ val activityInfo = mock(ActivityInfo::class.java)
+
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component),
+ argThat(includesFlagBits(MATCH_DISABLED_COMPONENTS))
+ )
+ ).thenReturn(activityInfo);
+
+ whenever(
+ packageManager.getActivityInfo(
+ eq(component),
+ argThat(excludesFlagBits(MATCH_DISABLED_COMPONENTS))
+ )
+ ).thenThrow(PackageManager.NameNotFoundException::class.java);
+
+ whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY)))
+ .thenReturn(listOf(component))
+
+ whenever(activityInfo.loadLabel(eq(packageManager))).thenReturn(appName)
+
+ val list = controller.maybeNotifyOfScreenshot(data)
+
+ assertEquals(1, list.size)
+ assertEquals(appName, list[0])
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index 3440f91..31f7771 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -45,7 +45,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -58,8 +57,9 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class WorkProfileMessageControllerTest extends SysuiTestCase {
- private static final String DEFAULT_LABEL = "default label";
- private static final String APP_LABEL = "app label";
+ private static final String FILES_APP_COMPONENT = "com.android.test/.FilesComponent";
+ private static final String FILES_APP_LABEL = "Custom Files App";
+ private static final String DEFAULT_FILES_APP_LABEL = "Files";
private static final UserHandle NON_WORK_USER = UserHandle.of(0);
private static final UserHandle WORK_USER = UserHandle.of(10);
@@ -88,14 +88,21 @@
when(mMockContext.getSharedPreferences(
eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
- when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
- when(mPackageManager.getActivityIcon(any(ComponentName.class)))
+ when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ .thenReturn(FILES_APP_COMPONENT);
+ when(mMockContext.getString(R.string.screenshot_default_files_app_name))
+ .thenReturn(DEFAULT_FILES_APP_LABEL);
+ when(mPackageManager.getActivityIcon(
+ eq(ComponentName.unflattenFromString(FILES_APP_COMPONENT))))
.thenReturn(mActivityIcon);
- when(mPackageManager.getUserBadgedIcon(
- any(), any())).thenReturn(mBadgedActivityIcon);
- when(mPackageManager.getActivityInfo(any(),
- any(PackageManager.ComponentInfoFlags.class))).thenReturn(mActivityInfo);
- when(mActivityInfo.loadLabel(eq(mPackageManager))).thenReturn(APP_LABEL);
+ when(mPackageManager.getUserBadgedIcon(any(), any()))
+ .thenReturn(mBadgedActivityIcon);
+ when(mPackageManager.getActivityInfo(
+ eq(ComponentName.unflattenFromString(FILES_APP_COMPONENT)),
+ any(PackageManager.ComponentInfoFlags.class)))
+ .thenReturn(mActivityInfo);
+ when(mActivityInfo.loadLabel(eq(mPackageManager)))
+ .thenReturn(FILES_APP_LABEL);
mSharedPreferences.edit().putBoolean(
WorkProfileMessageController.PREFERENCE_KEY, false).apply();
@@ -120,14 +127,15 @@
@Test
public void testOnScreenshotTaken_packageNotFound()
throws PackageManager.NameNotFoundException {
- when(mPackageManager.getActivityInfo(any(),
+ when(mPackageManager.getActivityInfo(
+ eq(ComponentName.unflattenFromString(FILES_APP_COMPONENT)),
any(PackageManager.ComponentInfoFlags.class))).thenThrow(
new PackageManager.NameNotFoundException());
WorkProfileMessageController.WorkProfileFirstRunData data =
mMessageController.onScreenshotTaken(WORK_USER);
- assertEquals(DEFAULT_LABEL, data.getAppName());
+ assertEquals(DEFAULT_FILES_APP_LABEL, data.getAppName());
assertNull(data.getIcon());
}
@@ -136,16 +144,28 @@
WorkProfileMessageController.WorkProfileFirstRunData data =
mMessageController.onScreenshotTaken(WORK_USER);
- assertEquals(APP_LABEL, data.getAppName());
+ assertEquals(FILES_APP_LABEL, data.getAppName());
assertEquals(mBadgedActivityIcon, data.getIcon());
}
@Test
+ public void testOnScreenshotTaken_noFilesAppComponentDefined() {
+ when(mMockContext.getString(R.string.config_sceenshotWorkProfileFilesApp))
+ .thenReturn("");
+
+ WorkProfileMessageController.WorkProfileFirstRunData data =
+ mMessageController.onScreenshotTaken(WORK_USER);
+
+ assertEquals(DEFAULT_FILES_APP_LABEL, data.getAppName());
+ assertNull(data.getIcon());
+ }
+
+ @Test
public void testPopulateView() throws InterruptedException {
ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.screenshot_work_profile_first_run, null);
WorkProfileMessageController.WorkProfileFirstRunData data =
- new WorkProfileMessageController.WorkProfileFirstRunData(APP_LABEL,
+ new WorkProfileMessageController.WorkProfileFirstRunData(FILES_APP_LABEL,
mBadgedActivityIcon);
final CountDownLatch countdown = new CountDownLatch(1);
mMessageController.populateView(layout, data, () -> {
@@ -157,7 +177,7 @@
assertEquals(mBadgedActivityIcon, image.getDrawable());
TextView text = layout.findViewById(R.id.screenshot_message_content);
// The app name is used in a template, but at least validate that it was inserted.
- assertTrue(text.getText().toString().contains(APP_LABEL));
+ assertTrue(text.getText().toString().contains(FILES_APP_LABEL));
// Validate that clicking the dismiss button calls back properly.
assertEquals(1, countdown.getCount());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 99cf8d0..7087c01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -24,7 +24,9 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
@@ -184,6 +186,7 @@
@Mock protected NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock protected KeyguardBottomAreaView mKeyguardBottomArea;
@Mock protected KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
+ @Mock protected ViewPropertyAnimator mViewPropertyAnimator;
@Mock protected KeyguardBottomAreaView mQsFrame;
@Mock protected HeadsUpManagerPhone mHeadsUpManager;
@Mock protected NotificationShelfController mNotificationShelfController;
@@ -357,7 +360,14 @@
.thenReturn(mHeadsUpCallback);
when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea);
when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
- when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
+ when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator);
+ when(mView.animate()).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setInterpolator(any())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator);
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 9a2e415..d36cc7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -42,6 +42,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -693,6 +695,24 @@
}
@Test
+ public void testFoldToAodAnimationCleansupInAnimationEnd() {
+ ArgumentCaptor<Animator.AnimatorListener> animCaptor =
+ ArgumentCaptor.forClass(Animator.AnimatorListener.class);
+ ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> updateCaptor =
+ ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+
+ // Start fold animation & Capture Listeners
+ mNotificationPanelViewController.startFoldToAodAnimation(() -> {}, () -> {}, () -> {});
+ verify(mViewPropertyAnimator).setListener(animCaptor.capture());
+ verify(mViewPropertyAnimator).setUpdateListener(updateCaptor.capture());
+
+ // End animation and validate listeners were unset
+ animCaptor.getValue().onAnimationEnd(null);
+ verify(mViewPropertyAnimator).setListener(null);
+ verify(mViewPropertyAnimator).setUpdateListener(null);
+ }
+
+ @Test
public void testExpandWithQsMethodIsUsingLockscreenTransitionController() {
enableSplitShade(/* enabled= */ true);
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 629208e..5f34b2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
import com.android.systemui.multishade.data.repository.MultiShadeRepository
import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -62,7 +63,6 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -129,6 +129,16 @@
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
+ val multiShadeInteractor =
+ MultiShadeInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository =
+ MultiShadeRepository(
+ applicationContext = context,
+ inputProxy = inputProxy,
+ ),
+ inputProxy = inputProxy,
+ )
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
@@ -154,18 +164,15 @@
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
featureFlags,
+ { multiShadeInteractor },
+ FakeSystemClock(),
{
- MultiShadeInteractor(
+ MultiShadeMotionEventInteractor(
+ applicationContext = context,
applicationScope = testScope.backgroundScope,
- repository =
- MultiShadeRepository(
- applicationContext = context,
- inputProxy = inputProxy,
- ),
- inputProxy = inputProxy,
+ interactor = multiShadeInteractor,
)
},
- FakeSystemClock(),
)
underTest.setupExpandedStatusBar()
@@ -308,7 +315,7 @@
fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() {
// down event should be intercepted by keyguardViewManager
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
- .thenReturn(true)
+ .thenReturn(true)
// Then touch should not be intercepted
val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
@@ -316,14 +323,6 @@
}
@Test
- fun testGetBouncerContainer() =
- testScope.runTest {
- Mockito.clearInvocations(view)
- underTest.bouncerContainer
- verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
- }
-
- @Test
fun testGetKeyguardMessageArea() =
testScope.runTest {
underTest.keyguardMessageArea
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index b4b5ec1..b40181e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
import com.android.systemui.multishade.data.repository.MultiShadeRepository
import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -140,6 +141,16 @@
featureFlags.set(Flags.DUAL_SHADE, false)
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
+ val multiShadeInteractor =
+ MultiShadeInteractor(
+ applicationScope = testScope.backgroundScope,
+ repository =
+ MultiShadeRepository(
+ applicationContext = context,
+ inputProxy = inputProxy,
+ ),
+ inputProxy = inputProxy,
+ )
controller =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
@@ -165,18 +176,15 @@
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
featureFlags,
+ { multiShadeInteractor },
+ FakeSystemClock(),
{
- MultiShadeInteractor(
+ MultiShadeMotionEventInteractor(
+ applicationContext = context,
applicationScope = testScope.backgroundScope,
- repository =
- MultiShadeRepository(
- applicationContext = context,
- inputProxy = inputProxy,
- ),
- inputProxy = inputProxy,
+ interactor = multiShadeInteractor,
)
},
- FakeSystemClock(),
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt
new file mode 100644
index 0000000..64fec5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.shade
+
+import android.animation.Animator
+import android.testing.AndroidTestingRunner
+import android.transition.TransitionValues
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardStatusViewController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.NotificationPanelViewController.SplitShadeTransitionAdapter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SplitShadeTransitionAdapterTest : SysuiTestCase() {
+
+ @Mock private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+
+ private lateinit var adapter: SplitShadeTransitionAdapter
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ adapter = SplitShadeTransitionAdapter(keyguardStatusViewController)
+ }
+
+ @Test
+ fun createAnimator_nullStartValues_returnsNull() {
+ val animator = adapter.createAnimator(startValues = null, endValues = TransitionValues())
+
+ assertThat(animator).isNull()
+ }
+
+ @Test
+ fun createAnimator_nullEndValues_returnsNull() {
+ val animator = adapter.createAnimator(startValues = TransitionValues(), endValues = null)
+
+ assertThat(animator).isNull()
+ }
+
+ @Test
+ fun createAnimator_nonNullStartAndEndValues_returnsAnimator() {
+ val animator =
+ adapter.createAnimator(startValues = TransitionValues(), endValues = TransitionValues())
+
+ assertThat(animator).isNotNull()
+ }
+}
+
+private fun SplitShadeTransitionAdapter.createAnimator(
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+): Animator? {
+ return createAnimator(/* sceneRoot= */ null, startValues, endValues)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
index 6a68b71..8841f48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -65,8 +65,8 @@
// Positive translationX -> translated to the right
// 10x10 view center is 25px from the center,
// When progress is 0.5 it should be translated at:
- // 25 * 0.3 * (1 - 0.5) = 3.75px
- assertThat(view.translationX).isWithin(0.01f).of(3.75f)
+ // 25 * 0.08 * (1 - 0.5) = 1px
+ assertThat(view.translationX).isWithin(0.01f).of(1.0f)
}
@Test
@@ -81,8 +81,8 @@
// Positive translationX -> translated to the right
// 10x10 view center is 25px from the center,
// When progress is 0 it should be translated at:
- // 25 * 0.3 * (1 - 0) = 7.5px
- assertThat(view.translationX).isWithin(0.01f).of(7.5f)
+ // 25 * 0.08 * (1 - 0) = 7.5px
+ assertThat(view.translationX).isWithin(0.01f).of(2f)
}
@Test
@@ -97,7 +97,7 @@
// Positive translationX -> translated to the right
// 10x10 view center is 25px from the center,
// When progress is 1 it should be translated at:
- // 25 * 0.3 * 0 = 0px
+ // 25 * 0.08 * 0 = 0px
assertThat(view.translationX).isEqualTo(0f)
}
@@ -113,8 +113,8 @@
// Positive translationX -> translated to the right, original translation is ignored
// 10x10 view center is 25px from the center,
// When progress is 0.5 it should be translated at:
- // 25 * 0.3 * (1 - 0.5) = 3.75px
- assertThat(view.translationX).isWithin(0.01f).of(3.75f)
+ // 25 * 0.08 * (1 - 0.5) = 1px
+ assertThat(view.translationX).isWithin(0.01f).of(1.0f)
}
@Test
@@ -154,7 +154,7 @@
animator.onTransitionProgress(0.5f)
// Positive translationY -> translated to the bottom
- assertThat(view.translationY).isWithin(0.01f).of(3.75f)
+ assertThat(view.translationY).isWithin(0.01f).of(1f)
}
@Test
@@ -169,7 +169,7 @@
animator.updateViewPositions()
// Negative translationX -> translated to the left
- assertThat(view.translationX).isWithin(0.1f).of(-5.25f)
+ assertThat(view.translationX).isWithin(0.1f).of(-1.4f)
}
private fun createView(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 251aced..569f90b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -105,6 +105,7 @@
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -188,6 +189,8 @@
private AuthController mAuthController;
@Mock
private AlarmManager mAlarmManager;
+ @Mock
+ private UserTracker mUserTracker;
@Captor
private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
@Captor
@@ -209,6 +212,7 @@
private BroadcastReceiver mBroadcastReceiver;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private TestableLooper mTestableLooper;
+ private final int mCurrentUserId = 1;
private KeyguardIndicationTextView mTextView; // AOD text
@@ -260,6 +264,7 @@
.thenReturn(mDisclosureGeneric);
when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
.thenReturn(mDisclosureWithOrganization);
+ when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
mWakeLock = new WakeLockFake();
mWakeLockBuilder = new WakeLockFake.Builder(mContext);
@@ -291,7 +296,8 @@
mKeyguardBypassController, mAccessibilityManager,
mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
mAlternateBouncerInteractor,
- mAlarmManager
+ mAlarmManager,
+ mUserTracker
);
mController.init();
mController.setIndicationArea(mIndicationArea);
@@ -813,7 +819,7 @@
public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
createController();
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- 0)).thenReturn(true);
+ getCurrentUser())).thenReturn(true);
String message = "A message";
mController.setVisible(true);
@@ -828,7 +834,7 @@
// GIVEN fingerprint enrolled
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- 0)).thenReturn(true);
+ getCurrentUser())).thenReturn(true);
// WHEN help messages received that are allowed to show
final String helpString = "helpString";
@@ -855,7 +861,7 @@
// GIVEN fingerprint enrolled
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- 0)).thenReturn(true);
+ getCurrentUser())).thenReturn(true);
// WHEN help messages received that aren't supposed to show
final String helpString = "helpString";
@@ -882,7 +888,7 @@
// GIVEN fingerprint NOT enrolled
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- 0)).thenReturn(false);
+ getCurrentUser())).thenReturn(false);
// WHEN help messages received
final Set<CharSequence> helpStrings = new HashSet<>();
@@ -913,7 +919,7 @@
// GIVEN fingerprint NOT enrolled
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- 0)).thenReturn(false);
+ getCurrentUser())).thenReturn(false);
// WHEN help message received and deferred message is valid
final String helpString = "helpMsg";
@@ -944,7 +950,7 @@
// GIVEN fingerprint enrolled
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- 0)).thenReturn(true);
+ getCurrentUser())).thenReturn(true);
// WHEN help message received and deferredMessage is valid
final String helpString = "helpMsg";
@@ -1173,7 +1179,7 @@
// WHEN trust is granted
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
- mKeyguardUpdateMonitorCallback.onTrustChanged(KeyguardUpdateMonitor.getCurrentUser());
+ mKeyguardUpdateMonitorCallback.onTrustChanged(getCurrentUser());
// THEN verify the trust granted message shows
verifyIndicationMessage(
@@ -1238,7 +1244,7 @@
public void coEx_faceSuccess_showsPressToOpen() {
// GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, no a11y enabled
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
when(mAccessibilityManager.isEnabled()).thenReturn(false);
@@ -1262,7 +1268,7 @@
public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() {
// GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
when(mAccessibilityManager.isEnabled()).thenReturn(true);
@@ -1286,7 +1292,7 @@
public void coEx_faceSuccess_a11yEnabled_showsFaceUnlockedSwipeToOpen() {
// GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
when(mAccessibilityManager.isEnabled()).thenReturn(true);
@@ -1309,7 +1315,7 @@
public void faceOnly_faceSuccess_showsFaceUnlockedSwipeToOpen() {
// GIVEN bouncer isn't showing, can skip bouncer, no udfps supported
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
createController();
@@ -1331,7 +1337,7 @@
public void udfpsOnly_a11yEnabled_showsSwipeToOpen() {
// GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
when(mAccessibilityManager.isEnabled()).thenReturn(true);
@@ -1351,7 +1357,7 @@
public void udfpsOnly_showsPressToOpen() {
// GIVEN bouncer isn't showing, udfps is supported, a11y is NOT enabled, can skip bouncer
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
when(mAccessibilityManager.isEnabled()).thenReturn(false);
@@ -1372,7 +1378,7 @@
// GIVEN bouncer isn't showing, can skip bouncer, no security (udfps isn't supported,
// face wasn't authenticated)
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
createController();
@@ -1390,7 +1396,7 @@
public void cannotSkipBouncer_showSwipeToUnlockHint() {
// GIVEN bouncer isn't showing and cannot skip bouncer
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()))
.thenReturn(false);
createController();
mController.setVisible(true);
@@ -1746,10 +1752,14 @@
private void setupFingerprintUnlockPossible(boolean possible) {
when(mKeyguardUpdateMonitor
- .getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser()))
+ .getCachedIsUnlockWithFingerprintPossible(getCurrentUser()))
.thenReturn(possible);
}
+ private int getCurrentUser() {
+ return mCurrentUserId;
+ }
+
private void onFaceLockoutError(String errMsg) {
mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
errMsg,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 653b0c7..09b00e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -858,6 +858,23 @@
}
@Test
+ public void testShouldNotScreen_appSuspended() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ modifyRanking(entry).setSuspended(true).build();
+
+ assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_SUSPENDED);
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUSPENDED");
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
public void logFullScreenIntentDecision_shouldAlmostAlwaysLogOneTime() {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 78da782..824eb4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -425,12 +425,12 @@
public void testGetViewTranslationAnimator_notExpandableNotificationRow() {
Animator animator = mock(Animator.class);
AnimatorUpdateListener listener = mock(AnimatorUpdateListener.class);
- doReturn(animator).when(mSwipeHelper).superGetViewTranslationAnimator(mView, 0, listener);
+ doReturn(animator).when(mSwipeHelper).createTranslationAnimation(mView, 0, listener);
- assertEquals("returns the correct animator from super", animator,
+ assertEquals("Should create a new animator", animator,
mSwipeHelper.getViewTranslationAnimator(mView, 0, listener));
- verify(mSwipeHelper, times(1)).superGetViewTranslationAnimator(mView, 0, listener);
+ verify(mSwipeHelper).createTranslationAnimation(mView, 0, listener);
}
@Test
@@ -439,10 +439,10 @@
AnimatorUpdateListener listener = mock(AnimatorUpdateListener.class);
doReturn(animator).when(mNotificationRow).getTranslateViewAnimator(0, listener);
- assertEquals("returns the correct animator from super when view is an ENR", animator,
+ assertEquals("Should return the animator from ExpandableNotificationRow", animator,
mSwipeHelper.getViewTranslationAnimator(mNotificationRow, 0, listener));
- verify(mNotificationRow, times(1)).getTranslateViewAnimator(0, listener);
+ verify(mNotificationRow).getTranslateViewAnimator(0, listener);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
index 3108ed9..fe12051 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
@@ -21,6 +21,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -49,6 +50,13 @@
}
@Test
+ public void userSwitcherChip_defaultVisibilityIsGone() {
+ assertThat(mKeyguardStatusBarView.findViewById(
+ R.id.user_switcher_container).getVisibility()).isEqualTo(
+ View.GONE);
+ }
+
+ @Test
public void setTopClipping_clippingUpdated() {
int topClipping = 40;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index cdc9898..3edf33b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -25,6 +25,8 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
@@ -34,6 +36,7 @@
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -54,6 +57,8 @@
@Mock
private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
@Mock
private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
@@ -93,6 +98,8 @@
@Test
fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
+ whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS))
+ .thenReturn(true)
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
unfoldConfig.isEnabled = true
@@ -108,6 +115,20 @@
}
@Test
+ fun onViewAttachedAndDrawn_statusBarAnimationDisabled_animationNotInitialized() {
+ whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS))
+ .thenReturn(false)
+ val view = createViewMock()
+ unfoldConfig.isEnabled = true
+ // create the controller on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
+
+ verify(moveFromCenterAnimation, never()).onViewsReady(any())
+ }
+
+ @Test
fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
val returnVal = view.onTouchEvent(
@@ -179,6 +200,7 @@
return PhoneStatusBarViewController.Factory(
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
+ featureFlags,
userChipViewModel,
centralSurfacesImpl,
shadeControllerImpl,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
index 3bc288a2..08e89fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -20,13 +20,17 @@
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.StatusBarIcon
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
+import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
@SmallTest
class StatusBarIconControllerImplTest : SysuiTestCase() {
@@ -34,15 +38,19 @@
private lateinit var underTest: StatusBarIconControllerImpl
private lateinit var iconList: StatusBarIconList
+ private lateinit var commandQueueCallbacks: CommandQueue.Callbacks
private val iconGroup: StatusBarIconController.IconManager = mock()
+ @Mock private lateinit var commandQueue: CommandQueue
+
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
iconList = StatusBarIconList(arrayOf())
underTest =
StatusBarIconControllerImpl(
context,
- mock(),
+ commandQueue,
mock(),
mock(),
mock(),
@@ -51,11 +59,14 @@
mock(),
)
underTest.addIconGroup(iconGroup)
+ val commandQueueCallbacksCaptor = kotlinArgumentCaptor<CommandQueue.Callbacks>()
+ verify(commandQueue).addCallback(commandQueueCallbacksCaptor.capture())
+ commandQueueCallbacks = commandQueueCallbacksCaptor.value
}
/** Regression test for b/255428281. */
@Test
- fun internalAndExternalIconWithSameName_bothDisplayed() {
+ fun internalAndExternalIconWithSameName_externalFromTile_bothDisplayed() {
val slotName = "mute"
// Internal
@@ -71,7 +82,7 @@
/* number= */ 0,
"contentDescription",
)
- underTest.setIcon(slotName, externalIcon)
+ underTest.setIconFromTile(slotName, externalIcon)
assertThat(iconList.slots).hasSize(2)
// Whichever was added last comes first
@@ -83,17 +94,45 @@
/** Regression test for b/255428281. */
@Test
- fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() {
+ fun internalAndExternalIconWithSameName_externalFromCommandQueue_bothDisplayed() {
val slotName = "mute"
// Internal
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
// External
- underTest.setIcon(slotName, createExternalIcon())
+ val externalIcon =
+ StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 2,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "contentDescription",
+ )
+ commandQueueCallbacks.setIcon(slotName, externalIcon)
- // WHEN the external icon is removed via #removeIcon
- underTest.removeIcon(slotName)
+ assertThat(iconList.slots).hasSize(2)
+ // Whichever was added last comes first
+ assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(iconList.slots[1].name).isEqualTo(slotName)
+ assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
+ assertThat(iconList.slots[1].hasIconsInSlot()).isTrue()
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_externalRemoved_fromCommandQueue_internalStays() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ commandQueueCallbacks.setIcon(slotName, createExternalIcon())
+
+ // WHEN the external icon is removed via CommandQueue.Callbacks#removeIcon
+ commandQueueCallbacks.removeIcon(slotName)
// THEN the external icon is removed but the internal icon remains
// Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for
@@ -109,17 +148,17 @@
/** Regression test for b/255428281. */
@Test
- fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() {
+ fun internalAndExternalIconWithSameName_externalRemoved_fromTileRemove_internalStays() {
val slotName = "mute"
// Internal
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
// External
- underTest.setIcon(slotName, createExternalIcon())
+ underTest.setIconFromTile(slotName, createExternalIcon())
- // WHEN the external icon is removed via #removeAllIconsForExternalSlot
- underTest.removeAllIconsForExternalSlot(slotName)
+ // WHEN the external icon is removed via #removeIconForTile
+ underTest.removeIconForTile(slotName)
// THEN the external icon is removed but the internal icon remains
assertThat(iconList.slots).hasSize(2)
@@ -133,17 +172,17 @@
/** Regression test for b/255428281. */
@Test
- fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() {
+ fun internalAndExternalIconWithSameName_externalRemoved_fromTileSetNull_internalStays() {
val slotName = "mute"
// Internal
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
// External
- underTest.setIcon(slotName, createExternalIcon())
+ underTest.setIconFromTile(slotName, createExternalIcon())
- // WHEN the external icon is removed via a #setIcon(null)
- underTest.setIcon(slotName, /* icon= */ null)
+ // WHEN the external icon is removed via a #setIconFromTile(null)
+ underTest.setIconFromTile(slotName, /* icon= */ null)
// THEN the external icon is removed but the internal icon remains
assertThat(iconList.slots).hasSize(2)
@@ -164,12 +203,12 @@
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
// External
- underTest.setIcon(slotName, createExternalIcon())
+ underTest.setIconFromTile(slotName, createExternalIcon())
// WHEN the internal icon is removed via #removeIcon
underTest.removeIcon(slotName, /* tag= */ 0)
- // THEN the external icon is removed but the internal icon remains
+ // THEN the internal icon is removed but the external icon remains
assertThat(iconList.slots).hasSize(2)
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
assertThat(iconList.slots[1].name).isEqualTo(slotName)
@@ -188,12 +227,12 @@
underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
// External
- underTest.setIcon(slotName, createExternalIcon())
+ underTest.setIconFromTile(slotName, createExternalIcon())
// WHEN the internal icon is removed via #removeAllIconsForSlot
underTest.removeAllIconsForSlot(slotName)
- // THEN the external icon is removed but the internal icon remains
+ // THEN the internal icon is removed but the external icon remains
assertThat(iconList.slots).hasSize(2)
assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
assertThat(iconList.slots[1].name).isEqualTo(slotName)
@@ -221,7 +260,7 @@
/* number= */ 0,
"externalDescription",
)
- underTest.setIcon(slotName, startingExternalIcon)
+ underTest.setIconFromTile(slotName, startingExternalIcon)
// WHEN the internal icon is updated
underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription")
@@ -243,7 +282,7 @@
/** Regression test for b/255428281. */
@Test
- fun internalAndExternalIconWithSameName_externalUpdatedIndependently() {
+ fun internalAndExternalIconWithSameName_fromTile_externalUpdatedIndependently() {
val slotName = "mute"
// Internal
@@ -259,7 +298,7 @@
/* number= */ 0,
"externalDescription",
)
- underTest.setIcon(slotName, startingExternalIcon)
+ underTest.setIconFromTile(slotName, startingExternalIcon)
// WHEN the external icon is updated
val newExternalIcon =
@@ -271,7 +310,54 @@
/* number= */ 0,
"newExternalDescription",
)
- underTest.setIcon(slotName, newExternalIcon)
+ underTest.setIconFromTile(slotName, newExternalIcon)
+
+ // THEN only the external slot gets the updates
+ val externalSlot = iconList.slots[0]
+ val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!!
+ assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
+ assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription")
+ assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21)
+
+ // And the internal slot has its own values
+ val internalSlot = iconList.slots[1]
+ val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!!
+ assertThat(internalSlot.name).isEqualTo(slotName)
+ assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription")
+ assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10)
+ }
+
+ /** Regression test for b/255428281. */
+ @Test
+ fun internalAndExternalIconWithSameName_fromCommandQueue_externalUpdatedIndependently() {
+ val slotName = "mute"
+
+ // Internal
+ underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
+
+ // External
+ val startingExternalIcon =
+ StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 20,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "externalDescription",
+ )
+ commandQueueCallbacks.setIcon(slotName, startingExternalIcon)
+
+ // WHEN the external icon is updated
+ val newExternalIcon =
+ StatusBarIcon(
+ "external.package",
+ UserHandle.ALL,
+ /* iconId= */ 21,
+ /* iconLevel= */ 0,
+ /* number= */ 0,
+ "newExternalDescription",
+ )
+ commandQueueCallbacks.setIcon(slotName, newExternalIcon)
// THEN only the external slot gets the updates
val externalSlot = iconList.slots[0]
@@ -289,8 +375,16 @@
}
@Test
- fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() {
- underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
+ fun externalSlot_fromTile_alreadyEndsWithSuffix_suffixNotAddedTwice() {
+ underTest.setIconFromTile("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
+
+ assertThat(iconList.slots).hasSize(1)
+ assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
+ }
+
+ @Test
+ fun externalSlot_fromCommandQueue_alreadyEndsWithSuffix_suffixNotAddedTwice() {
+ commandQueueCallbacks.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon())
assertThat(iconList.slots).hasSize(1)
assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index d9546877..14aee4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -154,7 +154,6 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
.thenReturn(mKeyguardMessageAreaController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 64545b1..be0c83f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -294,6 +294,13 @@
}
@Test
+ public void userChip_defaultVisibilityIsGone() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ assertEquals(View.GONE, getUserChipView().getVisibility());
+ }
+
+ @Test
public void disable_noOngoingCall_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -333,6 +340,19 @@
}
@Test
+ public void disable_hasOngoingCallButAlsoHun_chipHidden() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+ when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
+ when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ assertEquals(View.GONE,
+ mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ }
+
+ @Test
public void disable_ongoingCallEnded_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -558,6 +578,10 @@
return (CollapsedStatusBarFragment) mFragment;
}
+ private View getUserChipView() {
+ return mFragment.getView().findViewById(R.id.user_switcher_container);
+ }
+
private View getClockView() {
return mFragment.getView().findViewById(R.id.clock);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 542b688..934e1c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -25,7 +25,6 @@
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
@@ -68,6 +67,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -392,13 +392,17 @@
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
- val type = NETWORK_TYPE_UNKNOWN
- val expected = UnknownNetworkType
- val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ val ti =
+ telephonyDisplayInfo(
+ networkType = NETWORK_TYPE_UNKNOWN,
+ overrideNetworkType = NETWORK_TYPE_UNKNOWN,
+ )
+
callback.onDisplayInfoChanged(ti)
+ val expected = UnknownNetworkType
assertThat(latest).isEqualTo(expected)
- assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type))
+ assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN))
job.cancel()
}
@@ -412,14 +416,10 @@
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val overrideType = OVERRIDE_NETWORK_TYPE_NONE
val type = NETWORK_TYPE_LTE
- val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
- val ti =
- mock<TelephonyDisplayInfo>().also {
- whenever(it.overrideNetworkType).thenReturn(overrideType)
- whenever(it.networkType).thenReturn(type)
- }
+ val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = overrideType)
callback.onDisplayInfoChanged(ti)
+ val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
assertThat(latest).isEqualTo(expected)
job.cancel()
@@ -433,14 +433,10 @@
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
- val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
- val ti =
- mock<TelephonyDisplayInfo>().also {
- whenever(it.networkType).thenReturn(type)
- whenever(it.overrideNetworkType).thenReturn(type)
- }
+ val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = type)
callback.onDisplayInfoChanged(ti)
+ val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
assertThat(latest).isEqualTo(expected)
job.cancel()
@@ -455,14 +451,10 @@
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val unknown = NETWORK_TYPE_UNKNOWN
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
- val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
- val ti =
- mock<TelephonyDisplayInfo>().also {
- whenever(it.networkType).thenReturn(unknown)
- whenever(it.overrideNetworkType).thenReturn(type)
- }
+ val ti = telephonyDisplayInfo(unknown, type)
callback.onDisplayInfoChanged(ti)
+ val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
assertThat(latest).isEqualTo(expected)
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index bbf04ed2..9da9ff7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -64,10 +64,14 @@
*
* Kind of like an interaction test case build just for [TelephonyCallback]
*
- * The list of telephony callbacks we use is: [TelephonyCallback.CarrierNetworkListener]
- * [TelephonyCallback.DataActivityListener] [TelephonyCallback.DataConnectionStateListener]
- * [TelephonyCallback.DataEnabledListener] [TelephonyCallback.DisplayInfoListener]
- * [TelephonyCallback.ServiceStateListener] [TelephonyCallback.SignalStrengthsListener]
+ * The list of telephony callbacks we use is:
+ * - [TelephonyCallback.CarrierNetworkListener]
+ * - [TelephonyCallback.DataActivityListener]
+ * - [TelephonyCallback.DataConnectionStateListener]
+ * - [TelephonyCallback.DataEnabledListener]
+ * - [TelephonyCallback.DisplayInfoListener]
+ * - [TelephonyCallback.ServiceStateListener]
+ * - [TelephonyCallback.SignalStrengthsListener]
*
* Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed
* by only a single callback can immediately create backpressure on the other fields related to a
@@ -201,7 +205,6 @@
200 /* unused */
)
- // Send a bunch of events that we don't care about, to overrun the replay buffer
flipActivity(100, activityCallback)
val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
@@ -225,7 +228,6 @@
enabledCallback.onDataEnabledChanged(true, 1 /* unused */)
- // Send a bunch of events that we don't care about, to overrun the replay buffer
flipActivity(100, activityCallback)
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
@@ -252,7 +254,6 @@
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
displayInfoCallback.onDisplayInfoChanged(ti)
- // Send a bunch of events that we don't care about, to overrun the replay buffer
flipActivity(100, activityCallback)
val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index d07b96f..cf815c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -19,6 +19,7 @@
import android.telephony.CellSignalStrengthCdma
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -48,6 +49,12 @@
return signalStrength
}
+ fun telephonyDisplayInfo(networkType: Int, overrideNetworkType: Int) =
+ mock<TelephonyDisplayInfo>().also {
+ whenever(it.networkType).thenReturn(networkType)
+ whenever(it.overrideNetworkType).thenReturn(overrideNetworkType)
+ }
+
inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
assertThat(cbs.size).isEqualTo(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 833cabb..7d64eaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,6 +45,8 @@
import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +54,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -60,10 +64,11 @@
private UserTracker mUserTracker;
private LocalBluetoothManager mMockBluetoothManager;
private CachedBluetoothDeviceManager mMockDeviceManager;
- private LocalBluetoothAdapter mMockAdapter;
+ private LocalBluetoothAdapter mMockLocalAdapter;
private TestableLooper mTestableLooper;
private DumpManager mMockDumpManager;
private BluetoothControllerImpl mBluetoothControllerImpl;
+ private BluetoothAdapter mMockAdapter;
private List<CachedBluetoothDevice> mDevices;
@@ -74,10 +79,11 @@
mDevices = new ArrayList<>();
mUserTracker = mock(UserTracker.class);
mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
+ mMockAdapter = mock(BluetoothAdapter.class);
when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
- mMockAdapter = mock(LocalBluetoothAdapter.class);
- when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockAdapter);
+ mMockLocalAdapter = mock(LocalBluetoothAdapter.class);
+ when(mMockBluetoothManager.getBluetoothAdapter()).thenReturn(mMockLocalAdapter);
when(mMockBluetoothManager.getEventManager()).thenReturn(mock(BluetoothEventManager.class));
when(mMockBluetoothManager.getProfileManager())
.thenReturn(mock(LocalBluetoothProfileManager.class));
@@ -89,7 +95,8 @@
mock(BluetoothLogger.class),
mTestableLooper.getLooper(),
mTestableLooper.getLooper(),
- mMockBluetoothManager);
+ mMockBluetoothManager,
+ mMockAdapter);
}
@Test
@@ -98,7 +105,8 @@
when(device.isConnected()).thenReturn(true);
when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
mDevices.add(device);
- when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+ when(mMockLocalAdapter.getConnectionState())
+ .thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
mBluetoothControllerImpl.onConnectionStateChanged(null,
BluetoothAdapter.STATE_DISCONNECTED);
@@ -163,7 +171,7 @@
@Test
public void testOnServiceConnected_updatesConnectionState() {
- when(mMockAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
+ when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
mBluetoothControllerImpl.onServiceConnected();
@@ -184,7 +192,7 @@
@Test
public void testOnBluetoothStateChange_updatesConnectionState() {
- when(mMockAdapter.getConnectionState()).thenReturn(
+ when(mMockLocalAdapter.getConnectionState()).thenReturn(
BluetoothAdapter.STATE_CONNECTING,
BluetoothAdapter.STATE_DISCONNECTED);
@@ -240,6 +248,33 @@
assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
}
+ @Test
+ public void testAddOnMetadataChangedListener_registersListenerOnAdapter() {
+ CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice device = mock(BluetoothDevice.class);
+ when(cachedDevice.getDevice()).thenReturn(device);
+ Executor executor = new FakeExecutor(new FakeSystemClock());
+ BluetoothAdapter.OnMetadataChangedListener listener = (bluetoothDevice, i, bytes) -> {
+ };
+
+ mBluetoothControllerImpl.addOnMetadataChangedListener(cachedDevice, executor, listener);
+
+ verify(mMockAdapter, times(1)).addOnMetadataChangedListener(device, executor, listener);
+ }
+
+ @Test
+ public void testRemoveOnMetadataChangedListener_removesListenerFromAdapter() {
+ CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice device = mock(BluetoothDevice.class);
+ when(cachedDevice.getDevice()).thenReturn(device);
+ BluetoothAdapter.OnMetadataChangedListener listener = (bluetoothDevice, i, bytes) -> {
+ };
+
+ mBluetoothControllerImpl.removeOnMetadataChangedListener(cachedDevice, listener);
+
+ verify(mMockAdapter, times(1)).removeOnMetadataChangedListener(device, listener);
+ }
+
/** Regression test for b/246876230. */
@Test
public void testOnActiveDeviceChanged_null_noCrash() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
index 4525ad2..17f8ec2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -507,11 +507,29 @@
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
verify(uiEventLogger, times(1))
- .logWithInstanceId(
+ .logWithInstanceIdAndPosition(
StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
0,
null,
- InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
+ InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
+ 0,
+ )
+ }
+
+ @Test
+ fun onBatteryStateChanged_batteryPresent_btStylusPresent_logsSessionStart() {
+ whenever(batteryState.isPresent).thenReturn(true)
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
+
+ verify(uiEventLogger, times(1))
+ .logWithInstanceIdAndPosition(
+ StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
+ 0,
+ null,
+ InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
+ 1,
)
}
@@ -545,19 +563,21 @@
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
verify(uiEventLogger, times(1))
- .logWithInstanceId(
+ .logWithInstanceIdAndPosition(
StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
0,
null,
- instanceId
+ instanceId,
+ 0
)
verify(uiEventLogger, times(1))
- .logWithInstanceId(
+ .logWithInstanceIdAndPosition(
StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
0,
null,
- instanceId
+ instanceId,
+ 0
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 586bdc6..6e24941 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -685,7 +685,7 @@
allowSwipeToDismiss: Boolean = false,
): ChipbarInfo {
return ChipbarInfo(
- TintedIcon(startIcon, tintAttr = null),
+ TintedIcon(startIcon, tint = null),
text,
endItem,
vibrationEffect,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 0413d92..9fe2f56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -40,7 +40,7 @@
@Before
fun setUp() {
- progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
+ progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider)
progressProvider.addCallback(listener)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 8476d0d..bf54d42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -16,6 +16,9 @@
package com.android.systemui.unfold.updates
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.core.util.Consumer
@@ -33,6 +36,7 @@
import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import org.junit.Before
@@ -49,20 +53,19 @@
@SmallTest
class DeviceFoldStateProviderTest : SysuiTestCase() {
- @Mock
- private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
+ @Mock private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
- @Mock
- private lateinit var handler: Handler
+ @Mock private lateinit var handler: Handler
- @Mock
- private lateinit var rotationChangeProvider: RotationChangeProvider
+ @Mock private lateinit var rotationChangeProvider: RotationChangeProvider
- @Mock
- private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
+ @Mock private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
- @Captor
- private lateinit var rotationListener: ArgumentCaptor<RotationListener>
+ @Mock private lateinit var resources: Resources
+
+ @Mock private lateinit var context: Context
+
+ @Captor private lateinit var rotationListener: ArgumentCaptor<RotationListener>
private val foldProvider = TestFoldProvider()
private val screenOnStatusProvider = TestScreenOnStatusProvider()
@@ -81,10 +84,13 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- val config = object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
- override val halfFoldedTimeoutMillis: Int
- get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
- }
+ val config =
+ object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
+ override val halfFoldedTimeoutMillis: Int
+ get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
+ }
+ whenever(context.resources).thenReturn(resources)
+ whenever(context.mainExecutor).thenReturn(mContext.mainExecutor)
foldStateProvider =
DeviceFoldStateProvider(
@@ -95,6 +101,7 @@
activityTypeProvider,
unfoldKeyguardVisibilityProvider,
rotationChangeProvider,
+ context,
context.mainExecutor,
handler
)
@@ -112,7 +119,8 @@
override fun onUnfoldedScreenAvailable() {
unfoldedScreenAvailabilityUpdates.add(Unit)
}
- })
+ }
+ )
foldStateProvider.start()
verify(rotationChangeProvider).addCallback(capture(rotationListener))
@@ -134,6 +142,7 @@
// By default, we're on launcher.
setupForegroundActivityType(isHomeActivity = true)
+ setIsLargeScreen(true)
}
@Test
@@ -181,7 +190,7 @@
sendHingeAngleEvent(10)
assertThat(foldUpdates)
- .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
+ .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
}
@@ -386,8 +395,10 @@
setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(
- START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
- HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
+ START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
+ HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() -
+ 1
+ )
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@@ -429,8 +440,10 @@
setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(
- START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
- HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
+ START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
+ HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() -
+ 1
+ )
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@@ -470,7 +483,7 @@
sendHingeAngleEvent(130)
sendHingeAngleEvent(120)
assertThat(foldUpdates)
- .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
+ .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
}
@Test
@@ -531,8 +544,8 @@
rotationListener.value.onRotationChanged(1)
- assertThat(foldUpdates).containsExactly(
- FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
+ assertThat(foldUpdates)
+ .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
}
@Test
@@ -545,6 +558,45 @@
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
}
+ @Test
+ fun onFolding_onSmallScreen_tansitionDoesNotStart() {
+ setIsLargeScreen(false)
+
+ setInitialHingeAngle(120)
+ sendHingeAngleEvent(110)
+ sendHingeAngleEvent(100)
+
+ assertThat(foldUpdates).isEmpty()
+ }
+
+ @Test
+ fun onFolding_onLargeScreen_tansitionStarts() {
+ setIsLargeScreen(true)
+
+ setInitialHingeAngle(120)
+ sendHingeAngleEvent(110)
+ sendHingeAngleEvent(100)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun onUnfold_onSmallScreen_emitsStartOpening() {
+ // the new display state might arrive later, so it shouldn't be used to decide to send the
+ // start opening event, but only for the closing.
+ setFoldState(folded = true)
+ setIsLargeScreen(false)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ sendHingeAngleEvent(10)
+ sendHingeAngleEvent(20)
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
+ }
+
private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
}
@@ -566,6 +618,13 @@
foldProvider.notifyFolded(folded)
}
+ private fun setIsLargeScreen(isLargeScreen: Boolean) {
+ val smallestScreenWidth = if (isLargeScreen) { 601 } else { 10 }
+ val configuration = Configuration()
+ configuration.smallestScreenWidthDp = smallestScreenWidth
+ whenever(resources.configuration).thenReturn(configuration)
+ }
+
private fun fireScreenOnEvent() {
screenOnStatusProvider.notifyScreenTurnedOn()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
index 075f393..84129be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java
@@ -16,10 +16,16 @@
package com.android.systemui.util.sensors;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.content.res.Resources;
+import android.hardware.Sensor;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -46,28 +52,52 @@
@Mock private Resources mResources;
@Mock private DevicePostureController mDevicePostureController;
@Mock private AsyncSensorManager mSensorManager;
+ @Mock private Sensor mMockedPrimaryProxSensor;
@Captor private ArgumentCaptor<DevicePostureController.Callback> mPostureListenerCaptor =
ArgumentCaptor.forClass(DevicePostureController.Callback.class);
private DevicePostureController.Callback mPostureListener;
- private PostureDependentProximitySensor mProximitySensor;
- private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ private PostureDependentProximitySensor mPostureDependentProximitySensor;
+ private ThresholdSensor[] mPrimaryProxSensors;
+ private ThresholdSensor[] mSecondaryProxSensors;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
- mProximitySensor = new PostureDependentProximitySensor(
- new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
- new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE],
- mFakeExecutor,
+ setupProximitySensors(DEVICE_POSTURE_CLOSED);
+ mPostureDependentProximitySensor = new PostureDependentProximitySensor(
+ mPrimaryProxSensors,
+ mSecondaryProxSensors,
+ new FakeExecutor(new FakeSystemClock()),
new FakeExecution(),
mDevicePostureController
);
}
+ /**
+ * Support a proximity sensor only for the given devicePosture for the primary sensor.
+ * Otherwise, all other postures don't support prox.
+ */
+ private void setupProximitySensors(
+ @DevicePostureController.DevicePostureInt int proxExistsForPosture) {
+ final ThresholdSensorImpl.Builder sensorBuilder = new ThresholdSensorImpl.BuilderFactory(
+ mResources, mSensorManager, new FakeExecution()).createBuilder();
+
+ mPrimaryProxSensors = new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+ mSecondaryProxSensors =
+ new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE];
+ for (int i = 0; i < DevicePostureController.SUPPORTED_POSTURES_SIZE; i++) {
+ mPrimaryProxSensors[i] = sensorBuilder.setSensor(null).setThresholdValue(0).build();
+ mSecondaryProxSensors[i] = sensorBuilder.setSensor(null).setThresholdValue(0).build();
+ }
+
+ mPrimaryProxSensors[proxExistsForPosture] = sensorBuilder
+ .setSensor(mMockedPrimaryProxSensor).setThresholdValue(5).build();
+ }
+
@Test
public void testPostureChangeListenerAdded() {
capturePostureListener();
@@ -83,30 +113,59 @@
// THEN device posture is updated to DEVICE_POSTURE_OPENED
assertEquals(DevicePostureController.DEVICE_POSTURE_OPENED,
- mProximitySensor.mDevicePosture);
+ mPostureDependentProximitySensor.mDevicePosture);
// WHEN the posture changes to DEVICE_POSTURE_CLOSED
- mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED);
+ mPostureListener.onPostureChanged(DEVICE_POSTURE_CLOSED);
// THEN device posture is updated to DEVICE_POSTURE_CLOSED
- assertEquals(DevicePostureController.DEVICE_POSTURE_CLOSED,
- mProximitySensor.mDevicePosture);
+ assertEquals(DEVICE_POSTURE_CLOSED,
+ mPostureDependentProximitySensor.mDevicePosture);
// WHEN the posture changes to DEVICE_POSTURE_FLIPPED
mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED);
// THEN device posture is updated to DEVICE_POSTURE_FLIPPED
assertEquals(DevicePostureController.DEVICE_POSTURE_FLIPPED,
- mProximitySensor.mDevicePosture);
+ mPostureDependentProximitySensor.mDevicePosture);
// WHEN the posture changes to DEVICE_POSTURE_HALF_OPENED
mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
// THEN device posture is updated to DEVICE_POSTURE_HALF_OPENED
assertEquals(DevicePostureController.DEVICE_POSTURE_HALF_OPENED,
- mProximitySensor.mDevicePosture);
+ mPostureDependentProximitySensor.mDevicePosture);
}
+ @Test
+ public void proxSensorRegisters_proxSensorValid() {
+ // GIVEN posture that supports a valid posture with a prox sensor
+ capturePostureListener();
+ mPostureListener.onPostureChanged(DEVICE_POSTURE_CLOSED);
+
+ // WHEN a listener registers
+ mPostureDependentProximitySensor.register(mock(ThresholdSensor.Listener.class));
+
+ // THEN PostureDependentProximitySensor is registered
+ assertTrue(mPostureDependentProximitySensor.isRegistered());
+ }
+
+ @Test
+ public void proxSensorReregisters_postureChangesAndNewlySupportsProx() {
+ // GIVEN there's a registered listener but posture doesn't support prox
+ assertFalse(mPostureDependentProximitySensor.isRegistered());
+ mPostureDependentProximitySensor.register(mock(ThresholdSensor.Listener.class));
+ assertFalse(mPostureDependentProximitySensor.isRegistered());
+
+ // WHEN posture that supports a valid posture with a prox sensor
+ capturePostureListener();
+ mPostureListener.onPostureChanged(DEVICE_POSTURE_CLOSED);
+
+ // THEN PostureDependentProximitySensor is registered
+ assertTrue(mPostureDependentProximitySensor.isRegistered());
+ }
+
+
private void capturePostureListener() {
verify(mDevicePostureController).addCallback(mPostureListenerCaptor.capture());
mPostureListener = mPostureListenerCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ee4e00b..cc3b4ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -284,6 +284,8 @@
private ShadeWindowLogger mShadeWindowLogger;
@Mock
private NotifPipelineFlags mNotifPipelineFlags;
+ @Mock
+ private Icon mAppBubbleIcon;
private TestableBubblePositioner mPositioner;
@@ -303,7 +305,7 @@
// For the purposes of this test, just run everything synchronously
ShellExecutor syncExecutor = new SyncExecutor();
- mUser0 = createUserHande(/* userId= */ 0);
+ mUser0 = createUserHandle(/* userId= */ 0);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
when(mNotificationShadeWindowView.getViewTreeObserver())
@@ -1250,7 +1252,7 @@
@Test
public void testShowManageMenuChangesSysuiState_appBubble() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
assertTrue(mBubbleController.hasBubbles());
// Expand the stack
@@ -1671,7 +1673,7 @@
assertThat(mBubbleController.isStackExpanded()).isFalse();
assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true),
/* showInShade= */ eq(false));
@@ -1681,13 +1683,13 @@
@Test
public void testShowOrHideAppBubble_expandIfCollapsed() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.collapseStack();
assertThat(mBubbleController.isStackExpanded()).isFalse();
// Calling this while collapsed will expand the app bubble
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isTrue();
@@ -1696,12 +1698,12 @@
@Test
public void testShowOrHideAppBubble_collapseIfSelected() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isTrue();
// Calling this while the app bubble is expanded should collapse the stack
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isFalse();
@@ -1711,15 +1713,15 @@
@Test
public void testShowOrHideAppBubbleWithNonPrimaryUser_bubbleCollapsedWithExpectedUser() {
- UserHandle user10 = createUserHande(/* userId = */ 10);
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10);
+ UserHandle user10 = createUserHandle(/* userId = */ 10);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10, mAppBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10);
// Calling this while the app bubble is expanded should collapse the stack
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10, mAppBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isFalse();
@@ -1729,13 +1731,13 @@
@Test
public void testShowOrHideAppBubble_selectIfNotSelected() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey());
assertThat(mBubbleController.isStackExpanded()).isTrue();
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0);
+ mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE);
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(2);
@@ -1870,7 +1872,7 @@
mBubbleController.onUserChanged(userId);
}
- private UserHandle createUserHande(int userId) {
+ private UserHandle createUserHandle(int userId) {
UserHandle user = mock(UserHandle.class);
when(user.getIdentifier()).thenReturn(userId);
return user;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index 6cbd175..4025ade 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -14,6 +14,7 @@
package com.android.systemui.utils.leaks;
+import android.bluetooth.BluetoothAdapter;
import android.testing.LeakCheck;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -23,6 +24,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
public class FakeBluetoothController extends BaseLeakChecker<Callback> implements
BluetoothController {
@@ -110,4 +112,16 @@
public List<CachedBluetoothDevice> getConnectedDevices() {
return Collections.emptyList();
}
+
+ @Override
+ public void addOnMetadataChangedListener(CachedBluetoothDevice device, Executor executor,
+ BluetoothAdapter.OnMetadataChangedListener listener) {
+
+ }
+
+ @Override
+ public void removeOnMetadataChangedListener(CachedBluetoothDevice device,
+ BluetoothAdapter.OnMetadataChangedListener listener) {
+
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 926c6c5..c664c99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -47,7 +47,12 @@
}
@Override
- public void setExternalIcon(String slot) {
+ public void setIconFromTile(String slot, StatusBarIcon icon) {
+
+ }
+
+ @Override
+ public void removeIconForTile(String slot) {
}
@@ -57,11 +62,6 @@
}
@Override
- public void setIcon(String slot, StatusBarIcon icon) {
-
- }
-
- @Override
public void setWifiIcon(String slot, WifiIconState state) {
}
@@ -98,10 +98,6 @@
}
@Override
- public void removeAllIconsForExternalSlot(String slot) {
- }
-
- @Override
public void setIconAccessibilityLiveRegion(String slot, int mode) {
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
index 2044f05..380c1fc 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
@@ -53,4 +53,4 @@
}
}
-private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 28e4936..f8f168b 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -15,8 +15,15 @@
*/
package com.android.systemui.unfold.progress
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.content.Context
import android.os.Trace
+import android.util.FloatProperty
import android.util.Log
+import android.view.animation.AnimationUtils.loadInterpolator
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
@@ -34,14 +41,22 @@
import javax.inject.Inject
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
-class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
- private val foldStateProvider: FoldStateProvider
-) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
+class PhysicsBasedUnfoldTransitionProgressProvider
+@Inject
+constructor(context: Context, private val foldStateProvider: FoldStateProvider) :
+ UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
+
+ private val emphasizedInterpolator =
+ loadInterpolator(context, android.R.interpolator.fast_out_extra_slow_in)
+
+ private var cannedAnimator: ValueAnimator? = null
private val springAnimation =
- SpringAnimation(this, AnimationProgressProperty).apply {
- addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
- }
+ SpringAnimation(
+ this,
+ FloatPropertyCompat.createFloatPropertyCompat(AnimationProgressProperty)
+ )
+ .apply { addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) }
private var isTransitionRunning = false
private var isAnimatedCancelRunning = false
@@ -76,7 +91,8 @@
override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) {
- FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> {
+ FOLD_UPDATE_FINISH_FULL_OPEN,
+ FOLD_UPDATE_FINISH_HALF_OPEN -> {
// Do not cancel if we haven't started the transition yet.
// This could happen when we fully unfolded the device before the screen
// became available. In this case we start and immediately cancel the animation
@@ -100,6 +116,14 @@
// the transition continues running.
if (isAnimatedCancelRunning) {
isAnimatedCancelRunning = false
+
+ // Switching to spring animation, start the animation if it
+ // is not running already
+ springAnimation.animateToFinalPosition(1.0f)
+
+ cannedAnimator?.removeAllListeners()
+ cannedAnimator?.cancel()
+ cannedAnimator = null
}
} else {
startTransition(startValue = 1f)
@@ -130,13 +154,22 @@
}
isAnimatedCancelRunning = true
- springAnimation.animateToFinalPosition(endValue)
+
+ if (USE_CANNED_ANIMATION) {
+ startCannedCancelAnimation()
+ } else {
+ springAnimation.animateToFinalPosition(endValue)
+ }
} else {
transitionProgress = endValue
isAnimatedCancelRunning = false
isTransitionRunning = false
springAnimation.cancel()
+ cannedAnimator?.removeAllListeners()
+ cannedAnimator?.cancel()
+ cannedAnimator = null
+
listeners.forEach { it.onTransitionFinished() }
if (DEBUG) {
@@ -157,7 +190,7 @@
}
private fun onStartTransition() {
- Trace.beginSection( "$TAG#onStartTransition")
+ Trace.beginSection("$TAG#onStartTransition")
listeners.forEach { it.onTransitionStarted() }
Trace.endSection()
@@ -195,8 +228,39 @@
listeners.remove(listener)
}
+ private fun startCannedCancelAnimation() {
+ cannedAnimator?.cancel()
+ cannedAnimator = null
+
+ // Temporary remove listener to cancel the spring animation without
+ // finishing the transition
+ springAnimation.removeEndListener(this)
+ springAnimation.cancel()
+ springAnimation.addEndListener(this)
+
+ cannedAnimator =
+ ObjectAnimator.ofFloat(this, AnimationProgressProperty, transitionProgress, 1f).apply {
+ addListener(CannedAnimationListener())
+ duration =
+ (CANNED_ANIMATION_DURATION_MS.toFloat() * (1f - transitionProgress)).toLong()
+ interpolator = emphasizedInterpolator
+ start()
+ }
+ }
+
+ private inner class CannedAnimationListener : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animator: Animator) {
+ Trace.beginAsyncSection("$TAG#cannedAnimatorRunning", 0)
+ }
+
+ override fun onAnimationEnd(animator: Animator) {
+ cancelTransition(1f, animate = false)
+ Trace.endAsyncSection("$TAG#cannedAnimatorRunning", 0)
+ }
+ }
+
private object AnimationProgressProperty :
- FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
+ FloatProperty<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
override fun setValue(
provider: PhysicsBasedUnfoldTransitionProgressProvider,
@@ -205,7 +269,7 @@
provider.transitionProgress = value
}
- override fun getValue(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
+ override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
provider.transitionProgress
}
}
@@ -213,6 +277,8 @@
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true
+private const val USE_CANNED_ANIMATION = true
+private const val CANNED_ANIMATION_DURATION_MS = 1000
private const val SPRING_STIFFNESS = 600.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index d653fc7..a633a5e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,12 +15,14 @@
*/
package com.android.systemui.unfold.updates
+import android.content.Context
import android.os.Handler
import android.os.Trace
import android.util.Log
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
+import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -45,6 +47,7 @@
private val activityTypeProvider: CurrentActivityTypeProvider,
private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider,
private val rotationChangeProvider: RotationChangeProvider,
+ private val context: Context,
@UnfoldMain private val mainExecutor: Executor,
@UnfoldMain private val handler: Handler
) : FoldStateProvider {
@@ -119,7 +122,7 @@
"lastHingeAngle: $lastHingeAngle, " +
"lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
)
- Trace.setCounter( "hinge_angle", angle.toLong())
+ Trace.setCounter("hinge_angle", angle.toLong())
}
val currentDirection =
@@ -136,6 +139,7 @@
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val eventNotAlreadyDispatched = lastFoldUpdate != transitionUpdate
val screenAvailableEventSent = isUnfoldHandled
+ val isOnLargeScreen = isOnLargeScreen()
if (
angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle
@@ -144,7 +148,9 @@
// angle range as closing threshold could overlap this range
screenAvailableEventSent && // do not send transition event if we are still in the
// process of turning on the inner display
- isClosingThresholdMet(angle) // hinge angle is below certain threshold.
+ isClosingThresholdMet(angle) && // hinge angle is below certain threshold.
+ isOnLargeScreen // Avoids sending closing event when on small screen.
+ // Start event is sent regardless due to hall sensor.
) {
notifyFoldUpdate(transitionUpdate, lastHingeAngle)
}
@@ -233,7 +239,7 @@
}
private fun cancelAnimation(): Unit =
- notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
+ notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
@@ -261,6 +267,11 @@
}
}
+ private fun isOnLargeScreen(): Boolean {
+ return context.resources.configuration.smallestScreenWidthDp >
+ INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
+ }
+
/** While the screen is off or the device is folded, hinge angle updates are not needed. */
private fun updateHingeAngleProviderState() {
if (isScreenOn && !isFolded) {
diff --git a/packages/WallpaperBackup/Android.bp b/packages/WallpaperBackup/Android.bp
index d142f25..8acc508 100644
--- a/packages/WallpaperBackup/Android.bp
+++ b/packages/WallpaperBackup/Android.bp
@@ -42,7 +42,7 @@
srcs: [
// Include the app source code because the app runs as the system user on-device.
"src/**/*.java",
- "test/src/**/*.java"
+ "test/src/**/*.java",
],
libs: [
"android.test.base",
@@ -54,7 +54,8 @@
"mockito-target-minus-junit4",
"truth-prebuilt",
],
+ resource_dirs: ["test/res"],
certificate: "platform",
platform_apis: true,
- test_suites: ["device-tests"]
+ test_suites: ["device-tests"],
}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index e549b61..6aca2fd 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -19,11 +19,18 @@
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
+
import android.app.AppGlobals;
import android.app.WallpaperManager;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
import android.app.backup.FullBackupDataOutput;
import android.content.ComponentName;
import android.content.Context;
@@ -103,6 +110,10 @@
private boolean mQuotaExceeded;
private WallpaperManager mWallpaperManager;
+ private WallpaperEventLogger mEventLogger;
+
+ private boolean mSystemHasLiveComponent;
+ private boolean mLockHasLiveComponent;
@Override
public void onCreate() {
@@ -117,6 +128,9 @@
if (DEBUG) {
Slog.v(TAG, "quota file " + mQuotaFile.getPath() + " exists=" + mQuotaExceeded);
}
+
+ BackupManager backupManager = new BackupManager(getApplicationContext());
+ mEventLogger = new WallpaperEventLogger(backupManager, /* wallpaperAgent */ this);
}
@Override
@@ -149,11 +163,18 @@
Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged);
}
+ // Due to the way image vs live wallpaper backup logic is intermingled, for logging
+ // purposes first check if we have live components for each wallpaper to avoid
+ // over-reporting errors.
+ mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
+ mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
+
backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
} catch (Exception e) {
Slog.e(TAG, "Unable to back up wallpaper", e);
+ mEventLogger.onBackupException(e);
} finally {
// Even if this time we had to back off on attempting to store the lock image
// due to exceeding the data quota, try again next time. This will alternate
@@ -170,6 +191,14 @@
if (wallpaperInfoFd == null) {
Slog.w(TAG, "Wallpaper metadata file doesn't exist");
+ // If we have live components, getting the file to back up somehow failed, so log it
+ // as an error.
+ if (mSystemHasLiveComponent) {
+ mEventLogger.onSystemLiveWallpaperBackupFailed(ERROR_NO_METADATA);
+ }
+ if (mLockHasLiveComponent) {
+ mEventLogger.onLockLiveWallpaperBackupFailed(ERROR_NO_METADATA);
+ }
return;
}
@@ -182,12 +211,22 @@
if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
backupFile(infoStage, data);
+
+ // We've backed up the info file which contains the live component, so log it as success
+ if (mSystemHasLiveComponent) {
+ mEventLogger.onSystemLiveWallpaperBackedUp(
+ mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM));
+ }
+ if (mLockHasLiveComponent) {
+ mEventLogger.onLockLiveWallpaperBackedUp(mWallpaperManager.getWallpaperInfo(FLAG_LOCK));
+ }
}
private void backupSystemWallpaperFile(SharedPreferences sharedPrefs,
boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException {
if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) {
Slog.d(TAG, "System wallpaper ineligible for backup");
+ logSystemImageErrorIfNoLiveComponent(ERROR_INELIGIBLE);
return;
}
@@ -197,6 +236,7 @@
if (systemWallpaperImageFd == null) {
Slog.w(TAG, "System wallpaper doesn't exist");
+ logSystemImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
return;
}
@@ -210,8 +250,17 @@
if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
backupFile(imageStage, data);
sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
+ mEventLogger.onSystemImageWallpaperBackedUp();
}
+ private void logSystemImageErrorIfNoLiveComponent(@BackupRestoreError String error) {
+ if (mSystemHasLiveComponent) {
+ return;
+ }
+ mEventLogger.onSystemImageWallpaperBackupFailed(error);
+ }
+
+
private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
@@ -224,11 +273,13 @@
}
Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+ logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
return;
}
if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) {
Slog.d(TAG, "Lock screen wallpaper ineligible for backup");
+ logLockImageErrorIfNoLiveComponent(ERROR_INELIGIBLE);
return;
}
@@ -239,11 +290,13 @@
// set, but we can't find it.
if (lockWallpaperFd == null) {
Slog.w(TAG, "Lock wallpaper doesn't exist");
+ logLockImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER);
return;
}
if (mQuotaExceeded) {
Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time");
+ logLockImageErrorIfNoLiveComponent(ERROR_QUOTA_EXCEEDED);
return;
}
@@ -255,6 +308,14 @@
if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
backupFile(lockImageStage, data);
sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+ mEventLogger.onLockImageWallpaperBackedUp();
+ }
+
+ private void logLockImageErrorIfNoLiveComponent(@BackupRestoreError String error) {
+ if (mLockHasLiveComponent) {
+ return;
+ }
+ mEventLogger.onLockImageWallpaperBackupFailed(error);
}
/**
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
new file mode 100644
index 0000000..64944b3
--- /dev/null
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 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.wallpaperbackup;
+
+import android.annotation.Nullable;
+import android.app.WallpaperInfo;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Log backup / restore related events using {@link BackupRestoreEventLogger}.
+ */
+public class WallpaperEventLogger {
+ /* Static image used as system (or home) screen wallpaper */
+ @BackupRestoreDataType
+ @VisibleForTesting
+ static final String WALLPAPER_IMG_SYSTEM = "wlp_img_system";
+
+ /* Static image used as lock screen wallpaper */
+ @BackupRestoreDataType
+ @VisibleForTesting
+ static final String WALLPAPER_IMG_LOCK = "wlp_img_lock";
+
+ /* Live component used as system (or home) screen wallpaper */
+ @BackupRestoreDataType
+ @VisibleForTesting
+ static final String WALLPAPER_LIVE_SYSTEM = "wlp_live_system";
+
+ /* Live component used as lock screen wallpaper */
+ @BackupRestoreDataType
+ @VisibleForTesting
+ static final String WALLPAPER_LIVE_LOCK = "wlp_live_lock";
+
+ @BackupRestoreError
+ static final String ERROR_INELIGIBLE = "ineligible";
+ @BackupRestoreError
+ static final String ERROR_NO_METADATA = "no_metadata";
+ @BackupRestoreError
+ static final String ERROR_NO_WALLPAPER = "no_wallpaper";
+ @BackupRestoreError
+ static final String ERROR_QUOTA_EXCEEDED = "quota_exceeded";
+
+ private final BackupRestoreEventLogger mLogger;
+
+ private final Set<String> mProcessedDataTypes = new HashSet<>();
+
+ WallpaperEventLogger(BackupManager backupManager, WallpaperBackupAgent wallpaperAgent) {
+ mLogger = backupManager.getBackupRestoreEventLogger(/* backupAgent */ wallpaperAgent);
+ }
+
+ void onSystemImageWallpaperBackedUp() {
+ logBackupSuccessInternal(WALLPAPER_IMG_SYSTEM, /* liveComponentWallpaperInfo */ null);
+ }
+
+ void onLockImageWallpaperBackedUp() {
+ logBackupSuccessInternal(WALLPAPER_IMG_LOCK, /* liveComponentWallpaperInfo */ null);
+ }
+
+ void onSystemLiveWallpaperBackedUp(WallpaperInfo wallpaperInfo) {
+ logBackupSuccessInternal(WALLPAPER_LIVE_SYSTEM, wallpaperInfo);
+ }
+
+ void onLockLiveWallpaperBackedUp(WallpaperInfo wallpaperInfo) {
+ logBackupSuccessInternal(WALLPAPER_LIVE_LOCK, wallpaperInfo);
+ }
+
+ void onSystemImageWallpaperBackupFailed(@BackupRestoreError String error) {
+ logBackupFailureInternal(WALLPAPER_IMG_SYSTEM, error);
+ }
+
+ void onLockImageWallpaperBackupFailed(@BackupRestoreError String error) {
+ logBackupFailureInternal(WALLPAPER_IMG_LOCK, error);
+ }
+
+ void onSystemLiveWallpaperBackupFailed(@BackupRestoreError String error) {
+ logBackupFailureInternal(WALLPAPER_LIVE_SYSTEM, error);
+ }
+
+ void onLockLiveWallpaperBackupFailed(@BackupRestoreError String error) {
+ logBackupFailureInternal(WALLPAPER_LIVE_LOCK, error);
+ }
+
+
+ /**
+ * Called when the whole backup flow is interrupted by an exception.
+ */
+ void onBackupException(Exception exception) {
+ String error = exception.getClass().getName();
+ if (!mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) && !mProcessedDataTypes.contains(
+ WALLPAPER_LIVE_SYSTEM)) {
+ mLogger.logItemsBackupFailed(WALLPAPER_IMG_SYSTEM, /* count */ 1, error);
+ }
+ if (!mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) && !mProcessedDataTypes.contains(
+ WALLPAPER_LIVE_LOCK)) {
+ mLogger.logItemsBackupFailed(WALLPAPER_IMG_LOCK, /* count */ 1, error);
+ }
+ }
+
+ private void logBackupSuccessInternal(@BackupRestoreDataType String which,
+ @Nullable WallpaperInfo liveComponentWallpaperInfo) {
+ mLogger.logItemsBackedUp(which, /* count */ 1);
+ logLiveWallpaperNameIfPresent(which, liveComponentWallpaperInfo);
+ mProcessedDataTypes.add(which);
+ }
+
+ private void logBackupFailureInternal(@BackupRestoreDataType String which,
+ @BackupRestoreError String error) {
+ mLogger.logItemsBackupFailed(which, /* count */ 1, error);
+ mProcessedDataTypes.add(which);
+ }
+
+ private void logLiveWallpaperNameIfPresent(@BackupRestoreDataType String wallpaperType,
+ WallpaperInfo wallpaperInfo) {
+ if (wallpaperInfo != null) {
+ mLogger.logBackupMetadata(wallpaperType, wallpaperInfo.getComponent().getClassName());
+ }
+ }
+}
diff --git a/packages/WallpaperBackup/test/AndroidManifest.xml b/packages/WallpaperBackup/test/AndroidManifest.xml
index 44ab1b6..eb1e98b 100644
--- a/packages/WallpaperBackup/test/AndroidManifest.xml
+++ b/packages/WallpaperBackup/test/AndroidManifest.xml
@@ -4,6 +4,21 @@
<application android:label="WallpaperBackup Tests">
<uses-library android:name="android.test.runner" />
+ <service android:name="com.android.wallpaperbackup.utils.TestWallpaperService"
+ android:enabled="true"
+ android:directBootAware="true"
+ android:label="Test wallpaper"
+ android:permission="android.permission.BIND_WALLPAPER"
+ android:exported="true">
+
+ <intent-filter>
+ <action android:name="android.service.wallpaper.WallpaperService"/>
+ </intent-filter>
+
+ <!-- Link to XML that defines the wallpaper info. -->
+ <meta-data android:name="android.service.wallpaper"
+ android:resource="@xml/livewallpaper"/>
+ </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/WallpaperBackup/test/res/xml/livewallpaper.xml b/packages/WallpaperBackup/test/res/xml/livewallpaper.xml
new file mode 100644
index 0000000..c6fbe2bda
--- /dev/null
+++ b/packages/WallpaperBackup/test/res/xml/livewallpaper.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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
+ -->
+<wallpaper/>
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 20dd5165..89459f6 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -23,22 +23,40 @@
import static com.android.wallpaperbackup.WallpaperBackupAgent.LOCK_WALLPAPER_STAGE;
import static com.android.wallpaperbackup.WallpaperBackupAgent.SYSTEM_WALLPAPER_STAGE;
import static com.android.wallpaperbackup.WallpaperBackupAgent.WALLPAPER_INFO_STAGE;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.WallpaperInfo;
import android.app.WallpaperManager;
+import android.app.backup.BackupAnnotations;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.app.backup.FullBackupDataOutput;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.service.wallpaper.WallpaperService;
+import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
@@ -69,12 +87,18 @@
private static final int TEST_SYSTEM_WALLPAPER_ID = 1;
private static final int TEST_LOCK_WALLPAPER_ID = 2;
private static final int NO_LOCK_WALLPAPER_ID = -1;
+ // An arbitrary user.
+ private static final UserHandle USER_HANDLE = new UserHandle(15);
- @Mock private FullBackupDataOutput mOutput;
- @Mock private WallpaperManager mWallpaperManager;
- @Mock private Context mMockContext;
+ @Mock
+ private FullBackupDataOutput mOutput;
+ @Mock
+ private WallpaperManager mWallpaperManager;
+ @Mock
+ private Context mMockContext;
- @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private ContextWithServiceOverrides mContext;
private IsolatedWallpaperBackupAgent mWallpaperBackupAgent;
@@ -90,9 +114,10 @@
mContext = new ContextWithServiceOverrides(ApplicationProvider.getApplicationContext());
mContext.injectSystemService(WallpaperManager.class, mWallpaperManager);
- mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
+ mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent();
mWallpaperBackupAgent.attach(mContext);
- mWallpaperBackupAgent.onCreate();
+ mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+ BackupAnnotations.OperationType.BACKUP);
mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
}
@@ -388,6 +413,185 @@
verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
}
+ @Test
+ public void testOnFullBackup_systemWallpaperImgSuccess_logsSuccess() throws Exception {
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testOnFullBackup_systemWallpaperImgIneligible_logsFailure() throws Exception {
+ when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(false);
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getFailCount()).isEqualTo(1);
+ assertThat(result.getErrors()).containsKey(ERROR_INELIGIBLE);
+ }
+
+ @Test
+ public void testOnFullBackup_systemWallpaperImgMissing_logsFailure() throws Exception {
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getFailCount()).isEqualTo(1);
+ assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+ }
+
+ @Test
+ public void testOnFullBackup_systemWallpaperImgMissingButHasLiveComponent_logsLiveSuccess()
+ throws Exception {
+ mockWallpaperInfoFileWithContents("info file");
+ when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo());
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_LIVE_SYSTEM,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
+ assertThat(result.getMetadataHash()).isNotNull();
+ }
+
+ @Test
+ public void testOnFullBackup_systemWallpaperImgMissingButHasLiveComponent_logsNothingForImg()
+ throws Exception {
+ mockWallpaperInfoFileWithContents("info file");
+ when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo());
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testOnFullBackup_lockWallpaperImgSuccess_logsSuccess() throws Exception {
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testOnFullBackup_lockWallpaperImgIneligible_logsFailure() throws Exception {
+ when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(false);
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getFailCount()).isEqualTo(1);
+ assertThat(result.getErrors()).containsKey(ERROR_INELIGIBLE);
+ }
+
+ @Test
+ public void testOnFullBackup_lockWallpaperImgMissing_logsFailure() throws Exception {
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getFailCount()).isEqualTo(1);
+ assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+ }
+
+ @Test
+ public void testOnFullBackup_lockWallpaperImgMissingButHasLiveComponent_logsLiveSuccess()
+ throws Exception {
+ mockWallpaperInfoFileWithContents("info file");
+ when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo());
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_LIVE_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
+ assertThat(result.getMetadataHash()).isNotNull();
+ }
+
+ @Test
+ public void testOnFullBackup_lockWallpaperImgMissingButHasLiveComponent_logsNothingForImg()
+ throws Exception {
+ mockWallpaperInfoFileWithContents("info file");
+ when(mWallpaperManager.getWallpaperInfo(anyInt())).thenReturn(getFakeWallpaperInfo());
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNull();
+ }
+
+
+ @Test
+ public void testOnFullBackup_exceptionThrown_logsException() throws Exception {
+ when(mWallpaperManager.isWallpaperBackupEligible(anyInt())).thenThrow(
+ new RuntimeException());
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getFailCount()).isEqualTo(1);
+ assertThat(result.getErrors()).containsKey(RuntimeException.class.getName());
+ }
+
+ @Test
+ public void testOnFullBackup_lastBackupOverQuota_logsLockFailure() throws Exception {
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ markAgentAsOverQuota();
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getFailCount()).isEqualTo(1);
+ assertThat(result.getErrors()).containsKey(ERROR_QUOTA_EXCEEDED);
+ }
+
+ @Test
+ public void testOnFullBackup_lastBackupOverQuota_logsSystemSuccess() throws Exception {
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ markAgentAsOverQuota();
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
+ }
+
private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) {
when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId);
when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId);
@@ -432,16 +636,41 @@
ParcelFileDescriptor.open(fakeLockWallpaperFile, MODE_READ_ONLY));
}
+ private WallpaperInfo getFakeWallpaperInfo() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+ intent.setPackage("com.android.wallpaperbackup.tests");
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+ assertEquals(1, result.size());
+ ResolveInfo info = result.get(0);
+ return new WallpaperInfo(context, info);
+ }
+
+ private void markAgentAsOverQuota() throws Exception {
+ // Create over quota file to indicate the last backup was over quota
+ File quotaFile = new File(mContext.getFilesDir(), WallpaperBackupAgent.QUOTA_SENTINEL);
+ quotaFile.createNewFile();
+
+ // Now redo the setup of the agent to pick up the over quota
+ mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+ BackupAnnotations.OperationType.BACKUP);
+ }
+
+ private static DataTypeResult getLoggingResult(String dataType, List<DataTypeResult> results) {
+ for (DataTypeResult result : results) {
+ if ((result.getDataType()).equals(dataType)) {
+ return result;
+ }
+ }
+ return null;
+ }
+
private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
- File mWallpaperBaseDirectory;
List<File> mBackedUpFiles = new ArrayList<>();
PackageMonitor mWallpaperPackageMonitor;
boolean mIsDeviceInRestore = false;
- IsolatedWallpaperBackupAgent(File wallpaperBaseDirectory) {
- mWallpaperBaseDirectory = wallpaperBaseDirectory;
- }
-
@Override
protected void backupFile(File file, FullBackupDataOutput data) {
mBackedUpFiles.add(file);
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
new file mode 100644
index 0000000..3816a3c
--- /dev/null
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 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.wallpaperbackup;
+
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYSTEM;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
+import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.WallpaperInfo;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.service.wallpaper.WallpaperService;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.wallpaperbackup.utils.TestWallpaperService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class WallpaperEventLoggerTest {
+
+ @Mock
+ private BackupRestoreEventLogger mMockLogger;
+
+ @Mock
+ private BackupManager mMockBackupManager;
+
+ @Mock
+ private WallpaperBackupAgent mMockBackupAgent;
+
+ private static final String WALLPAPER_ERROR = "some_error";
+
+ private WallpaperEventLogger mWallpaperEventLogger;
+ private WallpaperInfo mWallpaperInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mMockLogger);
+ when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mMockLogger);
+
+ mWallpaperInfo = getWallpaperInfo();
+ mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent);
+ }
+
+ @Test
+ public void onSystemImgWallpaperBackedUp_logsSuccess() {
+ mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+
+ verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_SYSTEM), eq(1));
+ }
+
+ @Test
+ public void onLockImgWallpaperBackedUp_logsSuccess() {
+ mWallpaperEventLogger.onLockImageWallpaperBackedUp();
+
+ verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_LOCK), eq(1));
+ }
+
+ @Test
+ public void onSystemLiveWallpaperBackedUp_logsSuccess() {
+ mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
+
+ verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_SYSTEM), eq(1));
+ }
+
+ @Test
+ public void onLockLiveWallpaperBackedUp_logsSuccess() {
+ mWallpaperEventLogger.onLockLiveWallpaperBackedUp(mWallpaperInfo);
+
+ verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_LOCK), eq(1));
+ }
+
+ @Test
+ public void onImgWallpaperBackedUp_nullInfo_doesNotLogMetadata() {
+ mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+
+ verify(mMockLogger, never()).logBackupMetadata(eq(WALLPAPER_IMG_SYSTEM), anyString());
+ }
+
+
+ @Test
+ public void onLiveWallpaperBackedUp_logsMetadata() {
+ mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
+
+ verify(mMockLogger).logBackupMetadata(eq(WALLPAPER_LIVE_SYSTEM),
+ eq(TestWallpaperService.class.getName()));
+ }
+
+
+ @Test
+ public void onSystemImgWallpaperBackupFailed_logsFail() {
+ mWallpaperEventLogger.onSystemImageWallpaperBackupFailed(WALLPAPER_ERROR);
+
+ verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), eq(1),
+ eq(WALLPAPER_ERROR));
+ }
+
+ @Test
+ public void onLockImgWallpaperBackupFailed_logsFail() {
+ mWallpaperEventLogger.onLockImageWallpaperBackupFailed(WALLPAPER_ERROR);
+
+ verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1),
+ eq(WALLPAPER_ERROR));
+ }
+
+
+ @Test
+ public void onSystemLiveWallpaperBackupFailed_logsFail() {
+ mWallpaperEventLogger.onSystemLiveWallpaperBackupFailed(WALLPAPER_ERROR);
+
+ verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_SYSTEM), eq(1),
+ eq(WALLPAPER_ERROR));
+ }
+
+ @Test
+ public void onLockLiveWallpaperBackupFailed_logsFail() {
+ mWallpaperEventLogger.onLockLiveWallpaperBackupFailed(WALLPAPER_ERROR);
+
+ verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_LOCK), eq(1),
+ eq(WALLPAPER_ERROR));
+ }
+
+
+ @Test
+ public void onWallpaperBackupException_someProcessed_doesNotLogErrorForProcessedType() {
+ mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+
+ mWallpaperEventLogger.onBackupException(new Exception());
+
+ verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(),
+ anyString());
+ }
+
+
+ @Test
+ public void onWallpaperBackupException_someProcessed_logsErrorForUnprocessedType() {
+ mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+
+ mWallpaperEventLogger.onBackupException(new Exception());
+
+ verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1),
+ eq(Exception.class.getName()));
+
+ }
+
+ @Test
+ public void onWallpaperBackupException_liveTypeProcessed_doesNotLogErrorForSameImgType() {
+ mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
+
+ mWallpaperEventLogger.onBackupException(new Exception());
+
+ verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(),
+ anyString());
+ }
+
+ private WallpaperInfo getWallpaperInfo() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+ intent.setPackage("com.android.wallpaperbackup.tests");
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+ assertEquals(1, result.size());
+ ResolveInfo info = result.get(0);
+ return new WallpaperInfo(context, info);
+ }
+}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/utils/TestWallpaperService.java
similarity index 60%
rename from tests/componentalias/src/android/content/componentalias/tests/s/Target04.java
rename to packages/WallpaperBackup/test/src/com/android/wallpaperbackup/utils/TestWallpaperService.java
index 0994258..cb85041 100644
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/utils/TestWallpaperService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -13,7 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.content.componentalias.tests.s;
-public class Target04 extends BaseService {
+package com.android.wallpaperbackup.utils;
+
+import android.service.wallpaper.WallpaperService;
+
+/**
+ * Empty wallpaper service used for wallpaper backup tests
+ */
+public class TestWallpaperService extends WallpaperService {
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index ee806a6..1298f63 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -2076,6 +2076,7 @@
ret.outputId.id = output.getId();
ret.physicalCameraId = output.getPhysicalCameraId();
ret.surfaceGroupId = output.getSurfaceGroupId();
+ ret.isMultiResolutionOutput = false;
if (output instanceof SurfaceOutputConfigImpl) {
SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output;
ret.type = CameraOutputConfig.TYPE_SURFACE;
@@ -2095,6 +2096,7 @@
ret.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER;
ret.imageFormat = multiResReaderConfig.getImageFormat();
ret.capacity = multiResReaderConfig.getMaxImages();
+ ret.isMultiResolutionOutput = true;
} else {
throw new IllegalStateException("Unknown output config type!");
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0edb8f2..e5c9be0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3884,6 +3884,7 @@
}
}
+ @Override
public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -3907,6 +3908,7 @@
}
}
+ @Override
public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) {
// The accessibility service is allowed. Don't show the restricted dialog.
if (isAccessibilityTargetAllowed(packageName, uid, userId)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
index fe7b11f..5a783f4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
@@ -63,7 +63,7 @@
private final WindowManager.LayoutParams mBackgroundParams;
private boolean mVisible = false;
- private static final float ASPECT_RATIO = 28f;
+ private static final float ASPECT_RATIO = 14f;
private static final float BG_ASPECT_RATIO = ASPECT_RATIO / 2f;
private ObjectAnimator mThumbNailAnimator;
@@ -261,9 +261,10 @@
mThumbNailView.setScaleY(scaleDown);
}
if (!Float.isNaN(centerX)) {
+ var padding = mThumbNailView.getPaddingTop();
var ratio = 1f / BG_ASPECT_RATIO;
- var centerXScaled = centerX * ratio - mThumbNailView.getWidth() / 2f;
- var centerYScaled = centerY * ratio - mThumbNailView.getHeight() / 2f;
+ var centerXScaled = centerX * ratio - (mThumbNailView.getWidth() / 2f + padding);
+ var centerYScaled = centerY * ratio - (mThumbNailView.getHeight() / 2f + padding);
if (DEBUG) {
Log.d(
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 01386da..af5b196 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -383,7 +383,7 @@
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service == null) {
// If we cannot get the service from the services cache, it will call
- // updateRemoteAugmentedAutofillService() finally. Skip call this update again.
+ // updateRemoteFieldClassificationService() finally. Skip call this update again.
getServiceForUserLocked(userId);
} else {
service.updateRemoteFieldClassificationService();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index bea049c..d5dcdaf 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -1731,7 +1731,7 @@
}
/**
- * Called when the {@link AutofillManagerService#mAugmentedAutofillResolver}
+ * Called when the {@link AutofillManagerService#mFieldClassificationResolver}
* changed (among other places).
*/
void updateRemoteFieldClassificationService() {
@@ -1742,7 +1742,6 @@
+ "destroying old remote service");
}
mRemoteFieldClassificationService.unbind();
-
mRemoteFieldClassificationService = null;
mRemoteFieldClassificationServiceInfo = null;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index b4e636e..62a2970 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -327,13 +327,11 @@
private int setTemporaryDetectionService(PrintWriter pw) {
final int userId = getNextIntArgRequired();
final String serviceName = getNextArg();
- final int duration = getNextIntArgRequired();
-
if (serviceName == null) {
mService.resetTemporaryDetectionService(userId);
return 0;
}
-
+ final int duration = getNextIntArgRequired();
if (duration <= 0) {
mService.resetTemporaryDetectionService(userId);
return 0;
diff --git a/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java b/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java
new file mode 100644
index 0000000..3b30af6
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2022 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.server.autofill;
+
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_EXPLICITLY_REQUESTED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_NORMAL_TRIGGER;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_PRE_TRIGGER;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_RETRIGGER;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_UNKNOWN;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.IntDef;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Optional;
+
+/**
+ * Helper class to log Autofill FillRequest filing stats.
+ */
+public final class FillRequestEventLogger {
+ private static final String TAG = "FillRequestEventLogger";
+
+ /**
+ * Reasons why presentation was not shown. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillFillRequestReported.RequestTriggerReason}.
+ */
+ @IntDef(prefix = {"TRIGGER_REASON"}, value = {
+ TRIGGER_REASON_UNKNOWN,
+ TRIGGER_REASON_EXPLICITLY_REQUESTED,
+ TRIGGER_REASON_RETRIGGER,
+ TRIGGER_REASON_PRE_TRIGGER,
+ TRIGGER_REASON_NORMAL_TRIGGER,
+ TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TriggerReason {
+ }
+
+ public static final int TRIGGER_REASON_UNKNOWN =
+ AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_UNKNOWN;
+ public static final int TRIGGER_REASON_EXPLICITLY_REQUESTED =
+ AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_EXPLICITLY_REQUESTED;
+ public static final int TRIGGER_REASON_RETRIGGER =
+ AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_RETRIGGER;
+ public static final int TRIGGER_REASON_PRE_TRIGGER =
+ AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_PRE_TRIGGER;
+ public static final int TRIGGER_REASON_NORMAL_TRIGGER =
+ AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_NORMAL_TRIGGER;
+ public static final int TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE =
+ AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
+
+ private final int mSessionId;
+ private Optional<FillRequestEventInternal> mEventInternal;
+
+ private FillRequestEventLogger(int sessionId) {
+ mSessionId = sessionId;
+ mEventInternal = Optional.empty();
+ }
+
+ /**
+ * A factory constructor to create FillRequestEventLogger.
+ */
+ public static FillRequestEventLogger forSessionId(int sessionId) {
+ return new FillRequestEventLogger(sessionId);
+ }
+ /**
+ * Reset mEventInternal before logging for a new request. It shall be called for each
+ * FillRequest.
+ */
+ public void startLogForNewRequest() {
+ if (!mEventInternal.isEmpty()) {
+ Slog.w(TAG, "FillRequestEventLogger is not empty before starting for a new " +
+ "request");
+ }
+ mEventInternal = Optional.of(new FillRequestEventInternal());
+ }
+
+ /**
+ * Set request_id as long as mEventInternal presents.
+ */
+ public void maybeSetRequestId(int requestId) {
+ mEventInternal.ifPresent(event -> event.mRequestId = requestId);
+ }
+
+ /**
+ * Set service_uid as long as mEventInternal presents.
+ */
+ public void maybeSetAutofillServiceUid(int uid) {
+ mEventInternal.ifPresent(event -> {
+ event.mAutofillServiceUid = uid;
+ });
+ }
+
+ /**
+ * Set inline_suggestion_host_uid as long as mEventInternal presents.
+ */
+ public void maybeSetInlineSuggestionHostUid(Context context, int userId) {
+ mEventInternal.ifPresent(event -> {
+ String imeString = Settings.Secure.getStringForUser(context.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD, userId);
+ if (TextUtils.isEmpty(imeString)) {
+ Slog.w(TAG, "No default IME found");
+ return;
+ }
+ ComponentName imeComponent = ComponentName.unflattenFromString(imeString);
+ if (imeComponent == null) {
+ Slog.w(TAG, "No default IME found");
+ return;
+ }
+ int imeUid;
+ String packageName = imeComponent.getPackageName();
+ try {
+ imeUid = context.getPackageManager().getApplicationInfoAsUser(packageName,
+ PackageManager.ApplicationInfoFlags.of(0), userId).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Couldn't find packageName: " + packageName);
+ return;
+ }
+ event.mInlineSuggestionHostUid = imeUid;
+ });
+ }
+
+
+ /**
+ * Set flags as long as mEventInternal presents.
+ */
+ public void maybeSetFlags(int flags) {
+ mEventInternal.ifPresent(event -> {
+ event.mFlags = flags;
+ });
+ }
+
+ /**
+ * Set request_trigger_reason as long as mEventInternal presents.
+ */
+ public void maybeSetRequestTriggerReason(@TriggerReason int reason) {
+ mEventInternal.ifPresent(event -> {
+ event.mRequestTriggerReason = reason;
+ });
+ }
+
+ /**
+ * Set is_augmented as long as mEventInternal presents.
+ */
+ public void maybeSetIsAugmented(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mIsAugmented = val;
+ });
+ }
+
+ /**
+ * Set is_client_suggestion as long as mEventInternal presents.
+ */
+ public void maybeSetIsClientSuggestionFallback(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mIsClientSuggestionFallback = val;
+ });
+ }
+
+ /**
+ * Set is_fill_dialog_eligible as long as mEventInternal presents.
+ */
+ public void maybeSetIsFillDialogEligible(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mIsFillDialogEligible = val;
+ });
+ }
+
+ /**
+ * Set latency_fill_request_sent_millis as long as mEventInternal presents.
+ */
+ public void maybeSetLatencyFillRequestSentMillis(int timestamp) {
+ mEventInternal.ifPresent(event -> {
+ event.mLatencyFillRequestSentMillis = timestamp;
+ });
+ }
+
+ /**
+ * Set app_package_uid as long as mEventInternal presents.
+ */
+ public void maybeSetAppPackageUid(int uid) {
+ mEventInternal.ifPresent(event -> {
+ event.mAppPackageUid = uid;
+ });
+ }
+
+ /**
+ * Log an AUTOFILL_FILL_REQUEST_REPORTED event.
+ */
+ public void logAndEndEvent() {
+ if (!mEventInternal.isPresent()) {
+ Slog.w(TAG, "Shouldn't be logging AutofillFillRequestReported again for same "
+ + "event");
+ return;
+ }
+ FillRequestEventInternal event = mEventInternal.get();
+ if (sVerbose) {
+ Slog.v(TAG, "Log AutofillFillRequestReported:"
+ + " requestId=" + event.mRequestId
+ + " sessionId=" + mSessionId
+ + " mAutofillServiceUid=" + event.mAutofillServiceUid
+ + " mInlineSuggestionHostUid=" + event.mInlineSuggestionHostUid
+ + " mIsAugmented=" + event.mIsAugmented
+ + " mIsClientSuggestionFallback=" + event.mIsClientSuggestionFallback
+ + " mIsFillDialogEligible=" + event.mIsFillDialogEligible
+ + " mRequestTriggerReason=" + event.mRequestTriggerReason
+ + " mFlags=" + event.mFlags
+ + " mLatencyFillRequestSentMillis=" + event.mLatencyFillRequestSentMillis
+ + " mAppPackageUid=" + event.mAppPackageUid);
+ }
+ FrameworkStatsLog.write(
+ AUTOFILL_FILL_REQUEST_REPORTED,
+ event.mRequestId,
+ mSessionId,
+ event.mAutofillServiceUid,
+ event.mInlineSuggestionHostUid,
+ event.mIsAugmented,
+ event.mIsClientSuggestionFallback,
+ event.mIsFillDialogEligible,
+ event.mRequestTriggerReason,
+ event.mFlags,
+ event.mLatencyFillRequestSentMillis,
+ event.mAppPackageUid);
+ mEventInternal = Optional.empty();
+ }
+
+ private static final class FillRequestEventInternal {
+ int mRequestId;
+ int mAppPackageUid = -1;
+ int mAutofillServiceUid = -1;
+ int mInlineSuggestionHostUid = -1;
+ boolean mIsAugmented = false;
+ boolean mIsClientSuggestionFallback = false;
+ boolean mIsFillDialogEligible = false;
+ int mRequestTriggerReason =
+ AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_UNKNOWN;
+ int mFlags = -1;
+ int mLatencyFillRequestSentMillis = -1;
+
+ FillRequestEventInternal() {
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 590f472..ca743cb 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -26,6 +26,12 @@
import static android.view.autofill.AutofillManager.COMMIT_REASON_VIEW_COMMITTED;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
@@ -38,6 +44,7 @@
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED;
import static com.android.server.autofill.Helper.sVerbose;
@@ -71,6 +78,7 @@
@IntDef(prefix = {"NOT_SHOWN_REASON"}, value = {
NOT_SHOWN_REASON_ANY_SHOWN,
NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED,
+ NOT_SHOWN_REASON_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE,
NOT_SHOWN_REASON_VIEW_CHANGED,
NOT_SHOWN_REASON_ACTIVITY_FINISHED,
NOT_SHOWN_REASON_REQUEST_TIMEOUT,
@@ -82,10 +90,38 @@
@Retention(RetentionPolicy.SOURCE)
public @interface NotShownReason {}
+ /**
+ * Reasons why presentation was not shown. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationType}.
+ */
+ @IntDef(prefix = {"AUTHENTICATION_TYPE"}, value = {
+ AUTHENTICATION_TYPE_UNKNOWN,
+ AUTHENTICATION_TYPE_DATASET_AUTHENTICATION,
+ AUTHENTICATION_TYPE_FULL_AUTHENTICATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AuthenticationType {
+ }
+
+ /**
+ * Reasons why presentation was not shown. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.AuthenticationResult}.
+ */
+ @IntDef(prefix = {"AUTHENTICATION_RESULT"}, value = {
+ AUTHENTICATION_RESULT_UNKNOWN,
+ AUTHENTICATION_RESULT_SUCCESS,
+ AUTHENTICATION_RESULT_FAILURE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AuthenticationResult {
+ }
+
public static final int NOT_SHOWN_REASON_ANY_SHOWN =
AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED =
AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED;
+ public static final int NOT_SHOWN_REASON_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE;
public static final int NOT_SHOWN_REASON_VIEW_CHANGED =
AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED;
public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED =
@@ -101,6 +137,20 @@
public static final int NOT_SHOWN_REASON_UNKNOWN =
AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON;
+ public static final int AUTHENTICATION_TYPE_UNKNOWN =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
+ public static final int AUTHENTICATION_TYPE_DATASET_AUTHENTICATION =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
+ public static final int AUTHENTICATION_TYPE_FULL_AUTHENTICATION =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
+
+ public static final int AUTHENTICATION_RESULT_UNKNOWN =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
+ public static final int AUTHENTICATION_RESULT_SUCCESS =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
+ public static final int AUTHENTICATION_RESULT_FAILURE =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+
private final int mSessionId;
private Optional<PresentationStatsEventInternal> mEventInternal;
@@ -225,10 +275,34 @@
});
}
+ public void maybeSetSelectedDatasetId(int selectedDatasetId) {
+ mEventInternal.ifPresent(event -> {
+ event.mSelectedDatasetId = selectedDatasetId;
+ });
+ }
+
+ public void maybeSetDialogDismissed(boolean dialogDismissed) {
+ mEventInternal.ifPresent(event -> {
+ event.mDialogDismissed = dialogDismissed;
+ });
+ }
+
+ public void maybeSetNegativeCtaButtonClicked(boolean negativeCtaButtonClicked) {
+ mEventInternal.ifPresent(event -> {
+ event.mNegativeCtaButtonClicked = negativeCtaButtonClicked;
+ });
+ }
+
+ public void maybeSetPositiveCtaButtonClicked(boolean positiveCtaButtonClicked) {
+ mEventInternal.ifPresent(event -> {
+ event.mPositiveCtaButtonClicked = positiveCtaButtonClicked;
+ });
+ }
+
public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) {
mEventInternal.ifPresent(event -> {
event.mDisplayPresentationType =
- AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
String imeString = Settings.Secure.getStringForUser(context.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD, userId);
if (TextUtils.isEmpty(imeString)) {
@@ -265,6 +339,43 @@
});
}
+ /**
+ * Set authentication_type as long as mEventInternal presents.
+ */
+ public void maybeSetAuthenticationType(@AuthenticationType int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mAuthenticationType = val;
+ });
+ }
+
+ /**
+ * Set authentication_result as long as mEventInternal presents.
+ */
+ public void maybeSetAuthenticationResult(@AuthenticationResult int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mAuthenticationResult = val;
+ });
+ }
+
+ /**
+ * Set latency_authentication_ui_display_millis as long as mEventInternal presents.
+ */
+ public void maybeSetLatencyAuthenticationUiDisplayMillis(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mLatencyAuthenticationUiDisplayMillis = val;
+ });
+ }
+
+ /**
+ * Set latency_dataset_display_millis as long as mEventInternal presents.
+ */
+ public void maybeSetLatencyDatasetDisplayMillis(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mLatencyDatasetDisplayMillis = val;
+ });
+ }
+
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
@@ -290,7 +401,16 @@
+ " mFillRequestSentTimestampMs=" + event.mFillRequestSentTimestampMs
+ " mFillResponseReceivedTimestampMs=" + event.mFillResponseReceivedTimestampMs
+ " mSuggestionSentTimestampMs=" + event.mSuggestionSentTimestampMs
- + " mSuggestionPresentedTimestampMs=" + event.mSuggestionPresentedTimestampMs);
+ + " mSuggestionPresentedTimestampMs=" + event.mSuggestionPresentedTimestampMs
+ + " mSelectedDatasetId=" + event.mSelectedDatasetId
+ + " mDialogDismissed=" + event.mDialogDismissed
+ + " mNegativeCtaButtonClicked=" + event.mNegativeCtaButtonClicked
+ + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked
+ + " mAuthenticationType=" + event.mAuthenticationType
+ + " mAuthenticationResult=" + event.mAuthenticationResult
+ + " mLatencyAuthenticationUiDisplayMillis="
+ + event.mLatencyAuthenticationUiDisplayMillis
+ + " mLatencyDatasetDisplayMillis=" + event.mLatencyDatasetDisplayMillis);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -316,15 +436,18 @@
event.mFillResponseReceivedTimestampMs,
event.mSuggestionSentTimestampMs,
event.mSuggestionPresentedTimestampMs,
- //TODO(b/265051751): add new framework logging.
- /* selected_dataset_id= */ 0,
- /* dialog_dismissed= */ false,
- /* negative_cta_button_clicked= */ false,
- /* positive_cta_button_clicked= */ false);
+ event.mSelectedDatasetId,
+ event.mDialogDismissed,
+ event.mNegativeCtaButtonClicked,
+ event.mPositiveCtaButtonClicked,
+ event.mAuthenticationType,
+ event.mAuthenticationResult,
+ event.mLatencyAuthenticationUiDisplayMillis,
+ event.mLatencyDatasetDisplayMillis);
mEventInternal = Optional.empty();
}
- private final class PresentationStatsEventInternal {
+ private static final class PresentationStatsEventInternal {
int mRequestId;
@NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN;
boolean mIsDatasetAvailable;
@@ -341,6 +464,14 @@
int mFillResponseReceivedTimestampMs;
int mSuggestionSentTimestampMs;
int mSuggestionPresentedTimestampMs;
+ int mSelectedDatasetId = -1;
+ boolean mDialogDismissed = false;
+ boolean mNegativeCtaButtonClicked = false;
+ boolean mPositiveCtaButtonClicked = false;
+ int mAuthenticationType = AUTHENTICATION_TYPE_UNKNOWN;
+ int mAuthenticationResult = AUTHENTICATION_RESULT_UNKNOWN;
+ int mLatencyAuthenticationUiDisplayMillis = -1;
+ int mLatencyDatasetDisplayMillis = -1;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 15a533c..4acdabe 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -41,6 +41,9 @@
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER;
+import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_PRE_TRIGGER;
+import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
import static com.android.server.autofill.Helper.containsCharsInOrder;
import static com.android.server.autofill.Helper.createSanitizers;
import static com.android.server.autofill.Helper.getNumericValue;
@@ -439,6 +442,10 @@
@GuardedBy("mLock")
private PresentationStatsEventLogger mPresentationStatsEventLogger;
+ @NonNull
+ @GuardedBy("mLock")
+ private FillRequestEventLogger mFillRequestEventLogger;
+
/**
* Fill dialog request would likely be sent slightly later.
*/
@@ -605,6 +612,14 @@
mPendingInlineSuggestionsRequest = null;
mWaitForInlineRequest = false;
mPendingFillRequest = null;
+
+ final long fillRequestSentRelativeTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mPresentationStatsEventLogger.maybeSetFillRequestSentTimestampMs(
+ (int) (fillRequestSentRelativeTimestamp));
+ mFillRequestEventLogger.maybeSetLatencyFillRequestSentMillis(
+ (int) (fillRequestSentRelativeTimestamp));
+ mFillRequestEventLogger.logAndEndEvent();
}
@Override
@@ -1082,11 +1097,21 @@
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
int flags) {
final FillResponse existingResponse = viewState.getResponse();
+ mFillRequestEventLogger.startLogForNewRequest();
+ mFillRequestEventLogger.maybeSetAppPackageUid(uid);
+ mFillRequestEventLogger.maybeSetFlags(mFlags);
+ if(mPreviouslyFillDialogPotentiallyStarted) {
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER);
+ } else {
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER);
+ }
if (existingResponse != null) {
setViewStatesLocked(
existingResponse,
ViewState.STATE_INITIAL,
/* clearResponse= */ true);
+ mFillRequestEventLogger.maybeSetRequestTriggerReason(
+ TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE);
}
mSessionFlags.mExpiredResponse = false;
mSessionState = STATE_ACTIVE;
@@ -1097,6 +1122,10 @@
+ ", flags=" + flags + ")");
}
mSessionFlags.mAugmentedAutofillOnly = true;
+ // Augmented autofill doesn't have request_id.
+ mFillRequestEventLogger.maybeSetRequestId(-1);
+ mFillRequestEventLogger.maybeSetIsAugmented(mSessionFlags.mAugmentedAutofillOnly);
+ mFillRequestEventLogger.logAndEndEvent();
triggerAugmentedAutofillLocked(flags);
return;
}
@@ -1123,6 +1152,12 @@
+ ", flags=" + flags);
}
mPresentationStatsEventLogger.maybeSetRequestId(requestId);
+ mFillRequestEventLogger.maybeSetRequestId(requestId);
+ mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
+ if (mSessionFlags.mInlineSupportedByService) {
+ mFillRequestEventLogger.maybeSetInlineSuggestionHostUid(mContext, userId);
+ }
+ mFillRequestEventLogger.maybeSetIsFillDialogEligible(!mSessionFlags.mFillDialogDisabled);
// If the focus changes very quickly before the first request is returned each focus change
// triggers a new partition and we end up with many duplicate partitions. This is
@@ -1189,11 +1224,6 @@
return;
}
- final long fillRequestSentRelativeTimestamp =
- SystemClock.elapsedRealtime() - mLatencyBaseTime;
- mPresentationStatsEventLogger.maybeSetFillRequestSentTimestampMs(
- (int) (fillRequestSentRelativeTimestamp));
-
// Now request the assist structure data.
requestAssistStructureLocked(requestId, flags);
}
@@ -1284,6 +1314,7 @@
mCompatMode = compatMode;
mSessionState = STATE_ACTIVE;
mPresentationStatsEventLogger = PresentationStatsEventLogger.forSessionId(sessionId);
+ mFillRequestEventLogger = FillRequestEventLogger.forSessionId(sessionId);
synchronized (mLock) {
mSessionFlags = new SessionFlags();
mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly;
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 742c94a..d6f1348 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -343,10 +343,8 @@
// Check if user is associated with the subscription
if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
Binder.getCallingUserHandle())) {
- if (TelephonyUtils.isUidForeground(mContext, Binder.getCallingUid())) {
- TelephonyUtils.showErrorIfSubscriptionAssociatedWithManagedProfile(mContext,
- subId);
- }
+ TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext,
+ subId, Binder.getCallingUid(), callingPkg);
return;
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 225afea..a60f06a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4945,10 +4945,6 @@
if (intent.getClipData() == null) {
intent.setClipData(ClipData.newPlainText(null, null));
}
- intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
- | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION));
final long bid = Binder.clearCallingIdentity();
try {
PackageManager pm = mContext.getPackageManager();
@@ -4994,7 +4990,19 @@
if (intent == null) {
return (simulateIntent == null);
}
- return intent.filterEquals(simulateIntent);
+ if (!intent.filterEquals(simulateIntent)) {
+ return false;
+ }
+
+ if (intent.getSelector() != simulateIntent.getSelector()) {
+ return false;
+ }
+
+ int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+ return (simulateIntent.getFlags() & prohibitedFlags) == 0;
}
private boolean isExportedSystemActivity(ActivityInfo activityInfo) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4a0a228..461103e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -602,7 +602,7 @@
try {
final ServiceRecord.StartItem si = r.pendingStarts.get(0);
startServiceInnerLocked(this, si.intent, r, false, true, si.callingId,
- si.mCallingProcessName, r.startRequested);
+ si.mCallingProcessName, r.startRequested, si.mCallingPackageName);
} catch (TransactionTooLargeException e) {
// Ignore, nobody upstack cares.
}
@@ -969,7 +969,7 @@
startServiceInnerLocked(r, service, callingUid, callingPid,
getCallingProcessNameLocked(callingUid, callingPid, callingPackage),
fgRequired, callerFg,
- backgroundStartPrivileges);
+ backgroundStartPrivileges, callingPackage);
if (res.aliasComponent != null
&& !realResult.getPackageName().startsWith("!")
&& !realResult.getPackageName().startsWith("?")) {
@@ -990,7 +990,7 @@
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
int callingUid, int callingPid, String callingProcessName, boolean fgRequired,
boolean callerFg,
- BackgroundStartPrivileges backgroundStartPrivileges)
+ BackgroundStartPrivileges backgroundStartPrivileges, String callingPackage)
throws TransactionTooLargeException {
NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
service, callingUid, r.packageName, r.userId);
@@ -1003,7 +1003,7 @@
r.delayedStop = false;
r.fgRequired = fgRequired;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- service, neededGrants, callingUid, callingProcessName));
+ service, neededGrants, callingUid, callingProcessName, callingPackage));
if (fgRequired) {
// We are now effectively running a foreground service.
@@ -1088,7 +1088,7 @@
r.allowBgActivityStartsOnServiceStart(backgroundStartPrivileges);
}
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting,
- callingUid, callingProcessName, wasStartRequested);
+ callingUid, callingProcessName, wasStartRequested, callingPackage);
return cmp;
}
@@ -1241,7 +1241,7 @@
try {
startServiceInnerLocked(s, serviceIntent, callingUid, callingPid,
callingProcessName, fgRequired, callerFg,
- backgroundStartPrivileges);
+ backgroundStartPrivileges, callingPackage);
} catch (TransactionTooLargeException e) {
/* ignore - local call */
}
@@ -1287,7 +1287,7 @@
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting, int callingUid, String callingProcessName,
- boolean wasStartRequested) throws TransactionTooLargeException {
+ boolean wasStartRequested, String callingPackage) throws TransactionTooLargeException {
synchronized (mAm.mProcessStats.mLock) {
final ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1328,7 +1328,9 @@
: SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
getShortProcessNameForStats(callingUid, callingProcessName),
getShortServiceNameForStats(r),
- packageState);
+ packageState,
+ packageName,
+ callingPackage);
if (r.startRequested && addToStarting) {
boolean first = smap.mStartingBackground.size() == 0;
@@ -3661,7 +3663,9 @@
: SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
getShortProcessNameForStats(callingUid, callerApp.processName),
getShortServiceNameForStats(s),
- packageState);
+ packageState,
+ s.packageName,
+ callerApp.info.packageName);
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
+ ": received=" + b.intent.received
@@ -5253,7 +5257,7 @@
// be called.
if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- null, null, 0, null));
+ null, null, 0, null, null));
}
sendServiceArgsLocked(r, execInFg, true);
@@ -6247,7 +6251,7 @@
stopServiceLocked(sr, true);
} else {
sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
- sr.getLastStartId(), baseIntent, null, 0, null));
+ sr.getLastStartId(), baseIntent, null, 0, null, null));
if (sr.app != null && sr.app.getThread() != null) {
// We always run in the foreground, since this is called as
// part of the "remove task" UI operation.
@@ -7318,7 +7322,7 @@
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
- callingPackage, callingPid, callingUid, r, backgroundStartPrivileges,
+ callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
isBindService);
if (!r.mAllowWhileInUsePermissionInFgs) {
r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
@@ -7345,7 +7349,7 @@
return true;
}
final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
- callingPackage, callingPid, callingUid, null /* serviceRecord */,
+ callingPackage, callingPid, callingUid, null /* targetProcess */,
BackgroundStartPrivileges.NONE, false);
@ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
@@ -7362,13 +7366,14 @@
/**
* Should allow while-in-use permissions in FGS or not.
* A typical BG started FGS is not allowed to have while-in-use permissions.
+ *
* @param callingPackage caller app's package name.
- * @param callingUid caller app's uid.
- * @param targetService the service to start.
+ * @param callingUid caller app's uid.
+ * @param targetProcess the process of the service to start.
* @return {@link ReasonCode}
*/
private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
- int callingPid, int callingUid, @Nullable ServiceRecord targetService,
+ int callingPid, int callingUid, @Nullable ProcessRecord targetProcess,
BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
int ret = REASON_DENIED;
@@ -7436,8 +7441,8 @@
}
if (ret == REASON_DENIED) {
- if (targetService != null && targetService.app != null) {
- ActiveInstrumentation instr = targetService.app.getActiveInstrumentation();
+ if (targetProcess != null) {
+ ActiveInstrumentation instr = targetProcess.getActiveInstrumentation();
if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
}
@@ -7522,7 +7527,7 @@
final @ReasonCode int allowWhileInUse2 =
shouldAllowFgsWhileInUsePermissionLocked(
clientPackageName,
- clientPid, clientUid, null /* serviceRecord */,
+ clientPid, clientUid, null /* targetProcess */,
BackgroundStartPrivileges.NONE, false);
final @ReasonCode int allowStartFgs =
shouldAllowFgsStartForegroundNoBindingCheckLocked(
@@ -7942,11 +7947,18 @@
boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
String callingPackage) {
return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
- /* targetService */ null,
+ /* targetProcess */ null,
BackgroundStartPrivileges.NONE, false)
!= REASON_DENIED;
}
+ boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid,
+ String callingPackage, @Nullable ProcessRecord targetProcess,
+ @NonNull BackgroundStartPrivileges backgroundStartPrivileges) {
+ return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
+ targetProcess, backgroundStartPrivileges, false) != REASON_DENIED;
+ }
+
/**
* Checks if a given packageName belongs to a given uid.
* @param packageName the package of the caller
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4fa28a1..82c4796a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -118,6 +118,8 @@
static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit";
static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration";
+ static final String KEY_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION =
+ "vis_to_invis_uij_schedule_grace_duration";
static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
static final String KEY_FGS_ATOM_SAMPLE_RATE = "fgs_atom_sample_rate";
static final String KEY_FGS_START_ALLOWED_LOG_SAMPLE_RATE = "fgs_start_allowed_log_sample_rate";
@@ -191,6 +193,8 @@
private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12;
private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 20 * 1000;
private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
+ private static final long DEFAULT_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION =
+ DEFAULT_FG_TO_BG_FGS_GRACE_DURATION;
private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 %
private static final float DEFAULT_FGS_START_ALLOWED_LOG_SAMPLE_RATE = 0.25f; // 25%
@@ -680,6 +684,15 @@
volatile long mFgToBgFgsGraceDuration = DEFAULT_FG_TO_BG_FGS_GRACE_DURATION;
/**
+ * The grace period in milliseconds to allow a process to schedule a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}
+ * after switching from visible to a non-visible state.
+ * Currently it's only applicable to its activities.
+ */
+ volatile long mVisibleToInvisibleUijScheduleGraceDurationMs =
+ DEFAULT_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION;
+
+ /**
* When service started from background, before the timeout it can be promoted to FGS by calling
* Service.startForeground().
*/
@@ -1123,6 +1136,9 @@
case KEY_FG_TO_BG_FGS_GRACE_DURATION:
updateFgToBgFgsGraceDuration();
break;
+ case KEY_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION:
+ updateFgToBgFgsGraceDuration();
+ break;
case KEY_FGS_START_FOREGROUND_TIMEOUT:
updateFgsStartForegroundTimeout();
break;
@@ -1598,6 +1614,13 @@
DEFAULT_FG_TO_BG_FGS_GRACE_DURATION);
}
+ private void updateVisibleToInvisibleUijScheduleGraceDuration() {
+ mVisibleToInvisibleUijScheduleGraceDurationMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION,
+ DEFAULT_VISIBLE_TO_INVISIBLE_UIJ_SCHEDULE_GRACE_DURATION);
+ }
+
private void updateFgsStartForegroundTimeout() {
mFgsStartForegroundTimeoutMs = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 59a4139..415c859 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.Manifest.permission.MANAGE_USERS;
+import static android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
@@ -32,6 +33,7 @@
import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
@@ -60,9 +62,22 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
+import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
+import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
+import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
+import static android.os.PowerExemptionManager.REASON_START_ACTIVITY_FLAG;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
+import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
+import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
+import static android.os.PowerExemptionManager.getReasonCodeFromProcState;
import static android.os.Process.BLUETOOTH_UID;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
@@ -6541,6 +6556,143 @@
}
/**
+ * Returns true if the reasonCode is included in the base set of reasons an app may be
+ * allowed to schedule a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+ * This is a shortcut and <b>DOES NOT</b> include all reasons.
+ * Use {@link #canScheduleUserInitiatedJobs(int, int, String)} to cover all cases.
+ */
+ private boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
+ switch (reasonCode) {
+ case REASON_PROC_STATE_PERSISTENT:
+ case REASON_PROC_STATE_PERSISTENT_UI:
+ case REASON_PROC_STATE_TOP:
+ case REASON_PROC_STATE_BTOP:
+ case REASON_UID_VISIBLE:
+ case REASON_SYSTEM_UID:
+ case REASON_START_ACTIVITY_FLAG:
+ case REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD:
+ case REASON_SYSTEM_ALERT_WINDOW_PERMISSION:
+ case REASON_COMPANION_DEVICE_MANAGER:
+ case REASON_BACKGROUND_ACTIVITY_PERMISSION:
+ case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION:
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the ProcessRecord has some conditions that allow the app to schedule a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+ * This is a shortcut and <b>DOES NOT</b> include all reasons.
+ * Use {@link #canScheduleUserInitiatedJobs(int, int, String)} to cover all cases.
+ */
+ @GuardedBy(anyOf = {"this", "mProcLock"})
+ private boolean isProcessInStateToScheduleUserInitiatedJobsLocked(
+ @Nullable ProcessRecord pr, long nowElapsed) {
+ if (pr == null) {
+ return false;
+ }
+
+ final BackgroundStartPrivileges backgroundStartPrivileges =
+ pr.getBackgroundStartPrivileges();
+ // Is the allow activity background start flag on?
+ if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
+ // REASON_START_ACTIVITY_FLAG;
+ return true;
+ }
+
+ final ProcessStateRecord state = pr.mState;
+ final int procstate = state.getCurProcState();
+ if (procstate <= PROCESS_STATE_BOUND_TOP) {
+ if (doesReasonCodeAllowSchedulingUserInitiatedJobs(
+ getReasonCodeFromProcState(procstate))) {
+ return true;
+ }
+ }
+
+ final long lastInvisibleTime = state.getLastInvisibleTime();
+ if (lastInvisibleTime > 0 && lastInvisibleTime < Long.MAX_VALUE) {
+ final long timeSinceVisibleMs = nowElapsed - lastInvisibleTime;
+ if (timeSinceVisibleMs < mConstants.mVisibleToInvisibleUijScheduleGraceDurationMs) {
+ // REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the app in question is in a state where we allow scheduling a
+ * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
+ */
+ // TODO(262260570): log allow reason to an atom
+ private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+ synchronized (this) {
+ final ProcessRecord processRecord;
+ synchronized (mPidsSelfLocked) {
+ processRecord = mPidsSelfLocked.get(pid);
+ }
+
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final BackgroundStartPrivileges backgroundStartPrivileges;
+ if (processRecord != null) {
+ if (isProcessInStateToScheduleUserInitiatedJobsLocked(processRecord, nowElapsed)) {
+ return true;
+ }
+ backgroundStartPrivileges = processRecord.getBackgroundStartPrivileges();
+ } else {
+ backgroundStartPrivileges = getBackgroundStartPrivileges(uid);
+ }
+ // Is the allow activity background start flag on?
+ if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
+ // REASON_START_ACTIVITY_FLAG;
+ return true;
+ }
+
+ // We allow scheduling a user-initiated job when the app is in the TOP or a
+ // Background Activity Launch approved state. These are cases that indicate the user
+ // has interacted with the app and therefore it is reasonable to believe the app may
+ // attempt to schedule a user-initiated job in response to the user interaction.
+ // As of Android UDC, the conditions required to grant a while-in-use permission
+ // covers the majority of those cases, and so we piggyback on that logic as the base.
+ // Missing cases are added after.
+ if (mServices.canAllowWhileInUsePermissionInFgsLocked(
+ pid, uid, pkgName, processRecord, backgroundStartPrivileges)) {
+ return true;
+ }
+
+ final UidRecord uidRecord = mProcessList.getUidRecordLOSP(uid);
+ if (uidRecord != null) {
+ for (int i = uidRecord.getNumOfProcs() - 1; i >= 0; --i) {
+ ProcessRecord pr = uidRecord.getProcessRecordByIndex(i);
+ if (isProcessInStateToScheduleUserInitiatedJobsLocked(pr, nowElapsed)) {
+ return true;
+ }
+ }
+ }
+
+ if (mAtmInternal.hasSystemAlertWindowPermission(uid, pid, pkgName)) {
+ // REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ return true;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+ final boolean isCompanionApp = mInternal.isAssociatedCompanionApp(userId, uid);
+ if (isCompanionApp) {
+ if (checkPermission(REQUEST_COMPANION_RUN_IN_BACKGROUND, pid, uid)
+ == PERMISSION_GRANTED) {
+ // REASON_COMPANION_DEVICE_MANAGER;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
* @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on
* the allowlist
*/
@@ -13933,7 +14085,7 @@
if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
/*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
throw new SecurityException("SDK sandbox not allowed to register receiver"
- + " with the given IntentFilter");
+ + " with the given IntentFilter: " + filter.toString());
}
}
@@ -18156,6 +18308,11 @@
return ActivityManagerService.this.getBackgroundStartPrivileges(uid);
}
+ @Override
+ public boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+ return ActivityManagerService.this.canScheduleUserInitiatedJobs(uid, pid, pkgName);
+ }
+
public void reportCurKeyguardUsageEvent(boolean keyguardShowing) {
ActivityManagerService.this.reportGlobalUsageEvent(keyguardShowing
? UsageEvents.Event.KEYGUARD_SHOWN
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 2d779c4..6928bd3 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -106,7 +106,6 @@
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageStatsManager;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -117,7 +116,6 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
import android.content.pm.ServiceInfo.ForegroundServiceType;
-import android.database.ContentObserver;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AppBackgroundRestrictionsInfo;
@@ -137,7 +135,6 @@
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -1069,8 +1066,7 @@
}
}
- final class ConstantsObserver extends ContentObserver implements
- OnPropertiesChangedListener {
+ final class ConstantsObserver implements OnPropertiesChangedListener {
/**
* Whether or not to set the app to restricted standby bucket automatically
* when it's background-restricted.
@@ -1181,8 +1177,6 @@
volatile boolean mBgAutoRestrictAbusiveApps;
- volatile boolean mRestrictedBucketEnabled;
-
volatile long mBgAbusiveNotificationMinIntervalMs;
volatile long mBgLongFgsNotificationMinIntervalMs;
@@ -1215,7 +1209,6 @@
volatile boolean mBgPromptAbusiveAppsToBgRestricted;
ConstantsObserver(Handler handler, Context context) {
- super(handler);
mDefaultBgPromptFgsWithNotiToBgRestricted = context.getResources().getBoolean(
com.android.internal.R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted);
mDefaultBgPromptAbusiveAppToBgRestricted = context.getResources().getBoolean(
@@ -1261,29 +1254,10 @@
}
}
- @Override
- public void onChange(boolean selfChange) {
- updateSettings();
- }
-
public void start() {
- final ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(Global.getUriFor(Global.ENABLE_RESTRICTED_BUCKET),
- false, this);
- updateSettings();
updateDeviceConfig();
}
- void updateSettings() {
- mRestrictedBucketEnabled = isRestrictedBucketEnabled();
- }
-
- private boolean isRestrictedBucketEnabled() {
- return Global.getInt(mContext.getContentResolver(),
- Global.ENABLE_RESTRICTED_BUCKET,
- Global.DEFAULT_ENABLE_RESTRICTED_BUCKET) == 1;
- }
-
void updateDeviceConfig() {
updateBgAutoRestrictedBucketChanged();
updateBgAutoRestrictAbusiveApps();
@@ -1763,8 +1737,7 @@
.isAppBackgroundRestricted(uid, packageName)) {
return new Pair<>(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, mEmptyTrackerInfo);
}
- level = mConstantsObserver.mRestrictedBucketEnabled
- && standbyBucket == STANDBY_BUCKET_RESTRICTED
+ level = standbyBucket == STANDBY_BUCKET_RESTRICTED
? RESTRICTION_LEVEL_RESTRICTED_BUCKET
: RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
if (calcTrackers) {
@@ -1811,13 +1784,9 @@
@RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
@RestrictionLevel int prevLevel = level;
BaseAppStateTracker resultTracker = null;
- final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled;
for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) {
@RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy()
.getProposedRestrictionLevel(packageName, uid, maxLevel);
- if (!isRestrictedBucketEnabled && l == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
- l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
- }
level = Math.max(level, l);
if (level != prevLevel) {
resultTracker = mAppStateTrackers.get(i);
@@ -2193,9 +2162,6 @@
}
if (level >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
&& curLevel < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
- if (!mConstantsObserver.mRestrictedBucketEnabled) {
- return;
- }
// Moving the app standby bucket to restricted in the meanwhile.
if (DEBUG_BG_RESTRICTION_CONTROLLER
&& level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 33d4004..4d46963 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -242,7 +242,7 @@
*/
public boolean CORE_DEFER_UNTIL_ACTIVE = DEFAULT_CORE_DEFER_UNTIL_ACTIVE;
private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active";
- private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = false;
+ private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = true;
// Settings override tracking for this instance
private String mSettingsKey;
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 1f65730..0cdd4e9 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -399,7 +399,8 @@
* Update if this process is in the "cached" state, typically signaling that
* broadcast dispatch should be paused or delayed.
*/
- public void setProcessCached(boolean cached) {
+ @VisibleForTesting
+ void setProcessCached(boolean cached) {
if (mProcessCached != cached) {
mProcessCached = cached;
invalidateRunnableAt();
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index c085706..fcddff0 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -601,7 +601,9 @@
r.dispatchTime - r.enqueueTime,
r.receiverTime - r.dispatchTime,
finishTime - r.receiverTime,
- packageState);
+ packageState,
+ r.curApp.info.packageName,
+ r.callerPackage);
}
if (state == BroadcastRecord.IDLE) {
Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -780,7 +782,8 @@
BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
dispatchDelay, receiveDelay, 0 /* finish_delay */,
- SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+ SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+ app != null ? app.info.packageName : null, callingPackage);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index ff4f9b9..1f0b162 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1122,7 +1122,7 @@
}
r.terminalCount++;
- notifyFinishReceiver(queue, r, index, receiver);
+ notifyFinishReceiver(queue, app, r, index, receiver);
checkFinished = true;
}
// When entire ordered broadcast finished, deliver final result
@@ -1595,9 +1595,10 @@
* typically for internal bookkeeping.
*/
private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue,
- @NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
+ @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
+ @NonNull Object receiver) {
if (r.wasDeliveryAttempted(index)) {
- logBroadcastDeliveryEventReported(queue, r, index, receiver);
+ logBroadcastDeliveryEventReported(queue, app, r, index, receiver);
}
final boolean recordFinished = (r.terminalCount == r.receivers.size());
@@ -1607,7 +1608,8 @@
}
private void logBroadcastDeliveryEventReported(@Nullable BroadcastProcessQueue queue,
- @NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
+ @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
+ @NonNull Object receiver) {
// Report statistics for each individual receiver
final int uid = getReceiverUid(receiver);
final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid;
@@ -1633,7 +1635,8 @@
? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
: SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
- receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState);
+ receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
+ app != null ? app.info.packageName : null, r.callerPackage);
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 3ab9e71..2617fb7 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1233,7 +1233,7 @@
}
UidRecord uidRec = app.getUidRecord();
- if (uidRec.isFrozen()) {
+ if (uidRec != null && uidRec.isFrozen()) {
uidRec.setFrozen(false);
mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
reportOneUidFrozenStateChanged(app.uid, false);
@@ -1914,7 +1914,9 @@
uids[0] = uid;
frozenStates[0] = frozen ? UID_FROZEN_STATE_FROZEN : UID_FROZEN_STATE_UNFROZEN;
- Slog.d(TAG_AM, "reportOneUidFrozenStateChanged uid " + uid + " frozen = " + frozen);
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM, "reportOneUidFrozenStateChanged uid " + uid + " frozen = " + frozen);
+ }
mAm.reportUidFrozenStateChanged(uids, frozenStates);
}
diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java
index 01735a7..f9eaf02 100644
--- a/services/core/java/com/android/server/am/ComponentAliasResolver.java
+++ b/services/core/java/com/android/server/am/ComponentAliasResolver.java
@@ -30,7 +30,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Binder;
-import android.os.Build;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -43,7 +42,6 @@
import com.android.internal.os.BackgroundThread;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import java.io.PrintWriter;
@@ -52,26 +50,11 @@
import java.util.function.Supplier;
/**
- * Manages and handles component aliases, which is an experimental feature.
+ * @deprecated This feature is no longer used. Delete this class.
*
- * NOTE: THIS CLASS IS PURELY EXPERIMENTAL AND WILL BE REMOVED IN FUTURE ANDROID VERSIONS.
- * DO NOT USE IT.
- *
- * "Component alias" allows an android manifest component (for now only broadcasts and services)
- * to be defined in one android package while having the implementation in a different package.
- *
- * When/if this becomes a real feature, it will be most likely implemented very differently,
- * which is why this shouldn't be used.
- *
- * For now, because this is an experimental feature to evaluate feasibility, the implementation is
- * "quick & dirty". For example, to define aliases, we use a regular intent filter and meta-data
- * in the manifest, instead of adding proper tags/attributes to AndroidManifest.xml.
- *
- * This feature is disabled by default.
- *
- * Also, for now, aliases can be defined across packages with different certificates, but
- * in a final version this will most likely be tightened.
+ * Also delete Intnt.(set|get)OriginalIntent.
*/
+@Deprecated
public class ComponentAliasResolver {
private static final String TAG = "ComponentAliasResolver";
private static final boolean DEBUG = true;
@@ -149,11 +132,6 @@
}
};
- private final CompatChange.ChangeListener mCompatChangeListener = (packageName) -> {
- if (DEBUG) Slog.d(TAG, "USE_EXPERIMENTAL_COMPONENT_ALIAS changed.");
- BackgroundThread.getHandler().post(this::refresh);
- };
-
/**
* Call this on systemRead().
*/
@@ -161,8 +139,6 @@
synchronized (mLock) {
mPlatformCompat = (PlatformCompat) ServiceManager.getService(
Context.PLATFORM_COMPAT_SERVICE);
- mPlatformCompat.registerListener(USE_EXPERIMENTAL_COMPONENT_ALIAS,
- mCompatChangeListener);
}
if (DEBUG) Slog.d(TAG, "Compat listener set.");
update(enabledByDeviceConfig, overrides);
@@ -176,10 +152,8 @@
if (mPlatformCompat == null) {
return; // System not ready.
}
- final boolean enabled = Build.isDebuggable()
- && (enabledByDeviceConfig
- || mPlatformCompat.isChangeEnabledByPackageName(
- USE_EXPERIMENTAL_COMPONENT_ALIAS, "android", UserHandle.USER_SYSTEM));
+ // Never enable it.
+ final boolean enabled = false;
if (enabled != mEnabled) {
Slog.i(TAG, (enabled ? "Enabling" : "Disabling") + " component aliases...");
FgThread.getHandler().post(() -> {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 48df1494..a1fcd42 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -263,7 +263,8 @@
PROVIDER_ACQUISITION_EVENT_REPORTED,
r.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
- PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+ PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+ cpi.packageName, callingPackage);
return holder;
}
@@ -334,7 +335,8 @@
PROVIDER_ACQUISITION_EVENT_REPORTED,
cpr.proc.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
- PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+ PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+ cpi.packageName, callingPackage);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -511,7 +513,8 @@
PROVIDER_ACQUISITION_EVENT_REPORTED,
proc.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
- PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL);
+ PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
+ cpi.packageName, callingPackage);
} else {
final int packageState =
((cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0)
@@ -536,7 +539,7 @@
PROVIDER_ACQUISITION_EVENT_REPORTED,
proc.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
- packageState);
+ packageState, cpi.packageName, callingPackage);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 663121e..4defdc6 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -249,6 +249,7 @@
final String mCallingProcessName;
final Intent intent;
final NeededUriGrants neededGrants;
+ final @Nullable String mCallingPackageName;
long deliveredTime;
int deliveryCount;
int doneExecutingCount;
@@ -258,7 +259,7 @@
StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id,
Intent _intent, NeededUriGrants _neededGrants, int _callingId,
- String callingProcessName) {
+ String callingProcessName, @Nullable String callingPackageName) {
sr = _sr;
taskRemoved = _taskRemoved;
id = _id;
@@ -266,6 +267,7 @@
neededGrants = _neededGrants;
callingId = _callingId;
mCallingProcessName = callingProcessName;
+ mCallingPackageName = callingPackageName;
}
UriPermissionOwner getUriPermissionsLocked() {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index e38e8c1..e39ac2b 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -311,6 +311,11 @@
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
+ ProcessRecord getProcessRecordByIndex(int idx) {
+ return mProcRecords.valueAt(idx);
+ }
+
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
ProcessRecord getProcessInPackage(String packageName) {
for (int i = mProcRecords.size() - 1; i >= 0; i--) {
final ProcessRecord app = mProcRecords.valueAt(i);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 127a9d8b..16bf355 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -170,6 +170,7 @@
import android.provider.Settings.System;
import android.service.notification.ZenModeConfig;
import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -184,6 +185,7 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -393,6 +395,7 @@
private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
private static final int MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES = 52;
private static final int MSG_LOWER_VOLUME_TO_RS1 = 53;
+ private static final int MSG_CONFIGURATION_CHANGED = 54;
/** Messages handled by the {@link SoundDoseHelper}. */
/*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
@@ -1046,9 +1049,14 @@
mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
- final boolean headTrackingDefault = mContext.getResources().getBoolean(
+ final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+ "ro.audio.spatializer_binaural_enabled_default", true);
+ final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+ "ro.audio.spatializer_transaural_enabled_default", true);
+ final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
- mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, headTrackingDefault);
+ mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+ binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
@@ -1214,17 +1222,6 @@
updateAudioHalPids();
- boolean cameraSoundForced = readCameraSoundForced();
- mCameraSoundForced = new Boolean(cameraSoundForced);
- sendMsg(mAudioHandler,
- MSG_SET_FORCE_USE,
- SENDMSG_QUEUE,
- AudioSystem.FOR_SYSTEM,
- cameraSoundForced ?
- AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
- new String("AudioService ctor"),
- 0);
-
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
@@ -1309,6 +1306,18 @@
* Called by handling of MSG_INIT_STREAMS_VOLUMES
*/
private void onInitStreamsAndVolumes() {
+ synchronized (mSettingsLock) {
+ mCameraSoundForced = readCameraSoundForced();
+ sendMsg(mAudioHandler,
+ MSG_SET_FORCE_USE,
+ SENDMSG_QUEUE,
+ AudioSystem.FOR_SYSTEM,
+ mCameraSoundForced
+ ? AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
+ new String("AudioService ctor"),
+ 0);
+ }
+
createStreamStates();
// must be called after createStreamStates() as it uses MUSIC volume as default if no
@@ -1344,8 +1353,19 @@
// check on volume initialization
checkVolumeRangeInitialization("AudioService()");
+
}
+ private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ Log.i(TAG, "onSubscriptionsChanged()");
+ sendMsg(mAudioHandler, MSG_CONFIGURATION_CHANGED, SENDMSG_REPLACE,
+ 0, 0, null, 0);
+ }
+ };
+
/**
* Initialize intent receives and settings observers for this service.
* Must be called after createStreamStates() as the handling of some events
@@ -1383,6 +1403,13 @@
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
Context.RECEIVER_EXPORTED);
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ if (subscriptionManager == null) {
+ Log.e(TAG, "initExternalEventReceivers cannot create SubscriptionManager!");
+ } else {
+ subscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+ }
}
public void systemReady() {
@@ -3660,7 +3687,7 @@
for (int stream = 0; stream < mStreamStates.length; stream++) {
VolumeStreamState vss = mStreamStates[stream];
if (streamAlias == mStreamVolumeAlias[stream] && vss.isMutable()) {
- if (!(readCameraSoundForced()
+ if (!(mCameraSoundForced
&& (vss.getStreamType()
== AudioSystem.STREAM_SYSTEM_ENFORCED))) {
boolean changed = vss.mute(state, /* apply= */ false);
@@ -7237,7 +7264,7 @@
super.setDeviceVolumeBehavior_enforcePermission();
// verify arguments
Objects.requireNonNull(device);
- AudioManager.enforceSettableVolumeBehavior(deviceVolumeBehavior);
+ AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+ AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
@@ -9232,6 +9259,10 @@
onLowerVolumeToRs1();
break;
+ case MSG_CONFIGURATION_CHANGED:
+ onConfigurationChanged();
+ break;
+
default:
if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
// msg could be for the SoundDoseHelper
@@ -9414,7 +9445,12 @@
}
AudioSystem.setParameters("screen_state=off");
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
- handleConfigurationChanged(context);
+ sendMsg(mAudioHandler,
+ MSG_CONFIGURATION_CHANGED,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ null, 0);
} else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
if (mUserSwitchedReceived) {
// attempt to stop music playback for background user except on first user
@@ -10156,10 +10192,30 @@
}
//==========================================================================================
+
+ // camera sound is forced if any of the resources corresponding to one active SIM
+ // demands it.
private boolean readCameraSoundForced() {
- return SystemProperties.getBoolean("audio.camerasound.force", false) ||
- mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_camera_sound_forced);
+ if (SystemProperties.getBoolean("audio.camerasound.force", false)
+ || mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_camera_sound_forced)) {
+ return true;
+ }
+
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ if (subscriptionManager == null) {
+ Log.e(TAG, "readCameraSoundForced cannot create SubscriptionManager!");
+ return false;
+ }
+ int[] subscriptionIds = subscriptionManager.getActiveSubscriptionIdList(false);
+ for (int subId : subscriptionIds) {
+ if (SubscriptionManager.getResourcesForSubId(mContext, subId).getBoolean(
+ com.android.internal.R.bool.config_camera_sound_forced)) {
+ return true;
+ }
+ }
+ return false;
}
//==========================================================================================
@@ -10370,11 +10426,11 @@
* Monitoring rotation is optional, and is defined by the definition and value
* of the "ro.audio.monitorRotation" system property.
*/
- private void handleConfigurationChanged(Context context) {
+ private void onConfigurationChanged() {
try {
// reading new configuration "safely" (i.e. under try catch) in case anything
// goes wrong.
- Configuration config = context.getResources().getConfiguration();
+ Configuration config = mContext.getResources().getConfiguration();
mSoundDoseHelper.configureSafeMedia(/*forced*/false, TAG);
boolean cameraSoundForced = readCameraSoundForced();
@@ -10401,7 +10457,7 @@
mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM,
cameraSoundForced ?
AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
- "handleConfigurationChanged");
+ "onConfigurationChanged");
sendMsg(mAudioHandler,
MSG_SET_ALL_VOLUMES,
SENDMSG_QUEUE,
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index bb49a18..7f7c138 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -369,7 +369,7 @@
if (mCurrentCsd > 0.0f) {
final SoundDoseRecord record = new SoundDoseRecord();
- record.timestamp = SystemClock.elapsedRealtime();
+ record.timestamp = SystemClock.elapsedRealtime() / 1000;
record.value = csd;
mDoseRecords.add(record);
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8f54e45..5edd434 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -172,13 +172,17 @@
// initialization
@SuppressWarnings("StaticAssignmentInConstructor")
SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
- boolean headTrackingEnabledByDefault) {
+ boolean binauralEnabledDefault,
+ boolean transauralEnabledDefault,
+ boolean headTrackingEnabledDefault) {
mAudioService = mother;
mASA = asa;
// "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
// constructed here is the factory for SADeviceState, thus SADeviceState and its
// private static field sHeadTrackingEnabledDefault should never be accessed directly.
- SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledByDefault;
+ SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
+ SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
+ SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
}
synchronized void init(boolean effectExpected, @Nullable String settings) {
@@ -1547,10 +1551,12 @@
}
/*package*/ static final class SADeviceState {
+ private static boolean sBinauralEnabledDefault = true;
+ private static boolean sTransauralEnabledDefault = true;
private static boolean sHeadTrackingEnabledDefault = false;
final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
final @NonNull String mDeviceAddress;
- boolean mEnabled = true; // by default, SA is enabled on any device
+ boolean mEnabled;
boolean mHasHeadTracker = false;
boolean mHeadTrackerEnabled;
static final String SETTING_FIELD_SEPARATOR = ",";
@@ -1566,6 +1572,12 @@
SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
mDeviceType = deviceType;
mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
+ final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+ mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+ ? sBinauralEnabledDefault
+ : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+ ? sTransauralEnabledDefault
+ : false;
mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index 82444f0..6bd4880 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -16,14 +16,22 @@
package com.android.server.biometrics.log;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.WakeReason;
import android.util.Slog;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import java.util.stream.Stream;
+
/**
* Wrapper for {@link FrameworkStatsLog} to isolate the testable parts.
*/
@@ -63,7 +71,7 @@
orientationType(operationContext.getOrientation()),
foldType(operationContext.getFoldState()),
operationContext.getOrderAndIncrement(),
- BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+ toProtoWakeReason(operationContext));
}
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -89,7 +97,8 @@
orientationType(operationContext.getOrientation()),
foldType(operationContext.getFoldState()),
operationContext.getOrderAndIncrement(),
- BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+ toProtoWakeReason(operationContext),
+ toProtoWakeReasonDetails(operationContext));
}
/** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -137,7 +146,81 @@
orientationType(operationContext.getOrientation()),
foldType(operationContext.getFoldState()),
operationContext.getOrderAndIncrement(),
- BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+ toProtoWakeReason(operationContext),
+ toProtoWakeReasonDetails(operationContext));
+ }
+
+ @VisibleForTesting
+ static int[] toProtoWakeReasonDetails(@NonNull OperationContextExt operationContext) {
+ final OperationContext ctx = operationContext.toAidlContext();
+ return Stream.of(toProtoWakeReasonDetails(ctx.authenticateReason))
+ .mapToInt(i -> i)
+ .filter(i -> i != BiometricsProtoEnums.DETAILS_UNKNOWN)
+ .toArray();
+ }
+
+ @VisibleForTesting
+ static int toProtoWakeReason(@NonNull OperationContextExt operationContext) {
+ @WakeReason final int reason = operationContext.getWakeReason();
+ switch (reason) {
+ case WakeReason.POWER_BUTTON:
+ return BiometricsProtoEnums.WAKE_REASON_POWER_BUTTON;
+ case WakeReason.GESTURE:
+ return BiometricsProtoEnums.WAKE_REASON_GESTURE;
+ case WakeReason.WAKE_KEY:
+ return BiometricsProtoEnums.WAKE_REASON_WAKE_KEY;
+ case WakeReason.WAKE_MOTION:
+ return BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION;
+ case WakeReason.LID:
+ return BiometricsProtoEnums.WAKE_REASON_LID;
+ case WakeReason.DISPLAY_GROUP_ADDED:
+ return BiometricsProtoEnums.WAKE_REASON_DISPLAY_GROUP_ADDED;
+ case WakeReason.TAP:
+ return BiometricsProtoEnums.WAKE_REASON_TAP;
+ case WakeReason.LIFT:
+ return BiometricsProtoEnums.WAKE_REASON_LIFT;
+ case WakeReason.BIOMETRIC:
+ return BiometricsProtoEnums.WAKE_REASON_BIOMETRIC;
+ default:
+ return BiometricsProtoEnums.WAKE_REASON_UNKNOWN;
+ }
+ }
+
+ private static int toProtoWakeReasonDetails(@Nullable AuthenticateReason reason) {
+ if (reason != null) {
+ switch (reason.getTag()) {
+ case AuthenticateReason.faceAuthenticateReason:
+ return toProtoWakeReasonDetailsFromFace(reason.getFaceAuthenticateReason());
+ }
+ }
+ return BiometricsProtoEnums.DETAILS_UNKNOWN;
+ }
+
+ private static int toProtoWakeReasonDetailsFromFace(@AuthenticateReason.Face int reason) {
+ switch (reason) {
+ case AuthenticateReason.Face.STARTED_WAKING_UP:
+ return BiometricsProtoEnums.DETAILS_FACE_STARTED_WAKING_UP;
+ case AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN:
+ return BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN;
+ case AuthenticateReason.Face.ASSISTANT_VISIBLE:
+ return BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE;
+ case AuthenticateReason.Face.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN:
+ return BiometricsProtoEnums.DETAILS_FACE_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
+ case AuthenticateReason.Face.NOTIFICATION_PANEL_CLICKED:
+ return BiometricsProtoEnums.DETAILS_FACE_NOTIFICATION_PANEL_CLICKED;
+ case AuthenticateReason.Face.OCCLUDING_APP_REQUESTED:
+ return BiometricsProtoEnums.DETAILS_FACE_OCCLUDING_APP_REQUESTED;
+ case AuthenticateReason.Face.PICK_UP_GESTURE_TRIGGERED:
+ return BiometricsProtoEnums.DETAILS_FACE_PICK_UP_GESTURE_TRIGGERED;
+ case AuthenticateReason.Face.QS_EXPANDED:
+ return BiometricsProtoEnums.DETAILS_FACE_QS_EXPANDED;
+ case AuthenticateReason.Face.SWIPE_UP_ON_BOUNCER:
+ return BiometricsProtoEnums.DETAILS_FACE_SWIPE_UP_ON_BOUNCER;
+ case AuthenticateReason.Face.UDFPS_POINTER_DOWN:
+ return BiometricsProtoEnums.DETAILS_FACE_UDFPS_POINTER_DOWN;
+ default:
+ return BiometricsProtoEnums.DETAILS_UNKNOWN;
+ }
}
/** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index ecb7e7c..2934339 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -188,10 +188,17 @@
}
/** {@link OperationContext#reason}. */
+ @OperationReason
public byte getReason() {
return mAidlContext.reason;
}
+ /** {@link OperationContext#wakeReason}. */
+ @WakeReason
+ public int getWakeReason() {
+ return mAidlContext.wakeReason;
+ }
+
/** If the screen is currently on. */
public boolean isDisplayOn() {
return mIsDisplayOn;
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index 4f2bfd1..aab815c 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -347,31 +347,13 @@
private static boolean isValidHalProgramSelector(
android.hardware.broadcastradio.ProgramSelector sel) {
- if (sel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ
- && sel.primaryId.type != IdentifierType.RDS_PI
- && sel.primaryId.type != IdentifierType.HD_STATION_ID_EXT
- && sel.primaryId.type != IdentifierType.DAB_SID_EXT
- && sel.primaryId.type != IdentifierType.DRMO_SERVICE_ID
- && sel.primaryId.type != IdentifierType.SXM_SERVICE_ID
- && !isVendorIdentifierType(sel.primaryId.type)) {
- return false;
- }
- if (sel.primaryId.type == IdentifierType.DAB_SID_EXT) {
- boolean hasEnsemble = false;
- boolean hasFrequency = false;
- for (int i = 0; i < sel.secondaryIds.length; i++) {
- if (sel.secondaryIds[i].type == IdentifierType.DAB_ENSEMBLE) {
- hasEnsemble = true;
- } else if (sel.secondaryIds[i].type == IdentifierType.DAB_FREQUENCY_KHZ) {
- hasFrequency = true;
- }
- if (hasEnsemble && hasFrequency) {
- return true;
- }
- }
- return false;
- }
- return true;
+ return sel.primaryId.type == IdentifierType.AMFM_FREQUENCY_KHZ
+ || sel.primaryId.type == IdentifierType.RDS_PI
+ || sel.primaryId.type == IdentifierType.HD_STATION_ID_EXT
+ || sel.primaryId.type == IdentifierType.DAB_SID_EXT
+ || sel.primaryId.type == IdentifierType.DRMO_SERVICE_ID
+ || sel.primaryId.type == IdentifierType.SXM_SERVICE_ID
+ || isVendorIdentifierType(sel.primaryId.type);
}
@Nullable
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6d3f8fd..1e9352d1 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -31,6 +31,7 @@
import static android.os.PowerWhitelistManager.REASON_VPN;
import static android.os.UserHandle.PER_USER_RANGE;
import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT;
import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
@@ -269,6 +270,42 @@
@VisibleForTesting
static final int DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT = 5 * 60;
+ /**
+ * Prefer using {@link IkeSessionParams.ESP_IP_VERSION_AUTO} and
+ * {@link IkeSessionParams.ESP_ENCAP_TYPE_AUTO} for ESP packets.
+ *
+ * This is one of the possible customization values for
+ * CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT.
+ */
+ @VisibleForTesting
+ public static final int PREFERRED_IKE_PROTOCOL_AUTO = 0;
+ /**
+ * Prefer using {@link IkeSessionParams.ESP_IP_VERSION_IPV4} and
+ * {@link IkeSessionParams.ESP_ENCAP_TYPE_UDP} for ESP packets.
+ *
+ * This is one of the possible customization values for
+ * CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT.
+ */
+ @VisibleForTesting
+ public static final int PREFERRED_IKE_PROTOCOL_IPV4_UDP = 40;
+ /**
+ * Prefer using {@link IkeSessionParams.ESP_IP_VERSION_IPV6} and
+ * {@link IkeSessionParams.ESP_ENCAP_TYPE_UDP} for ESP packets.
+ *
+ * Do not use this value for production code. Its numeric value will change in future versions.
+ */
+ @VisibleForTesting
+ public static final int PREFERRED_IKE_PROTOCOL_IPV6_UDP = 60;
+ /**
+ * Prefer using {@link IkeSessionParams.ESP_IP_VERSION_IPV6} and
+ * {@link IkeSessionParams.ESP_ENCAP_TYPE_NONE} for ESP packets.
+ *
+ * This is one of the possible customization values for
+ * CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT.
+ */
+ @VisibleForTesting
+ public static final int PREFERRED_IKE_PROTOCOL_IPV6_ESP = 61;
+
// TODO: create separate trackers for each unique VPN to support
// automated reconnection
@@ -326,12 +363,13 @@
private final LocalLog mVpnManagerEvents = new LocalLog(MAX_EVENTS_LOGS);
/**
- * Cached Map of <subscription ID, keepalive delay ms> since retrieving the PersistableBundle
+ * Cached Map of <subscription ID, CarrierConfigInfo> since retrieving the PersistableBundle
* and the target value from CarrierConfigManager is somewhat expensive as it has hundreds of
* fields. This cache is cleared when the carrier config changes to ensure data freshness.
*/
@GuardedBy("this")
- private final SparseArray<Integer> mCachedKeepalivePerSubId = new SparseArray<>();
+ private final SparseArray<CarrierConfigInfo> mCachedCarrierConfigInfoPerSubId =
+ new SparseArray<>();
/**
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -378,6 +416,28 @@
void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException;
}
+ private static class CarrierConfigInfo {
+ public final String mccMnc;
+ public final int keepaliveDelayMs;
+ public final int encapType;
+ public final int ipVersion;
+
+ CarrierConfigInfo(String mccMnc, int keepaliveDelayMs,
+ int encapType,
+ int ipVersion) {
+ this.mccMnc = mccMnc;
+ this.keepaliveDelayMs = keepaliveDelayMs;
+ this.encapType = encapType;
+ this.ipVersion = ipVersion;
+ }
+
+ @Override
+ public String toString() {
+ return "CarrierConfigInfo(" + mccMnc + ") [keepaliveDelayMs=" + keepaliveDelayMs
+ + ", encapType=" + encapType + ", ipVersion=" + ipVersion + "]";
+ }
+ }
+
@VisibleForTesting
public static class Dependencies {
public boolean isCallerSystem() {
@@ -2923,7 +2983,7 @@
public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId,
int specificCarrierId) {
synchronized (Vpn.this) {
- mCachedKeepalivePerSubId.remove(subId);
+ mCachedCarrierConfigInfoPerSubId.remove(subId);
// Ignore stale runner.
if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
@@ -3388,47 +3448,105 @@
}
private int guessEspIpVersionForNetwork() {
- // TODO : guess the IP version based on carrier if auto IP version selection is enabled
- return ESP_IP_VERSION_AUTO;
+ final CarrierConfigInfo carrierconfig = getCarrierConfig();
+ final int ipVersion = (carrierconfig != null)
+ ? carrierconfig.ipVersion : ESP_IP_VERSION_AUTO;
+ if (carrierconfig != null) {
+ Log.d(TAG, "Get customized IP version(" + ipVersion + ") on SIM("
+ + carrierconfig.mccMnc + ")");
+ }
+ return ipVersion;
}
private int guessEspEncapTypeForNetwork() {
- // TODO : guess the ESP encap type based on carrier if auto IP version selection is
- // enabled
- return ESP_ENCAP_TYPE_AUTO;
+ final CarrierConfigInfo carrierconfig = getCarrierConfig();
+ final int encapType = (carrierconfig != null)
+ ? carrierconfig.encapType : ESP_ENCAP_TYPE_AUTO;
+ if (carrierconfig != null) {
+ Log.d(TAG, "Get customized encap type(" + encapType + ") on SIM("
+ + carrierconfig.mccMnc + ")");
+ }
+ return encapType;
}
private int guessNattKeepaliveTimerForNetwork() {
+ final CarrierConfigInfo carrierconfig = getCarrierConfig();
+ final int natKeepalive = (carrierconfig != null)
+ ? carrierconfig.keepaliveDelayMs : AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+ if (carrierconfig != null) {
+ Log.d(TAG, "Get customized keepalive(" + natKeepalive + ") on SIM("
+ + carrierconfig.mccMnc + ")");
+ }
+ return natKeepalive;
+ }
+
+ private CarrierConfigInfo getCarrierConfig() {
final int subId = getCellSubIdForNetworkCapabilities(mUnderlyingNetworkCapabilities);
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.d(TAG, "Underlying network is not a cellular network");
- return AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+ return null;
}
synchronized (Vpn.this) {
- if (mCachedKeepalivePerSubId.contains(subId)) {
- Log.d(TAG, "Get cached keepalive config");
- return mCachedKeepalivePerSubId.get(subId);
+ if (mCachedCarrierConfigInfoPerSubId.contains(subId)) {
+ Log.d(TAG, "Get cached config");
+ return mCachedCarrierConfigInfoPerSubId.get(subId);
}
-
- final TelephonyManager perSubTm = mTelephonyManager.createForSubscriptionId(subId);
- if (perSubTm.getSimApplicationState() != TelephonyManager.SIM_STATE_LOADED) {
- Log.d(TAG, "SIM card is not ready on sub " + subId);
- return AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
- }
-
- final PersistableBundle carrierConfig =
- mCarrierConfigManager.getConfigForSubId(subId);
- if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
- return AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
- }
-
- final int natKeepalive =
- carrierConfig.getInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT);
- mCachedKeepalivePerSubId.put(subId, natKeepalive);
- Log.d(TAG, "Get customized keepalive=" + natKeepalive);
- return natKeepalive;
}
+
+ final TelephonyManager perSubTm = mTelephonyManager.createForSubscriptionId(subId);
+ if (perSubTm.getSimApplicationState() != TelephonyManager.SIM_STATE_LOADED) {
+ Log.d(TAG, "SIM card is not ready on sub " + subId);
+ return null;
+ }
+
+ final PersistableBundle carrierConfig =
+ mCarrierConfigManager.getConfigForSubId(subId);
+ if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
+ return null;
+ }
+
+ final int natKeepalive =
+ carrierConfig.getInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT);
+ final int preferredIpPortocol =
+ carrierConfig.getInt(KEY_PREFERRED_IKE_PROTOCOL_INT);
+ final String mccMnc = perSubTm.getSimOperator(subId);
+ final CarrierConfigInfo info =
+ buildCarrierConfigInfo(mccMnc, natKeepalive, preferredIpPortocol);
+ synchronized (Vpn.this) {
+ mCachedCarrierConfigInfoPerSubId.put(subId, info);
+ }
+
+ return info;
+ }
+
+ private CarrierConfigInfo buildCarrierConfigInfo(String mccMnc,
+ int natKeepalive, int preferredIpPortocol) {
+ final int ipVersion;
+ final int encapType;
+ switch (preferredIpPortocol) {
+ case PREFERRED_IKE_PROTOCOL_AUTO:
+ ipVersion = IkeSessionParams.ESP_IP_VERSION_AUTO;
+ encapType = IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
+ break;
+ case PREFERRED_IKE_PROTOCOL_IPV4_UDP:
+ ipVersion = IkeSessionParams.ESP_IP_VERSION_IPV4;
+ encapType = IkeSessionParams.ESP_ENCAP_TYPE_UDP;
+ break;
+ case PREFERRED_IKE_PROTOCOL_IPV6_UDP:
+ ipVersion = IkeSessionParams.ESP_IP_VERSION_IPV6;
+ encapType = IkeSessionParams.ESP_ENCAP_TYPE_UDP;
+ break;
+ case PREFERRED_IKE_PROTOCOL_IPV6_ESP:
+ ipVersion = IkeSessionParams.ESP_IP_VERSION_IPV6;
+ encapType = IkeSessionParams.ESP_ENCAP_TYPE_NONE;
+ break;
+ default:
+ ipVersion = IkeSessionParams.ESP_IP_VERSION_AUTO;
+ encapType = IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
+ break;
+ }
+ return new CarrierConfigInfo(mccMnc, natKeepalive, encapType, ipVersion);
}
boolean maybeMigrateIkeSession(@NonNull Network underlyingNetwork) {
@@ -3440,10 +3558,22 @@
+ mCurrentToken
+ " to network "
+ underlyingNetwork);
- final int ipVersion = mProfile.isAutomaticIpVersionSelectionEnabled()
- ? guessEspIpVersionForNetwork() : ESP_IP_VERSION_AUTO;
- final int encapType = mProfile.isAutomaticIpVersionSelectionEnabled()
- ? guessEspEncapTypeForNetwork() : ESP_ENCAP_TYPE_AUTO;
+
+ final int ipVersion;
+ final int encapType;
+ if (mProfile.isAutomaticIpVersionSelectionEnabled()) {
+ ipVersion = guessEspIpVersionForNetwork();
+ encapType = guessEspEncapTypeForNetwork();
+ } else if (mProfile.getIkeTunnelConnectionParams() != null) {
+ ipVersion = mProfile.getIkeTunnelConnectionParams()
+ .getIkeSessionParams().getIpVersion();
+ encapType = mProfile.getIkeTunnelConnectionParams()
+ .getIkeSessionParams().getEncapType();
+ } else {
+ ipVersion = ESP_IP_VERSION_AUTO;
+ encapType = ESP_ENCAP_TYPE_AUTO;
+ }
+
final int keepaliveDelaySeconds;
if (mProfile.isAutomaticNattKeepaliveTimerEnabled()) {
keepaliveDelaySeconds = guessNattKeepaliveTimerForNetwork();
@@ -4884,7 +5014,7 @@
pw.println("Reset session scheduled");
}
}
- pw.println("mCachedKeepalivePerSubId=" + mCachedKeepalivePerSubId);
+ pw.println("mCachedCarrierConfigInfoPerSubId=" + mCachedCarrierConfigInfoPerSubId);
pw.println("mUnderlyNetworkChanges (most recent first):");
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index ce68edbb..70d7bde 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -52,11 +52,6 @@
private static final String POLICY_DIR_PREFIX = "policy";
private static final String RELATED_CPUS_FILE = "related_cpus";
private static final String AFFECTED_CPUS_FILE = "affected_cpus";
- // TODO(b/263154344): Avoid reading from cpuinfo_cur_freq because non-root users don't have
- // read permission for this file. The file permissions are set by the Kernel. Instead, read
- // the current frequency only from scaling_cur_freq.
- private static final String CUR_CPUFREQ_FILE = "cpuinfo_cur_freq";
- private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq";
private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq";
private static final String MAX_SCALING_FREQ_FILE = "scaling_max_freq";
private static final String TIME_IN_STATE_FILE = "stats/time_in_state";
@@ -207,26 +202,16 @@
Slogf.w(TAG, "Missing dynamic policy info for policy ID %d", policyId);
continue;
}
- long curFreqKHz = CpuInfo.MISSING_FREQUENCY;
- long maxFreqKHz = CpuInfo.MISSING_FREQUENCY;
- if (dynamicPolicyInfo.curCpuFreqPair.cpuFreqKHz != CpuInfo.MISSING_FREQUENCY
- && staticPolicyInfo.maxCpuFreqPair.cpuFreqKHz != CpuInfo.MISSING_FREQUENCY) {
- curFreqKHz = dynamicPolicyInfo.curCpuFreqPair.cpuFreqKHz;
- maxFreqKHz = staticPolicyInfo.maxCpuFreqPair.cpuFreqKHz;
- } else if (dynamicPolicyInfo.curCpuFreqPair.scalingFreqKHz != CpuInfo.MISSING_FREQUENCY
- && staticPolicyInfo.maxCpuFreqPair.scalingFreqKHz
- != CpuInfo.MISSING_FREQUENCY) {
- curFreqKHz = dynamicPolicyInfo.curCpuFreqPair.scalingFreqKHz;
- maxFreqKHz = staticPolicyInfo.maxCpuFreqPair.scalingFreqKHz;
- } else {
+ if (dynamicPolicyInfo.curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY
+ || staticPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
Slogf.w(TAG, "Current and maximum CPU frequency information mismatch/missing for"
+ " policy ID %d", policyId);
continue;
}
- if (curFreqKHz > maxFreqKHz) {
+ if (dynamicPolicyInfo.curCpuFreqKHz > staticPolicyInfo.maxCpuFreqKHz) {
Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
- + " (%d) for policy ID (%d). Skipping CPU frequency policy", curFreqKHz,
- maxFreqKHz, policyId);
+ + " (%d) for policy ID (%d). Skipping CPU frequency policy",
+ dynamicPolicyInfo.curCpuFreqKHz, staticPolicyInfo.maxCpuFreqKHz, policyId);
continue;
}
for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
@@ -249,7 +234,7 @@
if (dynamicPolicyInfo.affectedCpuCores.indexOf(relatedCpuCore) < 0) {
cpuInfoByCpus.append(relatedCpuCore, new CpuInfo(relatedCpuCore,
cpusetCategories, /* isOnline= */false, CpuInfo.MISSING_FREQUENCY,
- maxFreqKHz, CpuInfo.MISSING_FREQUENCY, usageStats));
+ staticPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY, usageStats));
continue;
}
// If a CPU core is online, it must have the usage stats. When the usage stats is
@@ -260,8 +245,8 @@
continue;
}
CpuInfo cpuInfo = new CpuInfo(relatedCpuCore, cpusetCategories, /* isOnline= */true,
- curFreqKHz, maxFreqKHz, dynamicPolicyInfo.avgTimeInStateCpuFreqKHz,
- usageStats);
+ dynamicPolicyInfo.curCpuFreqKHz, staticPolicyInfo.maxCpuFreqKHz,
+ dynamicPolicyInfo.avgTimeInStateCpuFreqKHz, usageStats);
cpuInfoByCpus.append(relatedCpuCore, cpuInfo);
if (DEBUG) {
Slogf.d(TAG, "Added %s for CPU core %d", cpuInfo, relatedCpuCore);
@@ -438,8 +423,8 @@
for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
int policyId = mCpuFreqPolicyDirsById.keyAt(i);
File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
- FrequencyPair maxCpuFreqPair = readMaxCpuFrequency(policyDir);
- if (maxCpuFreqPair.isEmpty()) {
+ long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
+ if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
Slogf.w(TAG, "Missing max CPU frequency information at %s",
policyDir.getAbsolutePath());
continue;
@@ -451,7 +436,7 @@
cpuCoresFile.getAbsolutePath());
continue;
}
- StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqPair,
+ StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqKHz,
relatedCpuCores);
mStaticPolicyInfoById.append(policyId, staticPolicyInfo);
if (DEBUG) {
@@ -461,18 +446,13 @@
}
}
- private FrequencyPair readMaxCpuFrequency(File policyDir) {
- return new FrequencyPair(readCpuFreqKHz(new File(policyDir, MAX_CPUFREQ_FILE)),
- readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE)));
- }
-
private SparseArray<DynamicPolicyInfo> readDynamicPolicyInfo() {
SparseArray<DynamicPolicyInfo> dynamicPolicyInfoById = new SparseArray<>();
for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
int policyId = mCpuFreqPolicyDirsById.keyAt(i);
File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
- FrequencyPair curCpuFreqPair = readCurrentCpuFrequency(policyDir);
- if (curCpuFreqPair.isEmpty()) {
+ long curCpuFreqKHz = readCpuFreqKHz(new File(policyDir, CUR_SCALING_FREQ_FILE));
+ if (curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
Slogf.w(TAG, "Missing current frequency information at %s",
policyDir.getAbsolutePath());
continue;
@@ -484,7 +464,7 @@
Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
continue;
}
- DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqPair,
+ DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqKHz,
avgTimeInStateCpuFreqKHz, affectedCpuCores);
dynamicPolicyInfoById.append(policyId, dynamicPolicyInfo);
if (DEBUG) {
@@ -495,11 +475,6 @@
return dynamicPolicyInfoById;
}
- private FrequencyPair readCurrentCpuFrequency(File policyDir) {
- return new FrequencyPair(readCpuFreqKHz(new File(policyDir, CUR_CPUFREQ_FILE)),
- readCpuFreqKHz(new File(policyDir, CUR_SCALING_FREQ_FILE)));
- }
-
private long readAvgTimeInStateCpuFrequency(int policyId, File policyDir) {
LongSparseLongArray latestTimeInState = readTimeInState(policyDir);
if (latestTimeInState == null || latestTimeInState.size() == 0) {
@@ -913,58 +888,37 @@
}
}
- private static final class FrequencyPair {
- public final long cpuFreqKHz;
- public final long scalingFreqKHz;
-
- FrequencyPair(long cpuFreqKHz, long scalingFreqKHz) {
- this.cpuFreqKHz = cpuFreqKHz;
- this.scalingFreqKHz = scalingFreqKHz;
- }
-
- boolean isEmpty() {
- return cpuFreqKHz == CpuInfo.MISSING_FREQUENCY
- && scalingFreqKHz == CpuInfo.MISSING_FREQUENCY;
- }
-
- @Override
- public String toString() {
- return "FrequencyPair{cpuFreqKHz = " + cpuFreqKHz + ", scalingFreqKHz = "
- + scalingFreqKHz + '}';
- }
- }
-
private static final class StaticPolicyInfo {
- public final FrequencyPair maxCpuFreqPair;
+ public final long maxCpuFreqKHz;
public final IntArray relatedCpuCores;
- StaticPolicyInfo(FrequencyPair maxCpuFreqPair, IntArray relatedCpuCores) {
- this.maxCpuFreqPair = maxCpuFreqPair;
+ StaticPolicyInfo(long maxCpuFreqKHz, IntArray relatedCpuCores) {
+ this.maxCpuFreqKHz = maxCpuFreqKHz;
this.relatedCpuCores = relatedCpuCores;
}
@Override
public String toString() {
- return "StaticPolicyInfo{maxCpuFreqPair = " + maxCpuFreqPair + ", relatedCpuCores = "
+ return "StaticPolicyInfo{maxCpuFreqKHz = " + maxCpuFreqKHz + ", relatedCpuCores = "
+ relatedCpuCores + '}';
}
}
private static final class DynamicPolicyInfo {
- public final FrequencyPair curCpuFreqPair;
+ public final long curCpuFreqKHz;
public final long avgTimeInStateCpuFreqKHz;
public final IntArray affectedCpuCores;
- DynamicPolicyInfo(FrequencyPair curCpuFreqPair, long avgTimeInStateCpuFreqKHz,
+ DynamicPolicyInfo(long curCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
IntArray affectedCpuCores) {
- this.curCpuFreqPair = curCpuFreqPair;
+ this.curCpuFreqKHz = curCpuFreqKHz;
this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
this.affectedCpuCores = affectedCpuCores;
}
@Override
public String toString() {
- return "DynamicPolicyInfo{curCpuFreqPair = " + curCpuFreqPair
+ return "DynamicPolicyInfo{curCpuFreqKHz = " + curCpuFreqKHz
+ ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
+ ", affectedCpuCores = " + affectedCpuCores + '}';
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ea157c8..e01aa9b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1514,14 +1514,17 @@
}
}
- // When calling setContentRecordingSession into the WindowManagerService, the WMS
+ // When calling WindowManagerService#setContentRecordingSession, WindowManagerService
// attempts to acquire a lock before executing its main body. Due to this, we need
// to be sure that it isn't called while the DisplayManagerService is also holding
// a lock, to avoid a deadlock scenario.
final ContentRecordingSession session =
virtualDisplayConfig.getContentRecordingSession();
-
- if (displayId != Display.INVALID_DISPLAY && session != null) {
+ // Ensure session details are only set when mirroring (through VirtualDisplay flags or
+ // MediaProjection).
+ final boolean shouldMirror =
+ projection != null || (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0;
+ if (shouldMirror && displayId != Display.INVALID_DISPLAY && session != null) {
// Only attempt to set content recording session if there are details to set and a
// VirtualDisplay has been successfully constructed.
session.setDisplayId(displayId);
@@ -1529,8 +1532,8 @@
// We set the content recording session here on the server side instead of using
// a second AIDL call in MediaProjection. By ensuring that a virtual display has
// been constructed before calling setContentRecordingSession, we avoid a race
- // condition between the DMS & WMS which could lead to the MediaProjection
- // being pre-emptively torn down.
+ // condition between the DisplayManagerService & WindowManagerService which could
+ // lead to the MediaProjection being pre-emptively torn down.
if (!mWindowManagerInternal.setContentRecordingSession(session)) {
// Unable to start mirroring, so tear down projection & release VirtualDisplay.
try {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e12cd8c..656882f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1056,10 +1056,11 @@
}
float userLux = BrightnessMappingStrategy.NO_USER_LUX;
- float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ float userNits = -1;
if (mInteractiveModeBrightnessMapper != null) {
userLux = mInteractiveModeBrightnessMapper.getUserLux();
- userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness);
}
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
@@ -1179,6 +1180,13 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
+ float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ if (userNits >= 0) {
+ userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
+ if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+ userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ }
+ }
mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
this, handler.getLooper(), mSensorManager, mLightSensor,
mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index fbc354e..3e01222 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -893,10 +893,11 @@
}
float userLux = BrightnessMappingStrategy.NO_USER_LUX;
- float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ float userNits = -1;
if (mInteractiveModeBrightnessMapper != null) {
userLux = mInteractiveModeBrightnessMapper.getUserLux();
- userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ float userBrightness = mInteractiveModeBrightnessMapper.getUserBrightness();
+ userNits = mInteractiveModeBrightnessMapper.convertToNits(userBrightness);
}
final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
@@ -1016,6 +1017,13 @@
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.stop();
}
+ float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ if (userNits >= 0) {
+ userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
+ if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+ userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
+ }
+ }
mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
this, handler.getLooper(), mSensorManager, mLightSensor,
mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8d0689f..79984c9 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -911,7 +911,8 @@
final float newHdrSdrRatio;
if (displayNits != DisplayDeviceConfig.NITS_INVALID
&& sdrNits != DisplayDeviceConfig.NITS_INVALID) {
- newHdrSdrRatio = displayNits / sdrNits;
+ // Ensure the ratio stays >= 1.0f as values below that are nonsensical
+ newHdrSdrRatio = Math.max(1.f, displayNits / sdrNits);
} else {
newHdrSdrRatio = Float.NaN;
}
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 3864200..13e29a3 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1444,13 +1444,12 @@
}
public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) {
- if (defaultPeakRefreshRate == null) {
- defaultPeakRefreshRate = (float) mContext.getResources().getInteger(
- R.integer.config_defaultPeakRefreshRate);
- }
-
- if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
- synchronized (mLock) {
+ synchronized (mLock) {
+ if (defaultPeakRefreshRate == null) {
+ setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ false);
+ updateRefreshRateSettingLocked();
+ } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
mDefaultPeakRefreshRate = defaultPeakRefreshRate;
updateRefreshRateSettingLocked();
}
@@ -2115,11 +2114,20 @@
mLowDisplayBrightnessThresholds = displayThresholds;
mLowAmbientBrightnessThresholds = ambientThresholds;
} else {
- // Invalid or empty. Use device default.
- mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
- R.array.config_brightnessThresholdsOfPeakRefreshRate);
- mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
- R.array.config_ambientThresholdsOfPeakRefreshRate);
+ DisplayDeviceConfig displayDeviceConfig;
+ synchronized (mLock) {
+ displayDeviceConfig = mDefaultDisplayDeviceConfig;
+ }
+ mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+ () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
+ R.array.config_brightnessThresholdsOfPeakRefreshRate,
+ displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+ mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+ () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
+ R.array.config_ambientThresholdsOfPeakRefreshRate,
+ displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
}
restartObserver();
}
@@ -2129,7 +2137,15 @@
* DeviceConfig properties.
*/
public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) {
- if (refreshRate != mRefreshRateInLowZone) {
+ if (refreshRate == -1) {
+ // Given there is no value available in DeviceConfig, lets not attempt loading it
+ // from there.
+ synchronized (mLock) {
+ loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ false);
+ }
+ restartObserver();
+ } else if (refreshRate != mRefreshRateInLowZone) {
mRefreshRateInLowZone = refreshRate;
restartObserver();
}
@@ -2142,11 +2158,20 @@
mHighDisplayBrightnessThresholds = displayThresholds;
mHighAmbientBrightnessThresholds = ambientThresholds;
} else {
- // Invalid or empty. Use device default.
- mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray(
- R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
- mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray(
- R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+ DisplayDeviceConfig displayDeviceConfig;
+ synchronized (mLock) {
+ displayDeviceConfig = mDefaultDisplayDeviceConfig;
+ }
+ mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+ () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
+ displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+ mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
+ () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+ () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
+ displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
}
restartObserver();
}
@@ -2156,7 +2181,15 @@
* DeviceConfig properties.
*/
public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) {
- if (refreshRate != mRefreshRateInHighZone) {
+ if (refreshRate == -1) {
+ // Given there is no value available in DeviceConfig, lets not attempt loading it
+ // from there.
+ synchronized (mLock) {
+ loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ false);
+ }
+ restartObserver();
+ } else if (refreshRate != mRefreshRateInHighZone) {
mRefreshRateInHighZone = refreshRate;
restartObserver();
}
@@ -3067,10 +3100,8 @@
new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
.sendToTarget();
- if (refreshRateInLowZone != -1) {
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
- 0).sendToTarget();
- }
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
+ 0).sendToTarget();
int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
@@ -3080,10 +3111,8 @@
new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
.sendToTarget();
- if (refreshRateInHighZone != -1) {
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone,
- 0).sendToTarget();
- }
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone,
+ 0).sendToTarget();
synchronized (mLock) {
final int refreshRateInHbmSunlight =
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b336b95..7a0bf0c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2003,7 +2003,7 @@
@Override
public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
@@ -2017,7 +2017,7 @@
public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
@@ -2040,7 +2040,7 @@
@Override
public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
@@ -2062,7 +2062,7 @@
@Override
public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
@@ -2158,7 +2158,8 @@
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
@@ -3634,7 +3635,8 @@
int unverifiedTargetSdkVersion, @UserIdInt int userId,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
if (editorInfo == null || editorInfo.targetInputMethodUser == null
|| editorInfo.targetInputMethodUser.getIdentifier() != userId) {
@@ -4115,7 +4117,8 @@
@Override
public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
if (mSettings.getCurrentUserId() == userId) {
@@ -4133,7 +4136,8 @@
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
@UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
final int callingUid = Binder.getCallingUid();
@@ -4186,7 +4190,8 @@
public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
@NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
final int callingUid = Binder.getCallingUid();
final ComponentName imeComponentName =
@@ -5412,7 +5417,8 @@
@Override
public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId) {
- mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
if (mSettings.getCurrentUserId() == userId) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index a081dff..5ef89ad 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -54,7 +54,9 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final String DEBUG_PROPERTIES_FILE = "/etc/gps_debug.conf";
+ private static final String DEBUG_PROPERTIES_SYSTEM_FILE = "/etc/gps_debug.conf";
+
+ private static final String DEBUG_PROPERTIES_VENDOR_FILE = "/vendor/etc/gps_debug.conf";
// config.xml properties
private static final String CONFIG_SUPL_HOST = "SUPL_HOST";
@@ -285,7 +287,8 @@
/*
* Overlay carrier properties from a debug configuration file.
*/
- loadPropertiesFromGpsDebugConfig(mProperties);
+ loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_VENDOR_FILE);
+ loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_SYSTEM_FILE);
mEsExtensionSec = getRangeCheckedConfigEsExtensionSec();
logConfigurations();
@@ -392,9 +395,9 @@
}
}
- private void loadPropertiesFromGpsDebugConfig(Properties properties) {
+ private void loadPropertiesFromGpsDebugConfig(Properties properties, String filePath) {
try {
- File file = new File(DEBUG_PROPERTIES_FILE);
+ File file = new File(filePath);
FileInputStream stream = null;
try {
stream = new FileInputStream(file);
@@ -403,7 +406,7 @@
IoUtils.closeQuietly(stream);
}
} catch (IOException e) {
- if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + DEBUG_PROPERTIES_FILE);
+ if (DEBUG) Log.d(TAG, "Could not open GPS configuration file " + filePath);
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 1aee345..f107d0b 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -106,6 +106,14 @@
case COMMAND_HELP:
onHelp();
return 0;
+ case COMMAND_GET_DISABLED:
+ runGetDisabled();
+ return 0;
+ case COMMAND_SET_DISABLED:
+ // Note: if the user has an LSKF, then this has no immediate effect but instead
+ // just ensures the lockscreen will be disabled later when the LSKF is cleared.
+ runSetDisabled();
+ return 0;
}
if (!checkCredential()) {
return -1;
@@ -124,15 +132,9 @@
case COMMAND_CLEAR:
success = runClear();
break;
- case COMMAND_SET_DISABLED:
- runSetDisabled();
- break;
case COMMAND_VERIFY:
runVerify();
break;
- case COMMAND_GET_DISABLED:
- runGetDisabled();
- break;
default:
getErrPrintWriter().println("Unknown command: " + cmd);
break;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index fa53a607..0e5e55c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
@@ -122,14 +123,14 @@
}
private static int getDbVersion(Context context) {
- // TODO(b/254335492): Check flag
+ // TODO(b/254335492): Update to version 7 and clean up code.
return DATABASE_VERSION;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_KEYS_ENTRY);
- if (db.getVersion() == 6) {
+ if (db.getVersion() == 6) { // always false
db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
} else {
db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V7);
@@ -147,37 +148,47 @@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion < 2) {
+ try {
+ if (oldVersion < 2) {
+ dropAllKnownTables(db); // Wipe database.
+ onCreate(db);
+ return;
+ }
+
+ if (oldVersion < 3 && newVersion >= 3) {
+ upgradeDbForVersion3(db);
+ oldVersion = 3;
+ }
+
+ if (oldVersion < 4 && newVersion >= 4) {
+ upgradeDbForVersion4(db);
+ oldVersion = 4;
+ }
+
+ if (oldVersion < 5 && newVersion >= 5) {
+ upgradeDbForVersion5(db);
+ oldVersion = 5;
+ }
+
+ if (oldVersion < 6 && newVersion >= 6) {
+ upgradeDbForVersion6(db);
+ oldVersion = 6;
+ }
+
+ if (oldVersion < 7 && newVersion >= 7) {
+ try {
+ upgradeDbForVersion7(db);
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Column was added without version update - ignore error", e);
+ }
+ oldVersion = 7;
+ }
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Recreating recoverablekeystore after unexpected upgrade error.", e);
dropAllKnownTables(db); // Wipe database.
onCreate(db);
return;
}
-
- if (oldVersion < 3 && newVersion >= 3) {
- upgradeDbForVersion3(db);
- oldVersion = 3;
- }
-
- if (oldVersion < 4 && newVersion >= 4) {
- upgradeDbForVersion4(db);
- oldVersion = 4;
- }
-
- if (oldVersion < 5 && newVersion >= 5) {
- upgradeDbForVersion5(db);
- oldVersion = 5;
- }
-
- if (oldVersion < 6 && newVersion >= 6) {
- upgradeDbForVersion6(db);
- oldVersion = 6;
- }
-
- if (oldVersion < 7 && newVersion >= 7) {
- upgradeDbForVersion7(db);
- oldVersion = 7;
- }
-
if (oldVersion != newVersion) {
Log.e(TAG, "Failed to update recoverablekeystore database to the most recent version");
}
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 647a89e..6f0903c 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -25,6 +25,7 @@
import android.content.IntentFilter;
import android.telecom.TelecomManager;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.util.NotificationMessagingUtil;
import java.util.Comparator;
@@ -38,6 +39,7 @@
private final Context mContext;
private final NotificationMessagingUtil mMessagingUtil;
+ private final boolean mSortByInterruptiveness;
private String mDefaultPhoneApp;
public NotificationComparator(Context context) {
@@ -45,6 +47,8 @@
mContext.registerReceiver(mPhoneAppBroadcastReceiver,
new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
mMessagingUtil = new NotificationMessagingUtil(mContext);
+ mSortByInterruptiveness = !SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+ SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS);
}
@Override
@@ -135,10 +139,12 @@
return -1 * Integer.compare(leftPriority, rightPriority);
}
- final boolean leftInterruptive = left.isInterruptive();
- final boolean rightInterruptive = right.isInterruptive();
- if (leftInterruptive != rightInterruptive) {
- return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
+ if (mSortByInterruptiveness) {
+ final boolean leftInterruptive = left.isInterruptive();
+ final boolean rightInterruptive = right.isInterruptive();
+ if (leftInterruptive != rightInterruptive) {
+ return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
+ }
}
// then break ties by time, most recent first
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c607eca..f09f7c2 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7803,7 +7803,8 @@
*/
@GuardedBy("mNotificationLock")
@VisibleForTesting
- protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) {
+ protected boolean isVisuallyInterruptive(@Nullable NotificationRecord old,
+ @NonNull NotificationRecord r) {
// Ignore summary updates because we don't display most of the information.
if (r.getSbn().isGroup() && r.getSbn().getNotification().isGroupSummary()) {
if (DEBUG_INTERRUPTIVENESS) {
@@ -7821,14 +7822,6 @@
return true;
}
- if (r == null) {
- if (DEBUG_INTERRUPTIVENESS) {
- Slog.v(TAG, "INTERRUPTIVENESS: "
- + r.getKey() + " is not interruptive: null");
- }
- return false;
- }
-
Notification oldN = old.getSbn().getNotification();
Notification newN = r.getSbn().getNotification();
if (oldN.extras == null || newN.extras == null) {
@@ -7886,6 +7879,14 @@
return true;
}
+ if (Notification.areIconsDifferent(oldN, newN)) {
+ if (DEBUG_INTERRUPTIVENESS) {
+ Slog.v(TAG, "INTERRUPTIVENESS: "
+ + r.getKey() + " is interruptive: icons differ");
+ }
+ return true;
+ }
+
// Fields below are invisible to bubbles.
if (r.canBubble()) {
if (DEBUG_INTERRUPTIVENESS) {
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 2e86df8..f95f7bc 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -18,7 +18,6 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.PackageManagerService.TAG;
import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
@@ -245,7 +244,7 @@
}
}
- if (!useArtService()) { // ART Service handles this on demand instead.
+ if (!DexOptHelper.useArtService()) { // ART Service handles this on demand instead.
// Prepare the application profiles only for upgrades and
// first boot (so that we don't repeat the same operation at
// each boot).
@@ -591,7 +590,7 @@
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
- if (useArtService()) {
+ if (DexOptHelper.useArtService()) {
destroyAppProfilesWithArtService(pkg);
} else {
try {
@@ -637,7 +636,7 @@
}
private void destroyAppProfilesLeafLIF(AndroidPackage pkg) {
- if (useArtService()) {
+ if (DexOptHelper.useArtService()) {
destroyAppProfilesWithArtService(pkg);
} else {
try {
@@ -651,6 +650,15 @@
}
private void destroyAppProfilesWithArtService(AndroidPackage pkg) {
+ if (!DexOptHelper.artManagerLocalIsInitialized()) {
+ // This function may get called while PackageManagerService is constructed (via e.g.
+ // InitAppsHelper.initSystemApps), and ART Service hasn't yet been started then (it
+ // requires a registered PackageManagerLocal instance). We can skip clearing any stale
+ // app profiles in this case, because ART Service and the runtime will ignore stale or
+ // otherwise invalid ref and cur profiles.
+ return;
+ }
+
try (PackageManagerLocal.FilteredSnapshot snapshot =
getPackageManagerLocal().withFilteredSnapshot()) {
try {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index a9d4115..064be7c 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -99,6 +99,8 @@
public final class DexOptHelper {
private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
+ private static boolean sArtManagerLocalIsInitialized = false;
+
private final PackageManagerService mPm;
// Start time for the boot dexopt in performPackageDexOptUpgradeIfNeeded when ART Service is
@@ -1035,6 +1037,7 @@
artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
pm.getDexOptHelper().new DexoptDoneHandler());
LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
+ sArtManagerLocalIsInitialized = true;
// Schedule the background job when boot is complete. This decouples us from when
// JobSchedulerService is initialized.
@@ -1048,6 +1051,15 @@
}
/**
+ * Returns true if an {@link ArtManagerLocal} instance has been created.
+ *
+ * Avoid this function if at all possible, because it may hide initialization order problems.
+ */
+ public static boolean artManagerLocalIsInitialized() {
+ return sArtManagerLocalIsInitialized;
+ }
+
+ /**
* Returns the registered {@link ArtManagerLocal} instance, or else throws an unchecked error.
*/
public static @NonNull ArtManagerLocal getArtManagerLocal() {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7fe6c7d..3ac7aa7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3279,7 +3279,7 @@
final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
removePackageHelper.removePackage(stubPkg, true /*chatty*/);
try {
- return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
+ return initPackageTracedLI(scanFile, parseFlags, scanFlags);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
e);
@@ -3410,8 +3410,7 @@
| ParsingPackageUtils.PARSE_MUST_BE_APK
| ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
@PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
- final AndroidPackage pkg = scanSystemPackageTracedLI(
- codePath, parseFlags, scanFlags, null);
+ final AndroidPackage pkg = initPackageTracedLI(codePath, parseFlags, scanFlags);
synchronized (mPm.mLock) {
PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -3591,7 +3590,7 @@
try {
final File codePath = new File(pkg.getPath());
synchronized (mPm.mInstallLock) {
- scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
+ initPackageTracedLI(codePath, 0, scanFlags);
}
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse updated, ex-system package: "
@@ -3734,12 +3733,6 @@
String errorMsg = null;
if (throwable == null) {
- // TODO(b/194319951): move lower in the scan chain
- // Static shared libraries have synthetic package names
- if (parseResult.parsedPackage.isStaticSharedLibrary()) {
- PackageManagerService.renameStaticSharedLibraryPackage(
- parseResult.parsedPackage);
- }
try {
addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
@@ -3804,8 +3797,8 @@
try {
synchronized (mPm.mInstallLock) {
- final AndroidPackage newPkg = scanSystemPackageTracedLI(
- scanFile, reparseFlags, rescanFlags, null);
+ final AndroidPackage newPkg = initPackageTracedLI(
+ scanFile, reparseFlags, rescanFlags);
// We rescanned a stub, add it to the list of stubbed system packages
if (newPkg.isStub()) {
stubSystemApps.add(packageName);
@@ -3819,28 +3812,26 @@
}
/**
- * Traces a package scan.
- * @see #scanSystemPackageLI(File, int, int, UserHandle)
+ * Traces a package scan and registers it with the system.
+ * @see #initPackageLI(File, int, int)
*/
@GuardedBy("mPm.mInstallLock")
- public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
- int scanFlags, @Nullable ApexManager.ActiveApexInfo apexInfo)
+ public AndroidPackage initPackageTracedLI(File scanFile, final int parseFlags, int scanFlags)
throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
try {
- return scanSystemPackageLI(scanFile, parseFlags, scanFlags, apexInfo);
+ return initPackageLI(scanFile, parseFlags, scanFlags);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
/**
- * Scans a package and returns the newly parsed package.
+ * Scans a package, registers it with the system and returns the newly parsed package.
* Returns {@code null} in case of errors and the error code is stored in mLastScanError
*/
@GuardedBy("mPm.mInstallLock")
- private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
- @Nullable ApexManager.ActiveApexInfo apexInfo)
+ private AndroidPackage initPackageLI(File scanFile, int parseFlags, int scanFlags)
throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
@@ -3852,13 +3843,8 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- // Static shared libraries have synthetic package names
- if (parsedPackage.isStaticSharedLibrary()) {
- PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
- }
-
return addForInitLI(parsedPackage, parseFlags, scanFlags,
- new UserHandle(UserHandle.USER_SYSTEM), apexInfo);
+ new UserHandle(UserHandle.USER_SYSTEM), null);
}
/**
@@ -3882,6 +3868,10 @@
throws PackageManagerException {
PackageSetting disabledPkgSetting;
synchronized (mPm.mLock) {
+ // Static shared libraries have synthetic package names
+ if (activeApexInfo == null && parsedPackage.isStaticSharedLibrary()) {
+ PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
+ }
disabledPkgSetting =
mPm.mSettings.getDisabledSystemPkgLPr(parsedPackage.getPackageName());
if (activeApexInfo != null && disabledPkgSetting != null) {
@@ -4286,10 +4276,14 @@
deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
mPm.mUserManager.getUserIds(), 0, null, false);
}
- } else if (newPkgVersionGreater) {
+ } else if (newPkgVersionGreater || newSharedUserSetting) {
// The application on /system is newer than the application on /data.
// Simply remove the application on /data [keeping application data]
// and replace it with the version on /system.
+ // Also, if the sharedUserSetting of the application on /system is different
+ // from the sharedUserSetting on data, we should trust the sharedUserSetting
+ // on /system, even if the application version on /system is smaller than
+ // the version on /data.
logCriticalInfo(Log.WARN,
"System package enabled;"
+ " name: " + pkgSetting.getPackageName()
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 0d417e4..68c8abf 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -821,8 +821,10 @@
* Creates an oat dir for given package and instruction set.
*/
public void createOatDir(String packageName, String oatDir, String dexInstructionSet)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
+ throws InstallerException {
+ // This method should be allowed even if ART Service is enabled, because it's used for
+ // creating oat dirs before creating hard links for partial installation.
+ // TODO(b/274658735): Add an ART Service API to support hard linking.
if (!checkBeforeRemote()) return;
try {
mInstalld.createOatDir(packageName, oatDir, dexInstructionSet);
@@ -1177,7 +1179,7 @@
// TODO(b/260124949): Remove the legacy dexopt code paths, i.e. this exception and all code
// that may throw it.
public LegacyDexoptDisabledException() {
- super("Invalid call to legacy dexopt installd method while ART Service is in use.");
+ super("Invalid call to legacy dexopt method while ART Service is in use.");
}
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 7f7a234..83d2f6a 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -132,14 +132,15 @@
// Not found or complete.
break;
}
- if (!streaming && state.timeoutExtended()) {
+
+ final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
+ if (!streaming && state.timeoutExtended(response.callerUid)) {
// Timeout extended.
break;
}
- final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
- VerificationUtils.processVerificationResponse(verificationId, state, response,
- "Verification timed out", mPm);
+ VerificationUtils.processVerificationResponseOnTimeout(verificationId, state,
+ response, mPm);
break;
}
@@ -195,8 +196,7 @@
}
final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
- VerificationUtils.processVerificationResponse(verificationId, state, response,
- "Install not allowed", mPm);
+ VerificationUtils.processVerificationResponse(verificationId, state, response, mPm);
break;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 03e0d36..36aeca1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -49,7 +49,6 @@
import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.internal.util.XmlUtils.writeUriAttribute;
-import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
@@ -173,7 +172,6 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -2560,15 +2558,9 @@
}
if (isLinkPossible(fromFiles, toDir)) {
- if (!useArtService()) { // ART Service creates oat dirs on demand instead.
- if (!mResolvedInstructionSets.isEmpty()) {
- final File oatDir = new File(toDir, "oat");
- try {
- createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ if (!mResolvedInstructionSets.isEmpty()) {
+ final File oatDir = new File(toDir, "oat");
+ createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
}
// pre-create lib dirs for linking if necessary
if (!mResolvedNativeLibPaths.isEmpty()) {
@@ -3829,7 +3821,7 @@
}
private void createOatDirs(String packageName, List<String> instructionSets, File fromDir)
- throws PackageManagerException, LegacyDexoptDisabledException {
+ throws PackageManagerException {
for (String instructionSet : instructionSets) {
try {
mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bc8760..a6faff8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4885,14 +4885,11 @@
mHandler.post(() -> {
final int id = verificationId >= 0 ? verificationId : -verificationId;
final PackageVerificationState state = mPendingVerification.get(id);
- if (state == null || state.timeoutExtended() || !state.checkRequiredVerifierUid(
- callingUid)) {
- // Only allow calls from required verifiers.
+ if (state == null || !state.extendTimeout(callingUid)) {
+ // Invalid uid or already extended.
return;
}
- state.extendTimeout();
-
final PackageVerificationResponse response = new PackageVerificationResponse(
verificationCodeAtTimeout, callingUid);
@@ -5561,32 +5558,18 @@
public void registerDexModule(String packageName, String dexModulePath,
boolean isSharedModule,
IDexModuleRegisterCallback callback) {
- if (useArtService()) {
- // ART Service currently doesn't support this explicit dexopting and instead relies
- // on background dexopt for secondary dex files. This API is problematic since it
- // doesn't provide the correct classloader context.
- Slog.i(TAG,
- "Ignored unsupported registerDexModule call for " + dexModulePath + " in "
- + packageName);
- return;
- }
-
- int userId = UserHandle.getCallingUserId();
- ApplicationInfo ai = snapshot().getApplicationInfo(packageName, /*flags*/ 0, userId);
- DexManager.RegisterDexModuleResult result;
- if (ai == null) {
- Slog.w(PackageManagerService.TAG,
- "Registering a dex module for a package that does not exist for the" +
- " calling user. package=" + packageName + ", user=" + userId);
- result = new DexManager.RegisterDexModuleResult(false, "Package not installed");
- } else {
- try {
- result = mDexManager.registerDexModule(
- ai, dexModulePath, isSharedModule, userId);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ // ART Service doesn't support this explicit dexopting and instead relies on background
+ // dexopt for secondary dex files. For compat parity between ART Service and the legacy
+ // code it's disabled for both.
+ //
+ // Also, this API is problematic anyway since it doesn't provide the correct classloader
+ // context, so it is hard to produce dexopt artifacts that the runtime can load
+ // successfully.
+ Slog.i(TAG,
+ "Ignored unsupported registerDexModule call for " + dexModulePath + " in "
+ + packageName);
+ DexManager.RegisterDexModuleResult result = new DexManager.RegisterDexModuleResult(
+ false, "registerDexModule call not supported since Android U");
if (callback != null) {
mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
index 929bc1e..0b6ccc4 100644
--- a/services/core/java/com/android/server/pm/PackageVerificationState.java
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -33,6 +33,8 @@
private final SparseBooleanArray mRequiredVerifierUids;
private final SparseBooleanArray mUnrespondedRequiredVerifierUids;
+ private final SparseBooleanArray mExtendedTimeoutUids;
+
private boolean mSufficientVerificationComplete;
private boolean mSufficientVerificationPassed;
@@ -41,8 +43,6 @@
private boolean mRequiredVerificationPassed;
- private boolean mExtendedTimeout;
-
private boolean mIntegrityVerificationComplete;
/**
@@ -54,9 +54,9 @@
mSufficientVerifierUids = new SparseBooleanArray();
mRequiredVerifierUids = new SparseBooleanArray();
mUnrespondedRequiredVerifierUids = new SparseBooleanArray();
+ mExtendedTimeoutUids = new SparseBooleanArray();
mRequiredVerificationComplete = false;
mRequiredVerificationPassed = true;
- mExtendedTimeout = false;
}
VerifyingSession getVerifyingSession() {
@@ -88,14 +88,27 @@
return mSufficientVerifierUids.get(uid, false);
}
+ void setVerifierResponseOnTimeout(int uid, int code) {
+ if (!checkRequiredVerifierUid(uid)) {
+ return;
+ }
+
+ // Timeout, not waiting for the sufficient verifiers anymore.
+ mSufficientVerifierUids.clear();
+
+ // Only if unresponded.
+ if (mUnrespondedRequiredVerifierUids.get(uid, false)) {
+ setVerifierResponse(uid, code);
+ }
+ }
+
/**
* Should be called when a verification is received from an agent so the state of the package
* verification can be tracked.
*
* @param uid user ID of the verifying agent
- * @return {@code true} if the verifying agent actually exists in our list
*/
- boolean setVerifierResponse(int uid, int code) {
+ void setVerifierResponse(int uid, int code) {
if (mRequiredVerifierUids.get(uid)) {
switch (code) {
case PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT:
@@ -109,13 +122,19 @@
break;
default:
mRequiredVerificationPassed = false;
+ // Required verifier rejected, no need to wait for the rest.
+ mUnrespondedRequiredVerifierUids.clear();
+ mSufficientVerifierUids.clear();
+ mExtendedTimeoutUids.clear();
}
+ // Responded, no need to extend timeout.
+ mExtendedTimeoutUids.delete(uid);
+
mUnrespondedRequiredVerifierUids.delete(uid);
if (mUnrespondedRequiredVerifierUids.size() == 0) {
mRequiredVerificationComplete = true;
}
- return true;
} else if (mSufficientVerifierUids.get(uid)) {
if (code == PackageManager.VERIFICATION_ALLOW) {
mSufficientVerificationPassed = true;
@@ -126,11 +145,7 @@
if (mSufficientVerifierUids.size() == 0) {
mSufficientVerificationComplete = true;
}
-
- return true;
}
-
- return false;
}
/**
@@ -181,10 +196,12 @@
}
/** Extend the timeout for this Package to be verified. */
- void extendTimeout() {
- if (!mExtendedTimeout) {
- mExtendedTimeout = true;
+ boolean extendTimeout(int uid) {
+ if (!checkRequiredVerifierUid(uid) || timeoutExtended(uid)) {
+ return false;
}
+ mExtendedTimeoutUids.append(uid, true);
+ return true;
}
/**
@@ -192,8 +209,8 @@
*
* @return {@code true} if a timeout was already extended.
*/
- boolean timeoutExtended() {
- return mExtendedTimeout;
+ boolean timeoutExtended(int uid) {
+ return mExtendedTimeoutUids.get(uid, false);
}
void setIntegrityVerificationResult(int code) {
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 7684a49..8f8f437 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -159,8 +159,8 @@
synchronized (mPm.mInstallLock) {
final AndroidPackage pkg;
try {
- pkg = installPackageHelper.scanSystemPackageTracedLI(
- ps.getPath(), parseFlags, SCAN_INITIAL, null);
+ pkg = installPackageHelper.initPackageTracedLI(
+ ps.getPath(), parseFlags, SCAN_INITIAL);
loaded.add(pkg);
} catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 18eebe4..b4b8cb2 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -619,10 +619,10 @@
final Bundle extras = new Bundle(3);
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
- extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
- null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
- null /* broadcastAllowList */,
+ extras, flags, null /* targetPkg */, null /* finishedReceiver */,
+ new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
(callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
mPm.snapshotComputer(), callingUid, intentExtras),
null /* bOptions */));
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 721ad88..194f237 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -506,15 +506,14 @@
*
* <p>If the user is a profile and is running, it's assigned to its parent display.
*/
- // TODO(b/272366483) rename this method to avoid confusion with getDisplaysAssignedTOUser().
- public abstract int getDisplayAssignedToUser(@UserIdInt int userId);
+ public abstract int getMainDisplayAssignedToUser(@UserIdInt int userId);
/**
* Returns all display ids assigned to the user including {@link
* #assignUserToExtraDisplay(int, int) extra displays}, or {@code null} if there is no display
* assigned to the specified user.
*
- * <p>Note that this method is different from {@link #getDisplayAssignedToUser(int)}, which
+ * <p>Note that this method is different from {@link #getMainDisplayAssignedToUser(int)}, which
* returns a main display only.
*/
public abstract @Nullable int[] getDisplaysAssignedToUser(@UserIdInt int userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7414640..a36e9f9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1275,7 +1275,7 @@
intent.putExtra(Intent.EXTRA_USER_HANDLE, profileHandle.getIdentifier());
getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
intent, parentHandle, /* requiresPermission= */ true);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, parentHandle);
}
@@ -1996,10 +1996,10 @@
}
@Override
- public int getDisplayIdAssignedToUser() {
+ public int getMainDisplayIdAssignedToUser() {
// Not checking for any permission as it returns info about calling user
int userId = UserHandle.getUserId(Binder.getCallingUid());
- int displayId = mUserVisibilityMediator.getDisplayAssignedToUser(userId);
+ int displayId = mUserVisibilityMediator.getMainDisplayAssignedToUser(userId);
return displayId;
}
@@ -7189,8 +7189,8 @@
}
@Override
- public int getDisplayAssignedToUser(@UserIdInt int userId) {
- return mUserVisibilityMediator.getDisplayAssignedToUser(userId);
+ public int getMainDisplayAssignedToUser(@UserIdInt int userId) {
+ return mUserVisibilityMediator.getMainDisplayAssignedToUser(userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 3710af6..cf82536 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -774,9 +774,9 @@
}
/**
- * See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
+ * See {@link UserManagerInternal#getMainDisplayAssignedToUser(int)}.
*/
- public int getDisplayAssignedToUser(@UserIdInt int userId) {
+ public int getMainDisplayAssignedToUser(@UserIdInt int userId) {
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
if (mVisibleBackgroundUserOnDefaultDisplayEnabled) {
// When device supports visible bg users on default display, the default display is
@@ -787,8 +787,8 @@
}
if (userStartedOnDefaultDisplay != USER_NULL) {
if (DBG) {
- Slogf.d(TAG, "getDisplayAssignedToUser(%d): returning INVALID_DISPLAY for "
- + "current user user %d was started on DEFAULT_DISPLAY",
+ Slogf.d(TAG, "getMainDisplayAssignedToUser(%d): returning INVALID_DISPLAY "
+ + "for current user user %d was started on DEFAULT_DISPLAY",
userId, userStartedOnDefaultDisplay);
}
return INVALID_DISPLAY;
@@ -809,7 +809,7 @@
/** See {@link UserManagerInternal#getDisplaysAssignedToUser(int)}. */
@Nullable
public int[] getDisplaysAssignedToUser(@UserIdInt int userId) {
- int mainDisplayId = getDisplayAssignedToUser(userId);
+ int mainDisplayId = getMainDisplayAssignedToUser(userId);
if (mainDisplayId == INVALID_DISPLAY) {
// The user will not have any extra displays if they have no main display.
// Return null if no display is assigned to the user.
diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java
index 30f2132..f061018 100644
--- a/services/core/java/com/android/server/pm/VerificationUtils.java
+++ b/services/core/java/com/android/server/pm/VerificationUtils.java
@@ -18,6 +18,7 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.server.pm.PackageManagerService.PACKAGE_MIME_TYPE;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -32,6 +33,8 @@
import android.provider.Settings;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
final class VerificationUtils {
/**
* The default maximum time to wait for the verification agent to return in
@@ -97,39 +100,63 @@
android.Manifest.permission.PACKAGE_VERIFICATION_AGENT);
}
+ @VisibleForTesting(visibility = PACKAGE)
+ static void processVerificationResponseOnTimeout(int verificationId,
+ PackageVerificationState state, PackageVerificationResponse response,
+ PackageManagerService pms) {
+ state.setVerifierResponseOnTimeout(response.callerUid, response.code);
+ processVerificationResponse(verificationId, state, response.code, "Verification timed out",
+ pms);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
static void processVerificationResponse(int verificationId, PackageVerificationState state,
- PackageVerificationResponse response, String failureReason, PackageManagerService pms) {
+ PackageVerificationResponse response, PackageManagerService pms) {
state.setVerifierResponse(response.callerUid, response.code);
+ processVerificationResponse(verificationId, state, response.code, "Install not allowed",
+ pms);
+ }
+
+ private static void processVerificationResponse(int verificationId,
+ PackageVerificationState state, int verificationResult, String failureReason,
+ PackageManagerService pms) {
if (!state.isVerificationComplete()) {
return;
}
final VerifyingSession verifyingSession = state.getVerifyingSession();
- final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
+ final Uri originUri = verifyingSession != null ? Uri.fromFile(
+ verifyingSession.mOriginInfo.mResolvedFile) : null;
final int verificationCode =
- state.isInstallAllowed() ? response.code : PackageManager.VERIFICATION_REJECT;
+ state.isInstallAllowed() ? verificationResult : PackageManager.VERIFICATION_REJECT;
- VerificationUtils.broadcastPackageVerified(verificationId, originUri,
- verificationCode, null,
- verifyingSession.getDataLoaderType(), verifyingSession.getUser(),
- pms.mContext);
+ if (pms != null && verifyingSession != null) {
+ VerificationUtils.broadcastPackageVerified(verificationId, originUri,
+ verificationCode, null,
+ verifyingSession.getDataLoaderType(), verifyingSession.getUser(),
+ pms.mContext);
+ }
if (state.isInstallAllowed()) {
Slog.i(TAG, "Continuing with installation of " + originUri);
} else {
String errorMsg = failureReason + " for " + originUri;
Slog.i(TAG, errorMsg);
- verifyingSession.setReturnCode(
- PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
+ if (verifyingSession != null) {
+ verifyingSession.setReturnCode(
+ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
+ }
}
- if (state.areAllVerificationsComplete()) {
+ if (pms != null && state.areAllVerificationsComplete()) {
pms.mPendingVerification.remove(verificationId);
}
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
- verifyingSession.handleVerificationFinished();
+ if (verifyingSession != null) {
+ verifyingSession.handleVerificationFinished();
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 7f0c3f9..6e738da 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,7 +16,6 @@
package com.android.server.pm.dex;
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
@@ -659,62 +658,6 @@
}
}
- // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
- // compilation happening here will use a pessimistic context.
- public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
- boolean isSharedModule, int userId) throws LegacyDexoptDisabledException {
- // Find the owning package record.
- DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
-
- if (searchResult.mOutcome == DEX_SEARCH_NOT_FOUND) {
- return new RegisterDexModuleResult(false, "Package not found");
- }
- if (!info.packageName.equals(searchResult.mOwningPackageName)) {
- return new RegisterDexModuleResult(false, "Dex path does not belong to package");
- }
- if (searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY ||
- searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT) {
- return new RegisterDexModuleResult(false, "Main apks cannot be registered");
- }
-
- // We found the package. Now record the usage for all declared ISAs.
- boolean update = false;
- // If this is a shared module set the loading package to an arbitrary package name
- // so that we can mark that module as usedByOthers.
- String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName;
- for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) {
- boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
- dexPath, userId, isa, /*primaryOrSplit*/ false,
- loadingPackage,
- PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT,
- /*overwriteCLC=*/ false);
- update |= newUpdate;
- }
- if (update) {
- mPackageDexUsage.maybeWriteAsync();
- }
-
- DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
- .getDexUseInfoMap().get(dexPath);
-
- // Try to optimize the package according to the install reason.
- DexoptOptions options = new DexoptOptions(info.packageName,
- PackageManagerService.REASON_INSTALL, /*flags*/0);
-
- int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
- options);
-
- // If we fail to optimize the package log an error but don't propagate the error
- // back to the app. The app cannot do much about it and the background job
- // will rety again when it executes.
- // TODO(calin): there might be some value to return the error here but it may
- // cause red herrings since that doesn't mean the app cannot use the module.
- if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
- Slog.e(TAG, "Failed to optimize dex module " + dexPath);
- }
- return new RegisterDexModuleResult(true, "Dex module registered successfully");
- }
-
/**
* Return all packages that contain records of secondary dex files.
*/
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index c0d71ac..579d4e3 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -26,10 +26,10 @@
]
},
{
- "name": "CtsPermission2TestCases",
+ "name": "CtsPermissionPolicyTestCases",
"options": [
{
- "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
+ "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
},
{
"include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index e146135..76a714c 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -93,7 +93,8 @@
mGlobalActionsAvailable = available;
if (mShowing && !mGlobalActionsAvailable) {
// Global actions provider died but we need to be showing global actions still, show the
- // legacy global acrions provider.
+ // legacy global actions provider and remove timeout callbacks to avoid legacy re-show.
+ mHandler.removeCallbacks(mShowTimeout);
ensureLegacyCreated();
mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3eeafeb..b32e8f0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1537,7 +1537,9 @@
private void interceptScreenshotChord(int source, long pressDelay) {
mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
+ // arg2 is unused, but necessary to insure we call the correct method signature
+ // since the screenshot source is read from message.arg1
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source, 0),
pressDelay);
}
@@ -3555,16 +3557,16 @@
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
} else {
- setKeyguardOccludedLw(occluded, true /* notify */);
+ setKeyguardOccludedLw(occluded);
}
}
@Override
- public int applyKeyguardOcclusionChange(boolean notify) {
+ public int applyKeyguardOcclusionChange() {
if (mKeyguardOccludedChanged) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
+ mPendingKeyguardOccluded);
- if (setKeyguardOccludedLw(mPendingKeyguardOccluded, notify)) {
+ if (setKeyguardOccludedLw(mPendingKeyguardOccluded)) {
return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
}
}
@@ -3583,8 +3585,10 @@
*/
private int handleTransitionForKeyguardLw(boolean startKeyguardExitAnimation,
boolean notifyOccluded) {
- final int redoLayout = applyKeyguardOcclusionChange(notifyOccluded);
- if (redoLayout != 0) return redoLayout;
+ if (notifyOccluded) {
+ final int redoLayout = applyKeyguardOcclusionChange();
+ if (redoLayout != 0) return redoLayout;
+ }
if (startKeyguardExitAnimation) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
startKeyguardExitAnimation(SystemClock.uptimeMillis());
@@ -3835,20 +3839,16 @@
}
/**
- * Updates the occluded state of the Keyguard.
+ * Updates the occluded state of the Keyguard immediately via
+ * {@link com.android.internal.policy.IKeyguardService}.
*
* @param isOccluded Whether the Keyguard is occluded by another window.
- * @param notify Notify keyguard occlude status change immediately via
- * {@link com.android.internal.policy.IKeyguardService}.
* @return Whether the flags have changed and we have to redo the layout.
*/
- private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) {
+ private boolean setKeyguardOccludedLw(boolean isOccluded) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
mKeyguardOccludedChanged = false;
- if (isKeyguardOccluded() == isOccluded) {
- return false;
- }
- mKeyguardDelegate.setOccluded(isOccluded, notify);
+ mKeyguardDelegate.setOccluded(isOccluded, true /* notify */);
return mKeyguardDelegate.isShowing();
}
diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING
index 094e70f..9f1cb1a 100644
--- a/services/core/java/com/android/server/policy/TEST_MAPPING
+++ b/services/core/java/com/android/server/policy/TEST_MAPPING
@@ -29,16 +29,16 @@
]
},
{
- "name": "CtsPermission2TestCases",
+ "name": "CtsPermissionPolicyTestCases",
"options": [
{
- "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
+ "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
},
{
- "include-filter": "android.permission2.cts.RestrictedStoragePermissionSharedUidTest"
+ "include-filter": "android.permissionpolicy.cts.RestrictedStoragePermissionSharedUidTest"
},
{
- "include-filter": "android.permission2.cts.RestrictedStoragePermissionTest"
+ "include-filter": "android.permissionpolicy.cts.RestrictedStoragePermissionTest"
}
]
},
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3c4dbf2..5d558e9 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -170,10 +170,10 @@
void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition);
/**
- * @param notify {@code true} if the status change should be immediately notified via
- * {@link com.android.internal.policy.IKeyguardService}
+ * Commit any queued changes to keyguard occlude status that had been deferred during the
+ * start of an animation or transition.
*/
- int applyKeyguardOcclusionChange(boolean notify);
+ int applyKeyguardOcclusionChange();
/**
* Interface to the Window Manager state associated with a particular
diff --git a/services/core/java/com/android/server/powerstats/BatteryTrigger.java b/services/core/java/com/android/server/powerstats/BatteryTrigger.java
index b35cb52..15c1811 100644
--- a/services/core/java/com/android/server/powerstats/BatteryTrigger.java
+++ b/services/core/java/com/android/server/powerstats/BatteryTrigger.java
@@ -59,7 +59,9 @@
if (triggerEnabled) {
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = mContext.registerReceiver(mBatteryLevelReceiver, filter);
- mBatteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ if (batteryStatus != null) {
+ mBatteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 55060a6..f53b52c1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3560,6 +3560,9 @@
}
private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) {
+ if (wallpaper == null) {
+ pw.println(" (null entry)");
+ }
pw.print(" User "); pw.print(wallpaper.userId);
pw.print(": id="); pw.print(wallpaper.wallpaperId);
pw.print(": mWhich="); pw.print(wallpaper.mWhich);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d08293b..38ba7c3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -121,6 +121,7 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
+import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -5205,7 +5206,7 @@
Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
return;
}
- if (visible == mVisibleRequested && visible == mVisible
+ if (visible == mVisibleRequested && visible == mVisible && visible == isClientVisible()
&& mTransitionController.isShellTransitionsEnabled()) {
// For shell transition, it is no-op if there is no state change.
return;
@@ -8362,6 +8363,10 @@
* requested in the config or via an ADB command. For more context see {@link
* LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and
* {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)}
+ * <p>
+ * Note that this is the final step that can change the resolved bounds. After this method
+ * is called, the position of the bounds will be moved to app space as sandboxing if the
+ * activity has a size compat scale.
*/
private void updateResolvedBoundsPosition(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
@@ -8423,6 +8428,24 @@
// Since bounds has changed, the configuration needs to be computed accordingly.
getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+
+ // The position of configuration bounds were calculated in screen space because that is
+ // easier to resolve the relative position in parent container. However, if the activity is
+ // scaled, the position should follow the scale because the configuration will be sent to
+ // the client which is expected to be in a scaled environment.
+ if (mSizeCompatScale != 1f) {
+ final int screenPosX = resolvedBounds.left;
+ final int screenPosY = resolvedBounds.top;
+ final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
+ final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
+ offsetBounds(resolvedConfig, dx, dy);
+ }
+ }
+
+ @NonNull Rect getScreenResolvedBounds() {
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ return mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
}
void recomputeConfiguration() {
@@ -8632,7 +8655,7 @@
resolvedBounds.set(containingBounds);
final float letterboxAspectRatioOverride =
- mLetterboxUiController.getFixedOrientationLetterboxAspectRatio();
+ mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig);
final float desiredAspectRatio =
letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
@@ -9685,9 +9708,36 @@
return;
}
- if (getParent() != null) {
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ final Transition transition = new Transition(TRANSIT_RELAUNCH, 0 /* flags */,
+ mTransitionController, mWmService.mSyncEngine);
+ final Runnable executeRestart = () -> {
+ if (mState != RESTARTING_PROCESS || !attachedToProcess()) {
+ transition.abort();
+ return;
+ }
+ // Request invisible so there will be a change after the activity is restarted
+ // to be visible.
+ setVisibleRequested(false);
+ transition.collect(this);
+ mTransitionController.requestStartTransition(transition, task,
+ null /* remoteTransition */, null /* displayChange */);
+ scheduleStopForRestartProcess();
+ };
+ if (mWmService.mSyncEngine.hasActiveSync()) {
+ mWmService.mSyncEngine.queueSyncSet(
+ () -> mTransitionController.moveToCollecting(transition), executeRestart);
+ } else {
+ mTransitionController.moveToCollecting(transition);
+ executeRestart.run();
+ }
+ } else {
startFreezingScreen();
+ scheduleStopForRestartProcess();
}
+ }
+
+ private void scheduleStopForRestartProcess() {
// The process will be killed until the activity reports stopped with saved state (see
// {@link ActivityTaskManagerService.activityStopped}).
try {
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 9e258cb..d358eb5 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -143,14 +143,15 @@
// Recording has already begun, but update recording since the display is now on.
if (mRecordedWindowContainer == null) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unexpectedly null window container; unable to update recording for "
- + "display %d",
+ "Content Recording: Unexpectedly null window container; unable to update "
+ + "recording for display %d",
mDisplayContent.getDisplayId());
return;
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Display %d was already recording, so apply transformations if necessary",
+ "Content Recording: Display %d was already recording, so apply "
+ + "transformations if necessary",
mDisplayContent.getDisplayId());
// Retrieve the size of the region to record, and continue with the update
// if the bounds or orientation has changed.
@@ -161,8 +162,8 @@
Point surfaceSize = fetchSurfaceSizeIfPresent();
if (surfaceSize != null) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Going ahead with updating recording for display %d to new "
- + "bounds %s and/or orientation %d.",
+ "Content Recording: Going ahead with updating recording for display "
+ + "%d to new bounds %s and/or orientation %d.",
mDisplayContent.getDisplayId(), recordedContentBounds,
recordedContentOrientation);
updateMirroredSurface(mDisplayContent.mWmService.mTransactionFactory.get(),
@@ -171,8 +172,9 @@
// If the surface removed, do nothing. We will handle this via onDisplayChanged
// (the display will be off if the surface is removed).
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unable to update recording for display %d to new bounds %s"
- + " and/or orientation %d, since the surface is not available.",
+ "Content Recording: Unable to update recording for display %d to new "
+ + "bounds %s and/or orientation %d, since the surface is not "
+ + "available.",
mDisplayContent.getDisplayId(), recordedContentBounds,
recordedContentOrientation);
}
@@ -189,8 +191,8 @@
return;
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Display %d has content (%b) so pause recording", mDisplayContent.getDisplayId(),
- mDisplayContent.getLastHasContent());
+ "Content Recording: Display %d has content (%b) so pause recording",
+ mDisplayContent.getDisplayId(), mDisplayContent.getLastHasContent());
// If the display is not on and it is a virtual display, then it no longer has an
// associated surface to write output to.
// If the display now has content, stop mirroring to it.
@@ -231,7 +233,8 @@
*/
private void stopMediaProjection() {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
+ "Content Recording: Stop MediaProjection on virtual display %d",
+ mDisplayContent.getDisplayId());
if (mMediaProjectionManager != null) {
mMediaProjectionManager.stopActiveProjection();
}
@@ -283,13 +286,14 @@
final Point surfaceSize = fetchSurfaceSizeIfPresent();
if (surfaceSize == null) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unable to start recording for display %d since the surface is not "
- + "available.",
+ "Content Recording: Unable to start recording for display %d since the "
+ + "surface is not available.",
mDisplayContent.getDisplayId());
return;
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Display %d has no content and is on, so start recording for state %d",
+ "Content Recording: Display %d has no content and is on, so start recording for "
+ + "state %d",
mDisplayContent.getDisplayId(), mDisplayContent.getDisplay().getState());
// Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
@@ -349,7 +353,7 @@
if (tokenToRecord == null) {
handleStartRecordingFailed();
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unable to start recording due to null token for display %d",
+ "Content Recording: Unable to start recording due to null token for display %d",
mDisplayContent.getDisplayId());
return null;
}
@@ -359,13 +363,14 @@
mDisplayContent.mWmService.mWindowContextListenerController.getContainer(
tokenToRecord);
if (wc == null) {
- // Fall back to screenrecording using the data sent to DisplayManager
+ // Fall back to mirroring using the data sent to DisplayManager
mDisplayContent.mWmService.mDisplayManagerInternal.setWindowManagerMirroring(
mDisplayContent.getDisplayId(), false);
handleStartRecordingFailed();
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unable to retrieve window container to start recording for "
- + "display %d", mDisplayContent.getDisplayId());
+ "Content Recording: Unable to retrieve window container to start "
+ + "recording for display %d",
+ mDisplayContent.getDisplayId());
return null;
}
// TODO(206461622) Migrate to using the RootDisplayArea
@@ -375,7 +380,7 @@
KEY_RECORD_TASK_FEATURE, false)) {
handleStartRecordingFailed();
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unable to record task since feature is disabled %d",
+ "Content Recording: Unable to record task since feature is disabled %d",
mDisplayContent.getDisplayId());
return null;
}
@@ -383,8 +388,9 @@
if (taskToRecord == null) {
handleStartRecordingFailed();
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unable to retrieve task to start recording for "
- + "display %d", mDisplayContent.getDisplayId());
+ "Content Recording: Unable to retrieve task to start recording for "
+ + "display %d",
+ mDisplayContent.getDisplayId());
} else {
taskToRecord.registerWindowContainerListener(this);
}
@@ -394,7 +400,8 @@
// capture for the entire display.
handleStartRecordingFailed();
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Unable to start recording due to invalid region for display %d",
+ "Content Recording: Unable to start recording due to invalid region for "
+ + "display %d",
mDisplayContent.getDisplayId());
return null;
}
@@ -488,8 +495,8 @@
// State of virtual display will change to 'ON' when the surface is set.
// will get event DISPLAY_DEVICE_EVENT_CHANGED
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Provided surface for recording on display %d is not present, so do not"
- + " update the surface",
+ "Content Recording: Provided surface for recording on display %d is not "
+ + "present, so do not update the surface",
mDisplayContent.getDisplayId());
return null;
}
@@ -500,7 +507,7 @@
@Override
public void onRemoved() {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Recorded task is removed, so stop recording on display %d",
+ "Content Recording: Recorded task is removed, so stop recording on display %d",
mDisplayContent.getDisplayId());
unregisterListener();
@@ -551,8 +558,8 @@
mIMediaProjectionManager.stopActiveProjection();
} catch (RemoteException e) {
ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
- "Unable to tell MediaProjectionManagerService to stop the active "
- + "projection: %s",
+ "Content Recording: Unable to tell MediaProjectionManagerService to stop "
+ + "the active projection: %s",
e);
}
}
@@ -568,8 +575,8 @@
height);
} catch (RemoteException e) {
ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
- "Unable to tell MediaProjectionManagerService about resizing the active "
- + "projection: %s",
+ "Content Recording: Unable to tell MediaProjectionManagerService about "
+ + "resizing the active projection: %s",
e);
}
}
@@ -585,8 +592,8 @@
isVisible);
} catch (RemoteException e) {
ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
- "Unable to tell MediaProjectionManagerService about visibility change on "
- + "the active projection: %s",
+ "Content Recording: Unable to tell MediaProjectionManagerService about "
+ + "visibility change on the active projection: %s",
e);
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index 1efc202..d60addc 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -53,35 +53,59 @@
}
/**
- * Updates the current recording session. If a new display is taking over recording, then
- * stops the prior display from recording.
+ * Updates the current recording session.
+ * <p>Handles the following scenarios:
+ * <ul>
+ * <li>Invalid scenarios: The incoming session is malformed, or the incoming session is
+ * identical to the current session</li>
+ * <li>Start Scenario: Starting a new session. Recording begins immediately.</li>
+ * <li>Takeover Scenario: Occurs during a Start Scenario, if a pre-existing session was
+ * in-progress. For example, recording on VirtualDisplay "app_foo" was ongoing. A
+ * session for VirtualDisplay "app_bar" arrives. The controller stops the session on
+ * VirtualDisplay "app_foo" and allows the session for VirtualDisplay "app_bar" to
+ * begin.</li>
+ * <li>Stopping scenario: The incoming session is null and there is currently an ongoing
+ * session. The controller stops recording.</li>
+ * </ul>
*
- * @param incomingSession the new recording session. Should either have a {@code null} token, to
- * stop the current session, or a session on a new/different display
- * than the current session.
- * @param wmService the window manager service
+ * @param incomingSession The incoming recording session (either an update to a current session
+ * or a new session), or null to stop the current session.
+ * @param wmService The window manager service.
*/
void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
@NonNull WindowManagerService wmService) {
- if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
- || ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
- // Ignore an invalid session, or a session for the same display as currently recording.
+ // Invalid scenario: ignore invalid incoming session.
+ if (incomingSession != null && !ContentRecordingSession.isValid(incomingSession)) {
+ return;
+ }
+ // Invalid scenario: ignore identical incoming session.
+ if (ContentRecordingSession.isProjectionOnSameDisplay(mSession, incomingSession)) {
+ // TODO(242833866) if incoming session is no longer waiting to record, allow
+ // the update through.
+
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Ignoring session on same display %d, with an existing "
+ + "session %s",
+ incomingSession.getDisplayId(), mSession.getDisplayId());
return;
}
DisplayContent incomingDisplayContent = null;
+ // Start scenario: recording begins immediately.
if (incomingSession != null) {
- // Recording will start on a new display, possibly taking over from a current session.
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Handle incoming session on display %d, with a pre-existing session %s",
- incomingSession.getDisplayId(),
+ "Content Recording: Handle incoming session on display %d, with a "
+ + "pre-existing session %s", incomingSession.getDisplayId(),
mSession == null ? null : mSession.getDisplayId());
incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
incomingSession.getDisplayId());
incomingDisplayContent.setContentRecordingSession(incomingSession);
+ // TODO(b/270118861) When user grants consent to re-use, explicitly ask ContentRecorder
+ // to update, since no config/display change arrives. Mark recording as black.
}
+ // Takeover and stopping scenario: stop recording on the pre-existing session.
if (mSession != null) {
- // Update the pre-existing display about the new session.
- ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Pause the recording session on display %s",
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Pause the recording session on display %s",
mDisplayContent.getDisplayId());
mDisplayContent.pauseRecording();
mDisplayContent.setContentRecordingSession(null);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a4d475f..ef01cc8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1228,7 +1228,8 @@
private void finishHoldScreenUpdate() {
final boolean hold = mTmpHoldScreenWindow != null;
if (hold && mTmpHoldScreenWindow != mHoldScreenWindow) {
- mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid));
+ mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid,
+ mTmpHoldScreenWindow.mSession.mPackageName));
}
mHoldScreenWindow = mTmpHoldScreenWindow;
mTmpHoldScreenWindow = null;
@@ -5935,7 +5936,7 @@
mOffTokenAcquirer.release(mDisplayId);
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Display %d state is now (%d), so update recording?",
+ "Content Recording: Display %d state is now (%d), so update recording?",
mDisplayId, displayState);
if (lastDisplayState != displayState) {
// If state is on due to surface being added, then start recording.
@@ -6560,7 +6561,7 @@
if (mirrorDisplayId == mDisplayId) {
if (mDisplayId != DEFAULT_DISPLAY) {
ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
- "Attempting to mirror self on %d", mirrorDisplayId);
+ "Content Recording: Attempting to mirror self on %d", mirrorDisplayId);
}
return false;
}
@@ -6570,16 +6571,18 @@
// to mirror the DEFAULT_DISPLAY so instead we just return
DisplayContent mirrorDc = mRootWindowContainer.getDisplayContentOrCreate(mirrorDisplayId);
if (mirrorDc == null && mDisplayId == DEFAULT_DISPLAY) {
- ProtoLog.w(WM_DEBUG_CONTENT_RECORDING, "Found no matching mirror display for id=%d for"
- + " DEFAULT_DISPLAY. Nothing to mirror.", mirrorDisplayId);
+ ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Found no matching mirror display for id=%d for "
+ + "DEFAULT_DISPLAY. Nothing to mirror.",
+ mirrorDisplayId);
return false;
}
if (mirrorDc == null) {
mirrorDc = mRootWindowContainer.getDefaultDisplay();
ProtoLog.w(WM_DEBUG_CONTENT_RECORDING,
- "Attempting to mirror %d from %d but no DisplayContent associated. Changing "
- + "to mirror default display.",
+ "Content Recording: Attempting to mirror %d from %d but no DisplayContent "
+ + "associated. Changing to mirror default display.",
mirrorDisplayId, mDisplayId);
}
@@ -6588,8 +6591,8 @@
.setDisplayId(mDisplayId);
setContentRecordingSession(session);
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Successfully created a ContentRecordingSession for displayId=%d to mirror "
- + "content from displayId=%d",
+ "Content Recording: Successfully created a ContentRecordingSession for "
+ + "displayId=%d to mirror content from displayId=%d",
mDisplayId, mirrorDisplayId);
return true;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 06d108b..86aca3a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -88,6 +88,10 @@
public class DisplayRotation {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM;
+ // Delay in milliseconds when updating config due to folding events. This prevents
+ // config changes and unexpected jumps while folding the device to closed state.
+ private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800;
+
private static class RotationAnimationPair {
@AnimRes
int mEnter;
@@ -1662,6 +1666,7 @@
private boolean mInHalfFoldTransition = false;
private final boolean mIsDisplayAlwaysSeparatingHinge;
private final Set<Integer> mTabletopRotations;
+ private final Runnable mActivityBoundsUpdateCallback;
FoldController() {
mTabletopRotations = new ArraySet<>();
@@ -1696,6 +1701,26 @@
}
mIsDisplayAlwaysSeparatingHinge = mContext.getResources().getBoolean(
R.bool.config_isDisplayHingeAlwaysSeparating);
+
+ mActivityBoundsUpdateCallback = new Runnable() {
+ public void run() {
+ if (mDeviceState == DeviceStateController.DeviceState.OPEN
+ || mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
+ synchronized (mLock) {
+ final Task topFullscreenTask =
+ mDisplayContent.getTask(
+ t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+ if (topFullscreenTask != null) {
+ final ActivityRecord top =
+ topFullscreenTask.topRunningActivity();
+ if (top != null) {
+ top.recomputeConfiguration();
+ }
+ }
+ }
+ }
+ }
+ };
}
boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
@@ -1767,14 +1792,9 @@
false /* forceRelayout */);
}
// Alert the activity of possible new bounds.
- final Task topFullscreenTask =
- mDisplayContent.getTask(t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
- if (topFullscreenTask != null) {
- final ActivityRecord top = topFullscreenTask.topRunningActivity();
- if (top != null) {
- top.recomputeConfiguration();
- }
- }
+ UiThread.getHandler().removeCallbacks(mActivityBoundsUpdateCallback);
+ UiThread.getHandler().postDelayed(mActivityBoundsUpdateCallback,
+ FOLDING_RECOMPUTE_CONFIG_DELAY_MS);
}
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8c59548..0b960ec 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
@@ -296,8 +297,11 @@
// be applied using the SurfaceControl hierarchy from the Organizer. This means
// we need to make sure that these changes in crop are reflected in the input
// windows, and so ensure this flag is set so that the input crop always reflects
- // the surface hierarchy.
- useSurfaceBoundsAsTouchRegion = true;
+ // the surface hierarchy. However, we only want to set this when the client did
+ // not already provide a touchable region, so that we don't ignore the one provided.
+ if (w.mTouchableInsets != TOUCHABLE_INSETS_REGION) {
+ useSurfaceBoundsAsTouchRegion = true;
+ }
if (w.mAttrs.isModal()) {
TaskFragment parent = w.getTaskFragment();
@@ -415,8 +419,12 @@
if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) {
requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
recentsAnimationInputConsumer.mName);
+ }
+ if (mDisplayContent.mInputMethodWindow != null
+ && mDisplayContent.mInputMethodWindow.isVisible()) {
// Hiding IME/IME icon when recents input consumer gain focus.
- if (!mDisplayContent.isImeAttachedToApp()) {
+ final boolean isImeAttachedToApp = mDisplayContent.isImeAttachedToApp();
+ if (!isImeAttachedToApp) {
// Hiding IME if IME window is not attached to app since it's not proper to
// snapshot Task with IME window to animate together in this case.
final InputMethodManagerInternal inputMethodManagerInternal =
@@ -425,6 +433,14 @@
inputMethodManagerInternal.hideCurrentInputMethod(
SoftInputShowHideReason.HIDE_RECENTS_ANIMATION);
}
+ // Ensure removing the IME snapshot when the app no longer to show on the
+ // task snapshot (also taking the new task snaphot to update the overview).
+ final ActivityRecord app = mDisplayContent.getImeInputTarget() != null
+ ? mDisplayContent.getImeInputTarget().getActivityRecord() : null;
+ if (app != null) {
+ mDisplayContent.removeImeSurfaceImmediately();
+ mDisplayContent.mAtmService.takeTaskSnapshot(app.getTask().mTaskId);
+ }
} else {
// Disable IME icon explicitly when IME attached to the app in case
// IME icon might flickering while swiping to the next app task still
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 37cf5bc..ec04894 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -184,6 +184,10 @@
// portrait device orientation.
private boolean mIsVerticalReachabilityEnabled;
+ // Whether book mode automatic horizontal reachability positioning is allowed for letterboxed
+ // fullscreen apps in landscape device orientation.
+ private boolean mIsAutomaticReachabilityInBookModeEnabled;
+
// Whether education is allowed for letterboxed fullscreen apps.
private boolean mIsEducationEnabled;
@@ -277,6 +281,8 @@
R.bool.config_letterboxIsHorizontalReachabilityEnabled);
mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsVerticalReachabilityEnabled);
+ mIsAutomaticReachabilityInBookModeEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsAutomaticReachabilityInBookModeEnabled);
mDefaultPositionForHorizontalReachability =
readLetterboxHorizontalReachabilityPositionFromConfig(mContext, false);
mDefaultPositionForVerticalReachability =
@@ -681,6 +687,14 @@
return mIsVerticalReachabilityEnabled;
}
+ /*
+ * Whether automatic horizontal reachability repositioning in book mode is allowed for
+ * letterboxed fullscreen apps in landscape device orientation.
+ */
+ boolean getIsAutomaticReachabilityInBookModeEnabled() {
+ return mIsAutomaticReachabilityInBookModeEnabled;
+ }
+
/**
* Overrides whether horizontal reachability repositioning is allowed for letterboxed fullscreen
* apps in landscape device orientation.
@@ -698,6 +712,14 @@
}
/**
+ * Overrides whether automatic horizontal reachability repositioning in book mode is allowed for
+ * letterboxed fullscreen apps in landscape device orientation.
+ */
+ void setIsAutomaticReachabilityInBookModeEnabled(boolean enabled) {
+ mIsAutomaticReachabilityInBookModeEnabled = enabled;
+ }
+
+ /**
* Resets whether horizontal reachability repositioning is allowed for letterboxed fullscreen
* apps in landscape device orientation to
* {@link R.bool.config_letterboxIsHorizontalReachabilityEnabled}.
@@ -717,6 +739,16 @@
R.bool.config_letterboxIsVerticalReachabilityEnabled);
}
+ /**
+ * Resets whether automatic horizontal reachability repositioning in book mode is
+ * allowed for letterboxed fullscreen apps in landscape device orientation to
+ * {@link R.bool.config_letterboxIsAutomaticReachabilityInBookModeEnabled}.
+ */
+ void resetEnabledAutomaticReachabilityInBookMode() {
+ mIsAutomaticReachabilityInBookModeEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsAutomaticReachabilityInBookModeEnabled);
+ }
+
/*
* Gets default horizontal position of the letterboxed app window when horizontal reachability
* is enabled.
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 980a941..f6f7b49 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -38,6 +38,10 @@
import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
@@ -183,7 +187,7 @@
private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
@Configuration.Orientation
- private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+ private int mInheritedOrientation = ORIENTATION_UNDEFINED;
// The app compat state for the opaque activity if any
private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
@@ -789,13 +793,18 @@
float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
// Don't check resolved configuration because it may not be updated yet during
// configuration change.
- boolean bookMode = isDisplayFullScreenAndInPosture(
- DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */);
+ boolean bookModeEnabled = isFullScreenAndBookModeEnabled();
return isHorizontalReachabilityEnabled(parentConfiguration)
// Using the last global dynamic position to avoid "jumps" when moving
// between apps or activities.
- ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookMode)
- : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookMode);
+ ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled)
+ : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled);
+ }
+
+ private boolean isFullScreenAndBookModeEnabled() {
+ return isDisplayFullScreenAndInPosture(
+ DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */)
+ && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
}
float getVerticalPositionMultiplier(Configuration parentConfiguration) {
@@ -810,12 +819,14 @@
: mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
}
- float getFixedOrientationLetterboxAspectRatio() {
+ float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
+ // Don't resize to split screen size when half folded if letterbox position is centered
return isDisplayFullScreenAndSeparatingHinge()
- ? getSplitScreenAspectRatio()
- : mActivityRecord.shouldCreateCompatDisplayInsets()
- ? getDefaultMinAspectRatioForUnresizableApps()
- : getDefaultMinAspectRatio();
+ && getHorizontalPositionMultiplier(parentConfiguration) != 0.5f
+ ? getSplitScreenAspectRatio()
+ : mActivityRecord.shouldCreateCompatDisplayInsets()
+ ? getDefaultMinAspectRatioForUnresizableApps()
+ : getDefaultMinAspectRatio();
}
private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -841,7 +852,7 @@
int dividerInsets =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
int dividerSize = dividerWindowWidth - dividerInsets * 2;
- final Rect bounds = new Rect(displayContent.getBounds());
+ final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds());
if (bounds.width() >= bounds.height()) {
bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
bounds.right = bounds.centerX();
@@ -877,7 +888,8 @@
return;
}
- boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge();
+ boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge()
+ && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
.getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
if (mLetterbox.getInnerFrame().left > x) {
@@ -957,6 +969,8 @@
* </ul>
*/
private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
+ // Use screen resolved bounds which uses resolved bounds or size compat bounds
+ // as activity bounds can sometimes be empty
return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled()
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
@@ -964,7 +978,7 @@
&& mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT)
// Check whether the activity fills the parent vertically.
&& parentConfiguration.windowConfiguration.getAppBounds().height()
- <= mActivityRecord.getBounds().height();
+ <= mActivityRecord.getScreenResolvedBounds().height();
}
@VisibleForTesting
@@ -984,6 +998,8 @@
* </ul>
*/
private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
+ // Use screen resolved bounds which uses resolved bounds or size compat bounds
+ // as activity bounds can sometimes be empty
return mLetterboxConfiguration.getIsVerticalReachabilityEnabled()
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
@@ -991,7 +1007,7 @@
&& mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE)
// Check whether the activity fills the parent horizontally.
&& parentConfiguration.windowConfiguration.getBounds().width()
- == mActivityRecord.getBounds().width();
+ == mActivityRecord.getScreenResolvedBounds().width();
}
@VisibleForTesting
@@ -1409,7 +1425,8 @@
mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
mActivityRecord, firstOpaqueActivityBeneath,
(opaqueConfig, transparentConfig) -> {
- final Configuration mutatedConfiguration = new Configuration();
+ final Configuration mutatedConfiguration =
+ fromOriginalTranslucentConfig(transparentConfig);
final Rect parentBounds = parent.getWindowConfiguration().getBounds();
final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
@@ -1497,8 +1514,24 @@
true /* traverseTopToBottom */));
}
+ // When overriding translucent activities configuration we need to keep some of the
+ // original properties
+ private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) {
+ final Configuration configuration = new Configuration(translucentConfig);
+ // The values for the following properties will be defined during the configuration
+ // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the
+ // properties inherited from the first not finishing opaque activity beneath.
+ configuration.orientation = ORIENTATION_UNDEFINED;
+ configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
+ configuration.screenHeightDp =
+ configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
+ configuration.smallestScreenWidthDp =
+ configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+ return configuration;
+ }
+
private void inheritConfiguration(ActivityRecord firstOpaque) {
- // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities
+ // To avoid wrong behaviour, we're not forcing a specific aspect ratio to activities
// which are not already providing one (e.g. permission dialogs) and presumably also
// not resizable.
if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) {
@@ -1516,7 +1549,7 @@
mLetterboxConfigListener = null;
mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
- mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+ mInheritedOrientation = ORIENTATION_UNDEFINED;
mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
mInheritedCompatDisplayInsets = null;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index fda2125..2f56343 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2281,11 +2281,10 @@
resumedOnDisplay[0] |= curResult;
return;
}
- if (rootTask.getDisplayArea().isTopRootTask(rootTask)
- && topRunningActivity.isState(RESUMED)) {
- // Kick off any lingering app transitions from the MoveTaskToFront
- // operation, but only consider the top task and root-task on that
- // display.
+ if (topRunningActivity.isState(RESUMED)
+ && topRunningActivity == rootTask.getDisplayArea().topRunningActivity()) {
+ // Kick off any lingering app transitions form the MoveTaskToFront operation,
+ // but only consider the top activity on that display.
rootTask.executeAppTransition(targetOptions);
} else {
resumedOnDisplay[0] |= topRunningActivity.makeActiveIfNeeded(target);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 78ee6f9..7b10c63 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -116,7 +116,7 @@
private boolean mShowingAlertWindowNotificationAllowed;
private boolean mClientDead = false;
private float mLastReportedAnimatorScale;
- private String mPackageName;
+ protected String mPackageName;
private String mRelayoutTag;
private final InsetsSourceControl.Array mDummyControls = new InsetsSourceControl.Array();
final boolean mSetsUnrestrictedKeepClearAreas;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8c6de8e..68b2d0f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3361,6 +3361,8 @@
&& info.pictureInPictureParams.isLaunchIntoPip()
&& top.getLastParentBeforePip() != null)
? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
+ info.lastParentTaskIdBeforePip = top != null && top.getLastParentBeforePip() != null
+ ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 09d33fa..6bc9fa4 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -458,6 +458,22 @@
&& organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder());
}
+ /**
+ * Returns the process of organizer if this TaskFragment is organized and the activity lives in
+ * a different process than the organizer.
+ */
+ @Nullable
+ private WindowProcessController getOrganizerProcessIfDifferent(@Nullable ActivityRecord r) {
+ if ((r == null || mTaskFragmentOrganizerProcessName == null)
+ || (mTaskFragmentOrganizerProcessName.equals(r.processName)
+ && mTaskFragmentOrganizerUid == r.getUid())) {
+ // No organizer or the process is the same.
+ return null;
+ }
+ return mAtmService.getProcessController(mTaskFragmentOrganizerProcessName,
+ mTaskFragmentOrganizerUid);
+ }
+
void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
mAnimationParams = animationParams;
}
@@ -815,6 +831,16 @@
setResumedActivity(record, reason + " - onActivityStateChanged");
mTaskSupervisor.mRecentTasks.add(record.getTask());
}
+
+ // Update the process state for the organizer process if the activity is in a different
+ // process in case the organizer process may not have activity state change in its process.
+ final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(record);
+ if (hostProcess != null) {
+ mTaskSupervisor.onProcessActivityStateChanged(hostProcess, false /* forceBatch */);
+ hostProcess.updateProcessInfo(false /* updateServiceConnectionActivities */,
+ true /* activityChange */, true /* updateOomAdj */,
+ false /* addPendingTopUid */);
+ }
}
/**
@@ -1942,6 +1968,11 @@
addingActivity.inHistory = true;
task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
}
+
+ final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(addingActivity);
+ if (hostProcess != null) {
+ hostProcess.addEmbeddedActivity(addingActivity);
+ }
}
@Override
@@ -2757,14 +2788,18 @@
void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
super.removeChild(child);
+ final ActivityRecord r = child.asActivityRecord();
if (BackNavigationController.isScreenshotEnabled()) {
//TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
// implemented
- ActivityRecord r = child.asActivityRecord();
if (r != null) {
mBackScreenshots.remove(r.mActivityComponent.flattenToString());
}
}
+ final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(r);
+ if (hostProcess != null) {
+ hostProcess.removeEmbeddedActivity(r);
+ }
if (removeSelfIfPossible && shouldRemoveSelfOnLastChildRemoval() && !hasChild()) {
removeImmediately("removeLastChild " + child);
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 93c8c36..184293e 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -390,7 +390,7 @@
boolean taskAppearedSent = t.mTaskAppearedSent;
if (taskAppearedSent) {
if (t.getSurfaceControl() != null) {
- t.migrateToNewSurfaceControl(t.getSyncTransaction());
+ t.migrateToNewSurfaceControl(t.getPendingTransaction());
}
t.mTaskAppearedSent = false;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b7dc0ec..2cfd2af8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1487,9 +1487,9 @@
// then we have to notify KeyguardService directly. This can happen if there is
// another ongoing transition when the app changes occlusion OR if the app dies or
// is killed. Both of these are common during tests.
- final boolean notify = !(transit == TRANSIT_KEYGUARD_OCCLUDE
- || transit == TRANSIT_KEYGUARD_UNOCCLUDE);
- mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(notify);
+ if (transit != TRANSIT_KEYGUARD_OCCLUDE && transit != TRANSIT_KEYGUARD_UNOCCLUDE) {
+ mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange();
+ }
}
}
@@ -2738,9 +2738,7 @@
buffer, screenshotBuffer.getColorSpace());
}
SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
-
- t.setBuffer(snapshotSurface, buffer);
- t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace());
+ TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer);
t.show(snapshotSurface);
// Place it on top of anything else in the container.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 918729d..a55c7c1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8703,7 +8703,13 @@
} else {
h.touchableRegion.set(region);
h.replaceTouchableRegionWithCrop = false;
- h.setTouchableRegionCrop(surface);
+
+ // Task managers may need to receive input events around task layers to resize tasks.
+ final int permissionResult = mContext.checkPermission(
+ permission.MANAGE_ACTIVITY_TASKS, callingPid, callingUid);
+ if (permissionResult != PackageManager.PERMISSION_GRANTED) {
+ h.setTouchableRegionCrop(surface);
+ }
}
final SurfaceControl.Transaction t = mTransactionFactory.get();
@@ -9383,30 +9389,6 @@
mSurfaceSyncGroupController.markSyncGroupReady(syncGroupToken);
}
- private ArraySet<ActivityRecord> getVisibleActivityRecords(int displayId) {
- ArraySet<ActivityRecord> result = new ArraySet<>();
- synchronized (mGlobalLock) {
- ArraySet<ComponentName> addedActivities = new ArraySet<>();
- DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent != null) {
- displayContent.forAllWindows(
- (w) -> {
- if (w.isVisible()
- && w.isDisplayed()
- && w.mActivityRecord != null
- && !addedActivities.contains(
- w.mActivityRecord.mActivityComponent)
- && w.mActivityRecord.isVisible()
- && w.isVisibleNow()) {
- addedActivities.add(w.mActivityRecord.mActivityComponent);
- result.add(w.mActivityRecord);
- }
- },
- true /* traverseTopToBottom */);
- }
- }
- return result;
- }
/**
* Must be called when a screenshot is taken via hardware chord.
@@ -9422,14 +9404,20 @@
throw new SecurityException("Requires STATUS_BAR_SERVICE permission");
}
synchronized (mGlobalLock) {
- ArraySet<ComponentName> notifiedApps = new ArraySet<>();
- ArraySet<ActivityRecord> visibleApps = getVisibleActivityRecords(displayId);
- for (ActivityRecord ar : visibleApps) {
- if (ar.isRegisteredForScreenCaptureCallback()) {
- ar.reportScreenCaptured();
- notifiedApps.add(ar.mActivityComponent);
- }
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ return new ArrayList<>();
}
+ ArraySet<ComponentName> notifiedApps = new ArraySet<>();
+ displayContent.forAllActivities(
+ (ar) -> {
+ if (!notifiedApps.contains(ar.mActivityComponent) && ar.isVisible()
+ && ar.isRegisteredForScreenCaptureCallback()) {
+ ar.reportScreenCaptured();
+ notifiedApps.add(ar.mActivityComponent);
+ }
+ },
+ true /* traverseTopToBottom */);
return List.copyOf(notifiedApps);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 8c2dd2d..437af4b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -981,6 +981,10 @@
runSetBooleanFlag(pw, mLetterboxConfiguration
::setIsVerticalReachabilityEnabled);
break;
+ case "--isAutomaticReachabilityInBookModeEnabled":
+ runSetBooleanFlag(pw, mLetterboxConfiguration
+ ::setIsAutomaticReachabilityInBookModeEnabled);
+ break;
case "--defaultPositionForHorizontalReachability":
runSetLetterboxDefaultPositionForHorizontalReachability(pw);
break;
@@ -1183,6 +1187,7 @@
mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
+ mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
mLetterboxConfiguration.resetIsEducationEnabled();
@@ -1218,6 +1223,8 @@
+ mLetterboxConfiguration.getIsHorizontalReachabilityEnabled());
pw.println("Is vertical reachability enabled: "
+ mLetterboxConfiguration.getIsVerticalReachabilityEnabled());
+ pw.println("Is automatic reachability in book mode enabled: "
+ + mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled());
pw.println("Default position for horizontal reachability: "
+ LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
mLetterboxConfiguration.getDefaultPositionForHorizontalReachability()));
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 8b0ef6c..c74af82 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -439,7 +439,11 @@
// multiple sync at the same time because it may cause conflict.
// Create a new transition when there is no active sync to collect the changes.
final Transition transition = mTransitionController.createTransition(type);
- applyTransaction(wct, -1 /* syncId */, transition, caller);
+ if (applyTransaction(wct, -1 /* syncId */, transition, caller)
+ == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
+ transition.abort();
+ return;
+ }
mTransitionController.requestStartTransition(transition, null /* startTask */,
null /* remoteTransition */, null /* displayChange */);
transition.setAllReady();
@@ -476,24 +480,26 @@
// calls startSyncSet.
() -> mTransitionController.moveToCollecting(nextTransition),
() -> {
- if (mTaskFragmentOrganizerController.isValidTransaction(wct)) {
- applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
+ if (mTaskFragmentOrganizerController.isValidTransaction(wct)
+ && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller)
+ != TRANSACT_EFFECTS_NONE
+ || !nextTransition.mParticipants.isEmpty())) {
mTransitionController.requestStartTransition(nextTransition,
null /* startTask */, null /* remoteTransition */,
null /* displayChange */);
nextTransition.setAllReady();
- } else {
- nextTransition.abort();
+ return;
}
+ nextTransition.abort();
});
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
+ private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller) {
- applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
+ return applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
}
/**
@@ -501,8 +507,9 @@
* @param transition A transition to collect changes into.
* @param caller Info about the calling process.
* @param finishTransition The transition that is currently being finished.
+ * @return The effects of the window container transaction.
*/
- private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
+ private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller,
@Nullable Transition finishTransition) {
int effects = TRANSACT_EFFECTS_NONE;
@@ -639,6 +646,7 @@
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
mService.continueWindowLayout();
}
+ return effects;
}
private int applyChanges(@NonNull WindowContainer<?> container,
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 834b708..8685723 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -69,6 +69,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -228,8 +229,17 @@
* in another process. This is used to check if the process is currently showing anything
* visible to the user.
*/
+ private static final int REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY = 1;
+ /** The activity in a different process is embedded in a task created by this process. */
+ private static final int REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY = 1 << 1;
+
+ /**
+ * Activities that run on different processes while this process shows something in these
+ * activities or the appearance of the activities are controlled by this process. The value of
+ * map is an array of size 1 to store the kinds of remote.
+ */
@Nullable
- private final ArrayList<ActivityRecord> mHostActivities = new ArrayList<>();
+ private ArrayMap<ActivityRecord, int[]> mRemoteActivities;
/** Whether our process is currently running a {@link RecentsAnimation} */
private boolean mRunningRecentsAnimation;
@@ -857,7 +867,7 @@
return true;
}
}
- if (isEmbedded()) {
+ if (hasEmbeddedWindow()) {
return true;
}
}
@@ -868,9 +878,13 @@
* @return {@code true} if this process is rendering content on to a window shown by
* another process.
*/
- private boolean isEmbedded() {
- for (int i = mHostActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mHostActivities.get(i);
+ private boolean hasEmbeddedWindow() {
+ if (mRemoteActivities == null) return false;
+ for (int i = mRemoteActivities.size() - 1; i >= 0; --i) {
+ if ((mRemoteActivities.valueAt(i)[0] & REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY) == 0) {
+ continue;
+ }
+ final ActivityRecord r = mRemoteActivities.keyAt(i);
if (r.isInterestingToUserLocked()) {
return true;
}
@@ -1038,15 +1052,46 @@
/** Adds an activity that hosts UI drawn by the current process. */
void addHostActivity(ActivityRecord r) {
- if (mHostActivities.contains(r)) {
- return;
- }
- mHostActivities.add(r);
+ final int[] flags = getRemoteActivityFlags(r);
+ flags[0] |= REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY;
}
/** Removes an activity that hosts UI drawn by the current process. */
void removeHostActivity(ActivityRecord r) {
- mHostActivities.remove(r);
+ removeRemoteActivityFlags(r, REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY);
+ }
+
+ /** Adds an embedded activity in a different process to this process that organizes it. */
+ void addEmbeddedActivity(ActivityRecord r) {
+ final int[] flags = getRemoteActivityFlags(r);
+ flags[0] |= REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY;
+ }
+
+ /** Removes an embedded activity which was added by {@link #addEmbeddedActivity}. */
+ void removeEmbeddedActivity(ActivityRecord r) {
+ removeRemoteActivityFlags(r, REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY);
+ }
+
+ private int[] getRemoteActivityFlags(ActivityRecord r) {
+ if (mRemoteActivities == null) {
+ mRemoteActivities = new ArrayMap<>();
+ }
+ int[] flags = mRemoteActivities.get(r);
+ if (flags == null) {
+ mRemoteActivities.put(r, flags = new int[1]);
+ }
+ return flags;
+ }
+
+ private void removeRemoteActivityFlags(ActivityRecord r, int flags) {
+ if (mRemoteActivities == null) return;
+ final int index = mRemoteActivities.indexOfKey(r);
+ if (index < 0) return;
+ final int[] currentFlags = mRemoteActivities.valueAt(index);
+ currentFlags[0] &= ~flags;
+ if (currentFlags[0] == 0) {
+ mRemoteActivities.removeAt(index);
+ }
}
public interface ComputeOomAdjCallback {
@@ -1121,6 +1166,16 @@
}
}
}
+ if (mRemoteActivities != null) {
+ // Make this process have visible state if its organizer embeds visible activities of
+ // other process, so this process can be responsive for the organizer events.
+ for (int i = mRemoteActivities.size() - 1; i >= 0; i--) {
+ if ((mRemoteActivities.valueAt(i)[0] & REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY) != 0
+ && mRemoteActivities.keyAt(i).isVisibleRequested()) {
+ stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE;
+ }
+ }
+ }
stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
if (visible) {
@@ -1795,7 +1850,21 @@
pw.print(prefix); pw.print(" - "); pw.println(mActivities.get(i));
}
}
-
+ if (mRemoteActivities != null && !mRemoteActivities.isEmpty()) {
+ pw.print(prefix); pw.println("Remote Activities:");
+ for (int i = mRemoteActivities.size() - 1; i >= 0; i--) {
+ pw.print(prefix); pw.print(" - ");
+ pw.print(mRemoteActivities.keyAt(i)); pw.print(" flags=");
+ final int flags = mRemoteActivities.valueAt(i)[0];
+ if ((flags & REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY) != 0) {
+ pw.print("host ");
+ }
+ if ((flags & REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY) != 0) {
+ pw.print("embedded");
+ }
+ pw.println();
+ }
+ }
if (mRecentTasks.size() > 0) {
pw.println(prefix + "Recent Tasks:");
for (int i = 0; i < mRecentTasks.size(); i++) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 8f9b949..90b92f4 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.credentials;
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
import static android.content.Context.CREDENTIAL_SERVICE;
@@ -41,8 +42,9 @@
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IGetPendingCredentialCallback;
+import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
+import android.credentials.PrepareGetCredentialResponseInternal;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
@@ -124,7 +126,8 @@
serviceInfos.forEach(
info -> {
services.add(
- new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
+ new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
+ info));
});
return services;
}
@@ -305,6 +308,29 @@
return providerSessions;
}
+ @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
+ // to be guarded by 'service.mLock', which is the same as mLock.
+ private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
+ PrepareGetRequestSession session,
+ Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
+ activeCredentialContainers) {
+ List<ProviderSession> providerSessions = new ArrayList<>();
+ for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result :
+ activeCredentialContainers) {
+ ProviderSession providerSession = ProviderRegistryGetSession.createNewSession(
+ mContext,
+ UserHandle.getCallingUserId(),
+ session,
+ session.mClientAppInfo,
+ result.second.mPackageName,
+ result.first);
+ providerSessions.add(providerSession);
+ session.addProviderSession(providerSession.getComponentName(), providerSession);
+ }
+ return providerSessions;
+ }
+
+
@NonNull
private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>>
getFilteredResultFromRegistry(List<CredentialOption> options) {
@@ -418,6 +444,7 @@
// Check privileged permissions
mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
}
+ enforcePermissionForAllowedProviders(request);
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
@@ -440,18 +467,122 @@
}
@Override
- public ICancellationSignal executeGetPendingCredential(
+ public ICancellationSignal executePrepareGetCredential(
GetCredentialRequest request,
- IGetPendingCredentialCallback callback,
+ IPrepareGetCredentialCallback prepareGetCredentialCallback,
+ IGetCredentialCallback getCredentialCallback,
final String callingPackage) {
- // TODO(b/273308895): implement
-
+ final long timestampBegan = System.nanoTime();
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ if (request.getOrigin() != null) {
+ // Check privileged permissions
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+ }
+ enforcePermissionForAllowedProviders(request);
+
+ final int userId = UserHandle.getCallingUserId();
+ final int callingUid = Binder.getCallingUid();
+ enforceCallingPackage(callingPackage, callingUid);
+
+ final PrepareGetRequestSession session =
+ new PrepareGetRequestSession(
+ getContext(),
+ userId,
+ callingUid,
+ prepareGetCredentialCallback,
+ getCredentialCallback,
+ request,
+ constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
+ CancellationSignal.fromTransport(cancelTransport),
+ timestampBegan);
+
+ processGetCredential(request, prepareGetCredentialCallback, session);
+
return cancelTransport;
}
private void processGetCredential(
GetCredentialRequest request,
+ IPrepareGetCredentialCallback callback,
+ PrepareGetRequestSession session) {
+ List<ProviderSession> providerSessions;
+
+ if (isCredentialDescriptionApiEnabled()) {
+ List<CredentialOption> optionsThatRequireActiveCredentials =
+ request.getCredentialOptions().stream()
+ .filter(
+ getCredentialOption ->
+ !TextUtils.isEmpty(
+ getCredentialOption
+ .getCredentialRetrievalData()
+ .getString(
+ CredentialOption
+ .FLATTENED_REQUEST,
+ null)))
+ .toList();
+
+ List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
+ request.getCredentialOptions().stream()
+ .filter(
+ getCredentialOption ->
+ TextUtils.isEmpty(
+ getCredentialOption
+ .getCredentialRetrievalData()
+ .getString(
+ CredentialOption
+ .FLATTENED_REQUEST,
+ null)))
+ .toList();
+
+ List<ProviderSession> sessionsWithoutRemoteService =
+ initiateProviderSessionsWithActiveContainers(
+ session,
+ getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
+
+ List<ProviderSession> sessionsWithRemoteService =
+ initiateProviderSessions(
+ session,
+ optionsThatDoNotRequireActiveCredentials.stream()
+ .map(CredentialOption::getType)
+ .collect(Collectors.toList()));
+
+ Set<ProviderSession> all = new LinkedHashSet<>();
+ all.addAll(sessionsWithRemoteService);
+ all.addAll(sessionsWithoutRemoteService);
+
+ providerSessions = new ArrayList<>(all);
+ } else {
+ // Initiate all provider sessions
+ providerSessions =
+ initiateProviderSessions(
+ session,
+ request.getCredentialOptions().stream()
+ .map(CredentialOption::getType)
+ .collect(Collectors.toList()));
+ }
+
+ if (providerSessions.isEmpty()) {
+ try {
+ // TODO: fix
+ callback.onResponse(new PrepareGetCredentialResponseInternal(
+ false, null, false, false, null));
+ } catch (RemoteException e) {
+ Log.i(
+ TAG,
+ "Issue invoking onError on IGetCredentialCallback "
+ + "callback: "
+ + e.getMessage());
+ }
+ }
+
+ finalizeAndEmitInitialPhaseMetric(session);
+ // TODO(b/271135048) - May still be worth emitting in the empty cases above.
+ providerSessions.forEach(ProviderSession::invokeSession);
+ }
+
+ private void processGetCredential(
+ GetCredentialRequest request,
IGetCredentialCallback callback,
GetRequestSession session) {
List<ProviderSession> providerSessions;
@@ -823,6 +954,17 @@
}
}
+ private void enforcePermissionForAllowedProviders(GetCredentialRequest request) {
+ boolean containsAllowedProviders = request.getCredentialOptions()
+ .stream()
+ .anyMatch(option -> option.getAllowedProviders() != null
+ && !option.getAllowedProviders().isEmpty());
+ if (containsAllowedProviders) {
+ mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS,
+ null);
+ }
+ }
+
private void enforceCallingPackage(String callingPackage, int callingUid) {
int packageUid;
PackageManager pm = mContext.createContextAsUser(
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 4e058a8..8082cdb 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -27,6 +27,7 @@
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
+import android.os.Binder;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
@@ -84,11 +85,12 @@
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
try {
- mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+ Binder.withCleanCallingIdentity(() ->
+ mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
- mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
- providerDataList));
- } catch (RemoteException e) {
+ mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
+ providerDataList)));
+ } catch (RuntimeException e) {
mChosenProviderFinalPhaseMetric.setUiReturned(false);
respondToClientWithErrorAndFinish(
GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
new file mode 100644
index 0000000..9165901
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2022 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.server.credentials;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
+import android.credentials.GetCredentialException;
+import android.credentials.GetCredentialRequest;
+import android.credentials.GetCredentialResponse;
+import android.credentials.IGetCredentialCallback;
+import android.credentials.IPrepareGetCredentialCallback;
+import android.credentials.PrepareGetCredentialResponseInternal;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.service.credentials.CallingAppInfo;
+import android.util.Log;
+
+import com.android.server.credentials.metrics.ApiName;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
+
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+
+/**
+ * Central session for a single prepareGetCredentials request. This class listens to the
+ * responses from providers, and the UX app, and updates the provider(S) state.
+ */
+public class PrepareGetRequestSession extends RequestSession<GetCredentialRequest,
+ IGetCredentialCallback>
+ implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
+ private static final String TAG = "GetRequestSession";
+
+ private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback;
+ private boolean mIsInitialQuery = true;
+
+ public PrepareGetRequestSession(Context context, int userId, int callingUid,
+ IPrepareGetCredentialCallback prepareGetCredentialCallback,
+ IGetCredentialCallback getCredCallback, GetCredentialRequest request,
+ CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal,
+ long startedTimestamp) {
+ super(context, userId, callingUid, request, getCredCallback, RequestInfo.TYPE_GET,
+ callingAppInfo, cancellationSignal, startedTimestamp);
+ int numTypes = (request.getCredentialOptions().stream()
+ .map(CredentialOption::getType).collect(
+ Collectors.toSet())).size(); // Dedupe type strings
+ setupInitialPhaseMetric(ApiName.GET_CREDENTIAL.getMetricCode(), numTypes);
+ mPrepareGetCredentialCallback = prepareGetCredentialCallback;
+ }
+
+ /**
+ * Creates a new provider session, and adds it list of providers that are contributing to
+ * this session.
+ *
+ * @return the provider session created within this request session, for the given provider
+ * info.
+ */
+ @Override
+ @Nullable
+ public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+ RemoteCredentialService remoteCredentialService) {
+ ProviderGetSession providerGetSession = ProviderGetSession
+ .createNewSession(mContext, mUserId, providerInfo,
+ this, remoteCredentialService);
+ if (providerGetSession != null) {
+ Log.i(TAG, "In startProviderSession - provider session created and being added");
+ mProviders.put(providerGetSession.getComponentName().flattenToString(),
+ providerGetSession);
+ }
+ return providerGetSession;
+ }
+
+ @Override
+ protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+ mChosenProviderFinalPhaseMetric.setUiCallStartTimeNanoseconds(System.nanoTime());
+ try {
+ mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+ RequestInfo.newGetRequestInfo(
+ mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
+ providerDataList));
+ } catch (RemoteException e) {
+ mChosenProviderFinalPhaseMetric.setUiReturned(false);
+ respondToClientWithErrorAndFinish(
+ GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
+ }
+ }
+
+ @Override
+ public void onFinalResponseReceived(ComponentName componentName,
+ @Nullable GetCredentialResponse response) {
+ mChosenProviderFinalPhaseMetric.setUiReturned(true);
+ mChosenProviderFinalPhaseMetric.setUiCallEndTimeNanoseconds(System.nanoTime());
+ Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ setChosenMetric(componentName);
+ if (response != null) {
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
+ respondToClientWithResponseAndFinish(response);
+ } else {
+ mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+ "Invalid response from provider");
+ }
+ }
+
+ //TODO: Try moving the three error & response methods below to RequestSession to be shared
+ // between get & create.
+ @Override
+ public void onFinalErrorReceived(ComponentName componentName, String errorType,
+ String message) {
+ respondToClientWithErrorAndFinish(errorType, message);
+ }
+
+ private void respondToClientWithResponseAndFinish(GetCredentialResponse response) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Log.i(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+ if (isSessionCancelled()) {
+// TODO: properly log the new api
+// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
+// ApiStatus.CLIENT_CANCELED);
+ finishSession(/*propagateCancellation=*/true);
+ return;
+ }
+ try {
+ mClientCallback.onResponse(response);
+// TODO: properly log the new api
+// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
+// ApiStatus.SUCCESS);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
+// TODO: properly log the new api
+// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
+// ApiStatus.FAILURE);
+ }
+ finishSession(/*propagateCancellation=*/false);
+ }
+
+ private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Log.i(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+ if (isSessionCancelled()) {
+// TODO: properly log the new api
+// logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
+// ApiStatus.CLIENT_CANCELED);
+ finishSession(/*propagateCancellation=*/true);
+ return;
+ }
+
+ try {
+ mClientCallback.onError(errorType, errorMsg);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
+ }
+ logFailureOrUserCancel(errorType);
+ finishSession(/*propagateCancellation=*/false);
+ }
+
+ private void logFailureOrUserCancel(String errorType) {
+ if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
+// TODO: properly log the new api
+// logApiCall(ApiName.GET_CREDENTIAL,
+// /* apiStatus */ ApiStatus.USER_CANCELED);
+ } else {
+// TODO: properly log the new api
+// logApiCall(ApiName.GET_CREDENTIAL,
+// /* apiStatus */ ApiStatus.FAILURE);
+ }
+ }
+
+ @Override
+ public void onUiCancellation(boolean isUserCancellation) {
+ if (isUserCancellation) {
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED,
+ "User cancelled the selector");
+ } else {
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED,
+ "The UI was interrupted - please try again.");
+ }
+ }
+
+ @Override
+ public void onUiSelectorInvocationFailure() {
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+ "No credentials available.");
+ }
+
+ @Override
+ public void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ Log.i(TAG, "in onStatusChanged with status: " + status);
+ // Auth entry was selected, and it did not have any underlying credentials
+ if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
+ handleEmptyAuthenticationSelection(componentName);
+ return;
+ }
+ // For any other status, we check if all providers are done and then invoke UI if needed
+ if (!isAnyProviderPending()) {
+ // If all provider responses have been received, we can either need the UI,
+ // or we need to respond with error. The only other case is the entry being
+ // selected after the UI has been invoked which has a separate code path.
+ if (isUiInvocationNeeded()) {
+ if (mIsInitialQuery) {
+ try {
+ mPrepareGetCredentialCallback.onResponse(
+ new PrepareGetCredentialResponseInternal(
+ false, null, false, false, getUiIntent()));
+ } catch (Exception e) {
+ Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
+ }
+ mIsInitialQuery = false;
+ } else {
+ getProviderDataAndInitiateUi();
+ }
+ } else {
+ if (mIsInitialQuery) {
+ try {
+ mPrepareGetCredentialCallback.onResponse(
+ new PrepareGetCredentialResponseInternal(
+ false, null, false, false, null));
+ } catch (Exception e) {
+ Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
+ }
+ mIsInitialQuery = false;
+ // TODO(273308895): should also clear session here
+ } else {
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+ "No credentials available");
+ }
+ }
+ }
+ }
+
+ private PendingIntent getUiIntent() {
+ ArrayList<ProviderData> providerDataList = new ArrayList<>();
+ for (ProviderSession session : mProviders.values()) {
+ Log.i(TAG, "preparing data for : " + session.getComponentName());
+ ProviderData providerData = session.prepareUiData();
+ if (providerData != null) {
+ Log.i(TAG, "Provider data is not null");
+ providerDataList.add(providerData);
+ }
+ }
+ if (!providerDataList.isEmpty()) {
+ return mCredentialManagerUi.createPendingIntent(
+ RequestInfo.newGetRequestInfo(
+ mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
+ providerDataList);
+ } else {
+ return null;
+ }
+ }
+
+ private void handleEmptyAuthenticationSelection(ComponentName componentName) {
+ // Update auth entry statuses across different provider sessions
+ mProviders.keySet().forEach(key -> {
+ ProviderGetSession session = (ProviderGetSession) mProviders.get(key);
+ if (!session.mComponentName.equals(componentName)) {
+ session.updateAuthEntriesStatusFromAnotherSession();
+ }
+ });
+
+ // Invoke UI since it needs to show a snackbar if last auth entry, or a status on each
+ // auth entries along with other valid entries
+ getProviderDataAndInitiateUi();
+
+ // Respond to client if all auth entries are empty and nothing else to show on the UI
+ if (providerDataContainsEmptyAuthEntriesOnly()) {
+ respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+ "No credentials available");
+ }
+ }
+
+ private boolean providerDataContainsEmptyAuthEntriesOnly() {
+ for (String key : mProviders.keySet()) {
+ ProviderGetSession session = (ProviderGetSession) mProviders.get(key);
+ if (!session.containsEmptyAuthEntriesOnly()) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 9bc5998..d17e984 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -94,7 +94,42 @@
RemoteCredentialService remoteCredentialService) {
android.credentials.GetCredentialRequest filteredRequest =
filterOptions(providerInfo.getCapabilities(),
- getRequestSession.mClientRequest);
+ getRequestSession.mClientRequest,
+ providerInfo.getComponentName());
+ if (filteredRequest != null) {
+ Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
+ new HashMap<>();
+ return new ProviderGetSession(
+ context,
+ providerInfo,
+ getRequestSession,
+ userId,
+ remoteCredentialService,
+ constructQueryPhaseRequest(
+ filteredRequest, getRequestSession.mClientAppInfo,
+ getRequestSession.mClientRequest.alwaysSendAppInfoToProvider(),
+ beginGetOptionToCredentialOptionMap),
+ filteredRequest,
+ getRequestSession.mClientAppInfo,
+ beginGetOptionToCredentialOptionMap,
+ getRequestSession.mHybridService
+ );
+ }
+ Log.i(TAG, "Unable to create provider session");
+ return null;
+ }
+
+ /** Creates a new provider session to be used by the request session. */
+ @Nullable public static ProviderGetSession createNewSession(
+ Context context,
+ @UserIdInt int userId,
+ CredentialProviderInfo providerInfo,
+ PrepareGetRequestSession getRequestSession,
+ RemoteCredentialService remoteCredentialService) {
+ android.credentials.GetCredentialRequest filteredRequest =
+ filterOptions(providerInfo.getCapabilities(),
+ getRequestSession.mClientRequest,
+ providerInfo.getComponentName());
if (filteredRequest != null) {
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
new HashMap<>();
@@ -142,17 +177,19 @@
@Nullable
private static android.credentials.GetCredentialRequest filterOptions(
List<String> providerCapabilities,
- android.credentials.GetCredentialRequest clientRequest
+ android.credentials.GetCredentialRequest clientRequest,
+ ComponentName componentName
) {
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
- if (providerCapabilities.contains(option.getType())) {
+ if (providerCapabilities.contains(option.getType())
+ && isProviderAllowed(option, componentName)) {
Log.i(TAG, "In createProviderRequest - capability found : "
+ option.getType());
filteredOptions.add(option);
} else {
Log.i(TAG, "In createProviderRequest - capability not "
- + "found : " + option.getType());
+ + "found, or provider not allowed : " + option.getType());
}
}
if (!filteredOptions.isEmpty()) {
@@ -165,6 +202,16 @@
return null;
}
+ private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
+ if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
+ componentName)) {
+ Log.d(TAG, "Provider allow list specified but does not contain this provider: "
+ + componentName.flattenToString());
+ return false;
+ }
+ return true;
+ }
+
public ProviderGetSession(Context context,
CredentialProviderInfo info,
ProviderInternalCallback<GetCredentialResponse> callbacks,
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index a5196c5..85c78445 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -76,6 +76,24 @@
requestOption);
}
+ /** Creates a new provider session to be used by the request session. */
+ @Nullable
+ public static ProviderRegistryGetSession createNewSession(
+ @NonNull Context context,
+ @UserIdInt int userId,
+ @NonNull PrepareGetRequestSession getRequestSession,
+ @NonNull CallingAppInfo callingAppInfo,
+ @NonNull String credentialProviderPackageName,
+ @NonNull CredentialOption requestOption) {
+ return new ProviderRegistryGetSession(
+ context,
+ userId,
+ getRequestSession,
+ callingAppInfo,
+ credentialProviderPackageName,
+ requestOption);
+ }
+
@NonNull
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@NonNull
@@ -106,6 +124,23 @@
.getString(CredentialOption.FLATTENED_REQUEST);
}
+ protected ProviderRegistryGetSession(@NonNull Context context,
+ @NonNull int userId,
+ @NonNull PrepareGetRequestSession session,
+ @NonNull CallingAppInfo callingAppInfo,
+ @NonNull String servicePackageName,
+ @NonNull CredentialOption requestOption) {
+ super(context, requestOption, session,
+ new ComponentName(servicePackageName, servicePackageName) ,
+ userId, null);
+ mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
+ mCallingAppInfo = callingAppInfo;
+ mCredentialProviderPackageName = servicePackageName;
+ mFlattenedRequestOptionString = requestOption
+ .getCredentialRetrievalData()
+ .getString(CredentialOption.FLATTENED_REQUEST);
+ }
+
private List<Entry> prepareUiCredentialEntries(
@NonNull List<CredentialEntry> credentialEntries) {
Log.i(TAG, "in prepareUiProviderDataWithCredentials");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 303de12..269c4ab 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -225,6 +225,7 @@
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
+import static android.provider.DeviceConfig.NAMESPACE_TELEPHONY;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -426,6 +427,7 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
@@ -3324,7 +3326,7 @@
onLockSettingsReady();
loadAdminDataAsync();
mOwners.systemReady();
- if (isWorkProfileTelephonyFlagEnabled()) {
+ if (isWorkProfileTelephonyEnabled()) {
applyManagedSubscriptionsPolicyIfRequired();
}
break;
@@ -3534,26 +3536,21 @@
userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
- final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
- boolean isManagedSubscription;
+ final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
preferentialNetworkServiceConfigs = owner != null
? owner.mPreferentialNetworkServiceConfigs
: List.of(PreferentialNetworkServiceConfig.DEFAULT);
-
- isManagedSubscription = owner != null && owner.mManagedSubscriptionsPolicy != null
- && owner.mManagedSubscriptionsPolicy.getPolicyType()
- == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS;
}
updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
- if (isManagedSubscription) {
- String defaultDialerPackageName = getDefaultRoleHolderPackageName(
- com.android.internal.R.string.config_defaultDialer);
- String defaultSmsPackageName = getDefaultRoleHolderPackageName(
- com.android.internal.R.string.config_defaultSms);
+ if (isProfileOwnerOfOrganizationOwnedDevice(userId)
+ && getManagedSubscriptionsPolicy().getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ String defaultDialerPackageName = getOemDefaultDialerPackage();
+ String defaultSmsPackageName = getOemDefaultSmsPackage();
updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
defaultSmsPackageName);
}
@@ -7640,7 +7637,7 @@
}
mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
- if (isWorkProfileTelephonyFlagEnabled()) {
+ if (isWorkProfileTelephonyEnabled()) {
clearManagedSubscriptionsPolicy();
clearLauncherShortcutOverrides();
updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
@@ -10991,8 +10988,10 @@
synchronized (mSubscriptionsChangedListenerLock) {
pw.println("Subscription changed listener : " + mSubscriptionsChangedListener);
}
- pw.println(
- "Flag enable_work_profile_telephony : " + isWorkProfileTelephonyFlagEnabled());
+ pw.println("DPM Flag enable_work_profile_telephony : "
+ + isWorkProfileTelephonyDevicePolicyManagerFlagEnabled());
+ pw.println("Telephony Flag enable_work_profile_telephony : "
+ + isWorkProfileTelephonySubscriptionManagerFlagEnabled());
mHandler.post(() -> handleDump(pw));
dumpResources(pw);
@@ -11780,22 +11779,9 @@
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(canManageUsers(caller) || canQueryAdminPolicy(caller));
- // Move AccessibilityManager out of lock to prevent potential deadlock
- final List<AccessibilityServiceInfo> installedServices;
- long id = mInjector.binderClearCallingIdentity();
- try {
- UserInfo user = getUserInfo(userId);
- if (user.isManagedProfile()) {
- userId = user.profileGroupId;
- }
- installedServices = withAccessibilityManager(userId,
- AccessibilityManager::getInstalledAccessibilityServiceList);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ List<String> result = null;
synchronized (getLockObject()) {
- List<String> result = null;
// If we have multiple profiles we return the intersection of the
// permitted lists. This can happen in cases where we have a device
// and profile owner.
@@ -11817,9 +11803,22 @@
}
}
}
+ }
- // If we have a permitted list add all system accessibility services.
- if (result != null) {
+ // If we have a permitted list add all system accessibility services.
+ if (result != null) {
+ long id = mInjector.binderClearCallingIdentity();
+ try {
+ UserInfo user = getUserInfo(userId);
+ if (user.isManagedProfile()) {
+ userId = user.profileGroupId;
+ }
+ // Move AccessibilityManager out of {@link getLockObject} to prevent potential
+ // deadlock.
+ final List<AccessibilityServiceInfo> installedServices =
+ withAccessibilityManager(userId,
+ AccessibilityManager::getInstalledAccessibilityServiceList);
+
if (installedServices != null) {
for (AccessibilityServiceInfo service : installedServices) {
ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
@@ -11829,10 +11828,12 @@
}
}
}
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
}
-
- return result;
}
+
+ return result;
}
@Override
@@ -22703,11 +22704,24 @@
DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
}
- private static boolean isWorkProfileTelephonyFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- ENABLE_WORK_PROFILE_TELEPHONY_FLAG,
- DEFAULT_WORK_PROFILE_TELEPHONY_FLAG);
+ private boolean isWorkProfileTelephonyEnabled() {
+ return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
+ && isWorkProfileTelephonySubscriptionManagerFlagEnabled();
+ }
+
+ private boolean isWorkProfileTelephonyDevicePolicyManagerFlagEnabled() {
+ return DeviceConfig.getBoolean(NAMESPACE_DEVICE_POLICY_MANAGER,
+ ENABLE_WORK_PROFILE_TELEPHONY_FLAG, DEFAULT_WORK_PROFILE_TELEPHONY_FLAG);
+ }
+
+ private boolean isWorkProfileTelephonySubscriptionManagerFlagEnabled() {
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(NAMESPACE_TELEPHONY, ENABLE_WORK_PROFILE_TELEPHONY_FLAG,
+ false);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
@Override
@@ -22820,7 +22834,7 @@
@Override
public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
- if (isWorkProfileTelephonyFlagEnabled()) {
+ if (isWorkProfileTelephonyEnabled()) {
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
@@ -22834,7 +22848,7 @@
@Override
public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
- if (!isWorkProfileTelephonyFlagEnabled()) {
+ if (!isWorkProfileTelephonyEnabled()) {
throw new UnsupportedOperationException("This api is not enabled");
}
CallerIdentity caller = getCallerIdentity();
@@ -22842,9 +22856,10 @@
"This policy can only be set by a profile owner on an organization-owned "
+ "device.");
+ int parentUserId = getProfileParentId(caller.getUserId());
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
- if (hasUserSetupCompleted(UserHandle.USER_SYSTEM) && !isAdminTestOnlyLocked(
+ if (hasUserSetupCompleted(parentUserId) && !isAdminTestOnlyLocked(
admin.info.getComponent(), caller.getUserId())) {
throw new IllegalStateException("Not allowed to apply this policy after setup");
}
@@ -22866,7 +22881,6 @@
if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
final long id = mInjector.binderClearCallingIdentity();
try {
- int parentUserId = getProfileParentId(caller.getUserId());
installOemDefaultDialerAndSmsApp(caller.getUserId());
updateTelephonyCrossProfileIntentFilters(parentUserId, caller.getUserId(), true);
} finally {
@@ -22877,10 +22891,8 @@
private void installOemDefaultDialerAndSmsApp(int targetUserId) {
try {
- String defaultDialerPackageName = getDefaultRoleHolderPackageName(
- com.android.internal.R.string.config_defaultDialer);
- String defaultSmsPackageName = getDefaultRoleHolderPackageName(
- com.android.internal.R.string.config_defaultSms);
+ String defaultDialerPackageName = getOemDefaultDialerPackage();
+ String defaultSmsPackageName = getOemDefaultSmsPackage();
if (defaultDialerPackageName != null) {
mIPackageManager.installExistingPackageAsUser(defaultDialerPackageName,
@@ -22906,6 +22918,15 @@
}
}
+ private String getOemDefaultDialerPackage() {
+ TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ return telecomManager.getSystemDialerPackage();
+ }
+
+ private String getOemDefaultSmsPackage() {
+ return mContext.getString(R.string.config_defaultSms);
+ }
+
private void updateDialerAndSmsManagedShortcutsOverrideCache(
String defaultDialerPackageName, String defaultSmsPackageName) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index fd31b22..d559b67 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -84,9 +84,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -2904,108 +2901,6 @@
PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
- private static class TestDexModuleRegisterCallback
- extends PackageManager.DexModuleRegisterCallback {
- private String mDexModulePath = null;
- private boolean mSuccess = false;
- private String mMessage = null;
- CountDownLatch doneSignal = new CountDownLatch(1);
-
- @Override
- public void onDexModuleRegistered(String dexModulePath, boolean success, String message) {
- mDexModulePath = dexModulePath;
- mSuccess = success;
- mMessage = message;
- doneSignal.countDown();
- }
-
- boolean waitTillDone() {
- long startTime = System.currentTimeMillis();
- while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
- try {
- return doneSignal.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- Log.i(TAG, "Interrupted during sleep", e);
- }
- }
- return false;
- }
-
- }
-
- // Verify that the base code path cannot be registered.
- public void testRegisterDexModuleBaseCode() throws Exception {
- PackageManager pm = getPm();
- ApplicationInfo info = getContext().getApplicationInfo();
- TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
- pm.registerDexModule(info.getBaseCodePath(), callback);
- assertTrue(callback.waitTillDone());
- assertEquals(info.getBaseCodePath(), callback.mDexModulePath);
- assertFalse("BaseCodePath should not be registered", callback.mSuccess);
- }
-
- // Verify that modules which are not own by the calling package are not registered.
- public void testRegisterDexModuleNotOwningModule() throws Exception {
- TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
- String moduleBelongingToOtherPackage = "/data/user/0/com.google.android.gms/module.apk";
- getPm().registerDexModule(moduleBelongingToOtherPackage, callback);
- assertTrue(callback.waitTillDone());
- assertEquals(moduleBelongingToOtherPackage, callback.mDexModulePath);
- assertTrue(callback.waitTillDone());
- assertFalse("Only modules belonging to the calling package can be registered",
- callback.mSuccess);
- }
-
- // Verify that modules owned by the package are successfully registered.
- public void testRegisterDexModuleSuccessfully() throws Exception {
- ApplicationInfo info = getContext().getApplicationInfo();
- // Copy the main apk into the data folder and use it as a "module".
- File dexModuleDir = new File(info.dataDir, "module-dir");
- File dexModule = new File(dexModuleDir, "module.apk");
- try {
- assertNotNull(FileUtils.createDir(
- dexModuleDir.getParentFile(), dexModuleDir.getName()));
- Files.copy(Paths.get(info.getBaseCodePath()), dexModule.toPath(),
- StandardCopyOption.REPLACE_EXISTING);
- TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
- getPm().registerDexModule(dexModule.toString(), callback);
- assertTrue(callback.waitTillDone());
- assertEquals(dexModule.toString(), callback.mDexModulePath);
- assertTrue(callback.waitTillDone());
- assertTrue(callback.mMessage, callback.mSuccess);
-
- // NOTE:
- // This actually verifies internal behaviour which might change. It's not
- // ideal but it's the best we can do since there's no other place we can currently
- // write a better test.
- for(String isa : getAppDexInstructionSets(info)) {
- Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.odex"));
- Files.exists(Paths.get(dexModuleDir.toString(), "oat", isa, "module.vdex"));
- }
- } finally {
- FileUtils.deleteContentsAndDir(dexModuleDir);
- }
- }
-
- // If the module does not exist on disk we should get a failure.
- public void testRegisterDexModuleNotExists() throws Exception {
- ApplicationInfo info = getContext().getApplicationInfo();
- String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
- TestDexModuleRegisterCallback callback = new TestDexModuleRegisterCallback();
- getPm().registerDexModule(nonExistentApk, callback);
- assertTrue(callback.waitTillDone());
- assertEquals(nonExistentApk, callback.mDexModulePath);
- assertTrue(callback.waitTillDone());
- assertFalse("DexModule registration should fail", callback.mSuccess);
- }
-
- // If the module does not exist on disk we should get a failure.
- public void testRegisterDexModuleNotExistsNoCallback() throws Exception {
- ApplicationInfo info = getContext().getApplicationInfo();
- String nonExistentApk = Paths.get(info.dataDir, "non-existent.apk").toString();
- getPm().registerDexModule(nonExistentApk, null);
- }
-
@LargeTest
public void testMinInstallableTargetSdkPass() throws Exception {
// Test installing a package that meets the minimum installable sdk requirement
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
index 8715afd..a93e8ad 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -95,9 +95,13 @@
state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_REJECT);
- assertFalse("Verification should not be marked as complete yet",
+ assertTrue("Verification should be considered complete now",
state.isVerificationComplete());
+ assertFalse("Installation should be marked as denied",
+ state.isInstallAllowed());
+
+ // Nothing changes.
state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_REJECT);
assertTrue("Verification should be considered complete now",
@@ -117,9 +121,13 @@
state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_REJECT);
- assertFalse("Verification should not be marked as complete yet",
+ assertTrue("Verification should be considered complete now",
state.isVerificationComplete());
+ assertFalse("Installation should be marked as denied",
+ state.isInstallAllowed());
+
+ // Nothing changes.
state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW);
assertTrue("Verification should be considered complete now",
@@ -151,6 +159,162 @@
state.isInstallAllowed());
}
+ public void testPackageVerificationState_TwoRequiredVerifiers_SecondTimesOut_DefaultAllow() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+ state.addRequiredVerifierUid(REQUIRED_UID_2);
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ // Timeout with default ALLOW.
+ processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_2, true);
+ }
+
+ public void testPackageVerificationState_TwoRequiredVerifiers_SecondTimesOut_DefaultReject() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+ state.addRequiredVerifierUid(REQUIRED_UID_2);
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ // Timeout with default REJECT.
+ processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_2, false);
+ }
+
+ public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_DefaultAllow() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+ state.addRequiredVerifierUid(REQUIRED_UID_2);
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ // Timeout with default ALLOW.
+ processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_DefaultReject() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+ state.addRequiredVerifierUid(REQUIRED_UID_2);
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ // Timeout with default REJECT.
+ processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_1);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertFalse("Installation should be marked as denied",
+ state.isInstallAllowed());
+
+ // Nothing changes.
+ state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertFalse("Installation should be marked as denied",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_SecondExtends_DefaultAllow() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+ state.addRequiredVerifierUid(REQUIRED_UID_2);
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.extendTimeout(REQUIRED_UID_2);
+
+ // Timeout with default ALLOW.
+ processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ assertTrue("Timeout is extended",
+ state.timeoutExtended(REQUIRED_UID_2));
+
+ state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_TwoRequiredVerifiers_FirstTimesOut_SecondExtends_DefaultReject() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+ state.addRequiredVerifierUid(REQUIRED_UID_2);
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.extendTimeout(REQUIRED_UID_2);
+
+ // Timeout with default REJECT.
+ processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_1);
+
+ assertFalse("Timeout should not be extended for this verifier",
+ state.timeoutExtended(REQUIRED_UID_2));
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertFalse("Installation should be marked as denied",
+ state.isInstallAllowed());
+
+ // Nothing changes.
+ state.setVerifierResponse(REQUIRED_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertFalse("Installation should be marked as denied",
+ state.isInstallAllowed());
+ }
+
public void testPackageVerificationState_RequiredAndOneSufficient_RequiredDeniedInstall() {
PackageVerificationState state = new PackageVerificationState(null);
state.addRequiredVerifierUid(REQUIRED_UID_1);
@@ -231,6 +395,66 @@
state.isInstallAllowed());
}
+ public void testPackageVerificationState_RequiredAllow_SufficientTimesOut_DefaultAllow() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ // Required allows.
+ state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+ // Timeout with default ALLOW.
+ processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1, true);
+ }
+
+ public void testPackageVerificationState_RequiredExtendAllow_SufficientTimesOut_DefaultAllow() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ // Extend first.
+ state.extendTimeout(REQUIRED_UID_1);
+
+ // Required allows.
+ state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+ // Timeout with default ALLOW.
+ processOnTimeout(state, PackageManager.VERIFICATION_ALLOW, REQUIRED_UID_1, true);
+ }
+
+ public void testPackageVerificationState_RequiredAllow_SufficientTimesOut_DefaultReject() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.addRequiredVerifierUid(REQUIRED_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ // Required allows.
+ state.setVerifierResponse(REQUIRED_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+ // Timeout with default REJECT.
+ processOnTimeout(state, PackageManager.VERIFICATION_REJECT, REQUIRED_UID_1, true);
+ }
+
public void testPackageVerificationState_RequiredAndTwoSufficient_OneSufficientIsEnough() {
PackageVerificationState state = new PackageVerificationState(null);
state.addRequiredVerifierUid(REQUIRED_UID_1);
@@ -400,4 +624,25 @@
assertFalse(state.areAllVerificationsComplete());
}
+
+ private void processOnTimeout(PackageVerificationState state, int code, int uid) {
+ // CHECK_PENDING_VERIFICATION handler.
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+ assertFalse("Timeout should not be extended for this verifier",
+ state.timeoutExtended(uid));
+
+ PackageVerificationResponse response = new PackageVerificationResponse(code, uid);
+ VerificationUtils.processVerificationResponseOnTimeout(-1, state, response, null);
+ }
+
+ private void processOnTimeout(PackageVerificationState state, int code, int uid,
+ boolean expectAllow) {
+ processOnTimeout(state, code, uid);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+ assertEquals("Installation should be marked as " + (expectAllow ? "allowed" : "rejected"),
+ expectAllow, state.isInstallAllowed());
+ }
}
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_cur_freq
deleted file mode 100644
index 80b164d..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-"1.23
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_max_freq
deleted file mode 100644
index 582d8c8..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-+2.5
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_max_freq
deleted file mode 100644
index 749fce6..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_cur_freq
deleted file mode 100644
index dadd973..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq
index 573541a..dadd973 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_cur_freq
deleted file mode 100644
index 749fce6..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq
index 573541a..749fce6 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_cur_freq
deleted file mode 100644
index dadd973..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq
index 573541a..dadd973 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_cur_freq
deleted file mode 100644
index 749fce6..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_cur_freq
+++ /dev/null
@@ -1 +0,0 @@
-1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_max_freq
deleted file mode 100644
index a93d6f7..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/cpuinfo_max_freq
+++ /dev/null
@@ -1 +0,0 @@
-2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq
index 573541a..749fce6 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_cur_freq
@@ -1 +1 @@
-0
+1000000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq
index 573541a..a93d6f7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy0/scaling_max_freq
@@ -1 +1 @@
-0
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_cur_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_cur_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_cur_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_max_freq
similarity index 100%
rename from services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/cpuinfo_max_freq
rename to services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy1/scaling_max_freq
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_cur_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_cur_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_max_freq
deleted file mode 100644
index e69de29..0000000
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_without_time_in_state_2/policy2/cpuinfo_max_freq
+++ /dev/null
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index f9f5325..a5adf3f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -2778,51 +2778,70 @@
setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, getNewMockPendingIntent());
}
- final String otherUidPackage1 = "other.uid.package1";
- final String otherUidPackage2 = "other.uid.package2";
- final int otherUid = 1243;
+ final String otherPackage1 = "other.package1";
+ final String otherPackage2 = "other.package2";
+ final int otherAppId = 1243;
+ final int otherUser1 = 31;
+ final int otherUser2 = 8;
+ final int otherUid1 = UserHandle.getUid(otherUser1, otherAppId);
+ final int otherUid2 = UserHandle.getUid(otherUser2, otherAppId);
registerAppIds(
- new String[]{TEST_CALLING_PACKAGE, otherUidPackage1, otherUidPackage2},
- new Integer[]{TEST_CALLING_UID, otherUid, otherUid}
+ new String[]{TEST_CALLING_PACKAGE, otherPackage1, otherPackage2},
+ new Integer[]{UserHandle.getAppId(TEST_CALLING_UID), otherAppId, otherAppId}
);
for (int i = 0; i < 9; i++) {
setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 11, 0,
- getNewMockPendingIntent(otherUid, otherUidPackage1), 0, 0, otherUid,
- otherUidPackage1, null);
+ getNewMockPendingIntent(otherUid1, otherPackage1), 0, 0, otherUid1,
+ otherPackage1, null);
}
for (int i = 0; i < 8; i++) {
setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 20, 0,
- getNewMockPendingIntent(otherUid, otherUidPackage2), 0, 0, otherUid,
- otherUidPackage2, null);
+ getNewMockPendingIntent(otherUid1, otherPackage2), 0, 0, otherUid1,
+ otherPackage2, null);
}
- assertEquals(27, mService.mAlarmStore.size());
+ for (int i = 0; i < 7; i++) {
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 28, 0,
+ getNewMockPendingIntent(otherUid2, otherPackage2), 0, 0, otherUid2,
+ otherPackage2, null);
+ }
+
+ assertEquals(34, mService.mAlarmStore.size());
try {
- mBinder.removeAll(otherUidPackage1);
+ mBinder.removeAll(otherPackage1);
fail("removeAll() for wrong package did not throw SecurityException");
} catch (SecurityException se) {
// Expected
}
try {
- mBinder.removeAll(otherUidPackage2);
+ mBinder.removeAll(otherPackage2);
fail("removeAll() for wrong package did not throw SecurityException");
} catch (SecurityException se) {
// Expected
}
mBinder.removeAll(TEST_CALLING_PACKAGE);
- assertEquals(17, mService.mAlarmStore.size());
+ assertEquals(24, mService.mAlarmStore.size());
assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(TEST_CALLING_PACKAGE)));
- mTestCallingUid = otherUid;
- mBinder.removeAll(otherUidPackage1);
- assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage1)));
- assertEquals(8, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage2)));
+ mTestCallingUid = otherUid1;
+ mBinder.removeAll(otherPackage1);
+ assertEquals(15, mService.mAlarmStore.size());
+ assertEquals(15, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2)));
+ assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherPackage1)));
+
+ mBinder.removeAll(otherPackage2);
+ assertEquals(7, mService.mAlarmStore.size());
+ assertEquals(7, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2)));
+
+ mTestCallingUid = otherUid2;
+ mBinder.removeAll(otherPackage2);
+ assertEquals(0, mService.mAlarmStore.size());
}
@Test
@@ -3856,4 +3875,52 @@
assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
}
+
+ @Test
+ public void lookForPackageLocked() throws Exception {
+ final String package2 = "test.package.2";
+ final int uid2 = 359712;
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 10, getNewMockPendingIntent());
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15,
+ getNewMockPendingIntent(uid2, package2));
+
+ doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong());
+
+ assertTrue(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ assertTrue(mService.lookForPackageLocked(package2, uid2));
+
+ mNowElapsedTest += 10; // Advance time past the first alarm only.
+ mTestTimer.expire();
+
+ assertTrue(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ assertTrue(mService.lookForPackageLocked(package2, uid2));
+
+ // The non-wakeup alarm is sent on interactive state change: false -> true.
+ mService.interactiveStateChangedLocked(false);
+ mService.interactiveStateChangedLocked(true);
+
+ assertFalse(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ assertTrue(mService.lookForPackageLocked(package2, uid2));
+
+ mNowElapsedTest += 10; // Advance time past the second alarm.
+ mTestTimer.expire();
+
+ assertFalse(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
+ assertFalse(mService.lookForPackageLocked(package2, uid2));
+ }
+
+ @Test
+ public void onQueryPackageRestart() {
+ final String[] packages = {"p1", "p2", "p3"};
+ final int uid = 5421;
+ final Intent packageAdded = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART)
+ .setData(Uri.fromParts("package", packages[0], null))
+ .putExtra(Intent.EXTRA_PACKAGES, packages)
+ .putExtra(Intent.EXTRA_UID, uid);
+ mPackageChangesReceiver.onReceive(mMockContext, packageAdded);
+
+ for (String p : packages) {
+ verify(mService).lookForPackageLocked(p, uid);
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
index a129f39..246b0f04 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
@@ -210,4 +210,26 @@
createDefaultAlarm(anything, anything, FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)));
assertTrue("Alarm clock not exempt", isExemptFromTare(createAlarmClock(anything)));
}
+
+ @Test
+ public void snapshotImmutable() {
+ final Alarm a = createDefaultAlarm(0, 0, 0);
+
+ final Random random = new Random(234);
+ final long[] policyElapsed = new long[NUM_POLICIES];
+ for (int i = 0; i < NUM_POLICIES; i++) {
+ a.setPolicyElapsed(i, policyElapsed[i] = random.nextInt(1 << 10));
+ }
+
+ final Alarm.Snapshot snapshot = new Alarm.Snapshot(a);
+
+ for (int i = 0; i < NUM_POLICIES; i++) {
+ assertEquals(policyElapsed[i], snapshot.mPolicyWhenElapsed[i]);
+ }
+
+ for (int i = 0; i < NUM_POLICIES; i++) {
+ a.setPolicyElapsed(i, policyElapsed[i] + 5 + i);
+ assertEquals(policyElapsed[i], snapshot.mPolicyWhenElapsed[i]);
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 8a5d3a6..390119c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -50,7 +50,9 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -354,6 +356,33 @@
}
/**
+ * Queue with a "normal" and "deferrable" broadcast is runnable at different times depending
+ * on process cached state; when cached it's delayed indefinitely.
+ */
+ @Test
+ public void testRunnableAt_Normal_Deferrable() {
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+ final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
+ List.of(makeMockRegisteredReceiver()), false);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+
+ queue.setProcessCached(false);
+ final long notCachedRunnableAt = queue.getRunnableAt();
+ queue.setProcessCached(true);
+ final long cachedRunnableAt = queue.getRunnableAt();
+ assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
+ assertFalse(queue.isRunnable());
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ queue.getRunnableAtReason());
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+ }
+
+ /**
* Queue with a "normal" broadcast is runnable at different times depending
* on process cached state; when cached it's delayed by some amount.
*/
@@ -363,8 +392,10 @@
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
- List.of(makeMockRegisteredReceiver()));
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
+ List.of(makeMockRegisteredReceiver()), false);
queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
queue.setProcessCached(false);
@@ -372,6 +403,8 @@
queue.setProcessCached(true);
final long cachedRunnableAt = queue.getRunnableAt();
assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
+ assertTrue(queue.isRunnable());
+ assertEquals(BroadcastProcessQueue.REASON_CACHED, queue.getRunnableAtReason());
assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
}
@@ -518,11 +551,13 @@
}
@Test
- public void testRunnableAt_Cached_Prioritized() {
+ public void testRunnableAt_Cached_Prioritized_NonDeferrable() {
final List receivers = List.of(
withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
- doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
receivers, null, false), REASON_CONTAINS_PRIORITIZED);
}
@@ -1078,7 +1113,8 @@
eq(getUidForPackage(PACKAGE_GREEN)), anyInt(), eq(Intent.ACTION_TIME_TICK),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
- anyLong(), anyLong(), anyLong(), anyInt()), times(1));
+ anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class), anyString()),
+ times(1));
}
private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 2d4f5ca..bca39ae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1646,6 +1646,79 @@
}
/**
+ * Verify prioritized receivers work as expected with deferrable broadcast - broadcast to
+ * app in cached state should be deferred and the rest should be delivered as per the priority
+ * order.
+ */
+ @Test
+ public void testPrioritized_withDeferrableBroadcasts() throws Exception {
+ // Legacy stack doesn't support deferral
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+ final ProcessRecord receiverOrangeApp = makeActiveProcessRecord(PACKAGE_ORANGE);
+
+ receiverGreenApp.setCached(true);
+ receiverBlueApp.setCached(true);
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions opts = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+ final List receivers = List.of(
+ makeRegisteredReceiver(callerApp, 10),
+ makeRegisteredReceiver(receiverGreenApp, 9),
+ makeRegisteredReceiver(receiverBlueApp, 8),
+ makeRegisteredReceiver(receiverYellowApp, 8),
+ makeRegisteredReceiver(receiverOrangeApp, 7)
+ );
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, receivers));
+ waitForIdle();
+
+ // Green ignored since it's in cached state
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+ // Blue ignored since it's in cached state
+ verifyScheduleRegisteredReceiver(never(), receiverBlueApp, timeTick);
+
+ final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED)).getThread();
+ final IApplicationThread yellowThread = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW)).getThread();
+ final IApplicationThread orangeThread = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+ getUidForPackage(PACKAGE_ORANGE)).getThread();
+
+ // Verify apps that are not in cached state will receive the broadcast in the order
+ // we expect.
+ final InOrder inOrder = inOrder(redThread, yellowThread, orangeThread);
+ inOrder.verify(redThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+ inOrder.verify(yellowThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+ inOrder.verify(orangeThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+
+ // Shift blue to be active and confirm that deferred broadcast is delivered
+ receiverBlueApp.setCached(false);
+ mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), false);
+ waitForIdle();
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
+
+ // Shift green to be active and confirm that deferred broadcast is delivered
+ receiverGreenApp.setCached(false);
+ mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false);
+ waitForIdle();
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+ }
+
+ /**
* Verify that we handle replacing a pending broadcast.
*/
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 51dcc03..0ab984b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -21,11 +21,14 @@
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -672,6 +675,7 @@
@Test
public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+ // New display device
setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
@@ -711,6 +715,56 @@
verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
}
+ @Test
+ public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+ float lux = 2000;
+ float brightness = 0.4f;
+ float nits = 500;
+ when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+ when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+ when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+ when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1);
+ clearInvocations(mHolder.injector);
+
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ advanceTime(1);
+
+ verify(mHolder.injector).getAutomaticBrightnessController(
+ any(AutomaticBrightnessController.Callbacks.class),
+ any(Looper.class),
+ eq(mSensorManagerMock),
+ any(),
+ eq(mHolder.brightnessMappingStrategy),
+ anyInt(),
+ anyFloat(),
+ anyFloat(),
+ anyFloat(),
+ anyInt(),
+ anyInt(),
+ anyLong(),
+ anyLong(),
+ anyBoolean(),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ eq(mContextSpy),
+ any(HighBrightnessModeController.class),
+ any(BrightnessThrottler.class),
+ isNull(),
+ anyInt(),
+ anyInt(),
+ eq(lux),
+ eq(brightness)
+ );
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -796,9 +850,9 @@
final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
mock(ScreenOffBrightnessSensorController.class);
- TestInjector injector = new TestInjector(displayPowerState, animator,
+ TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController);
+ hysteresisLevels, screenOffBrightnessSensorController));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -816,7 +870,8 @@
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, wakelockController,
- screenOffBrightnessSensorController, hbmMetadata);
+ screenOffBrightnessSensorController, hbmMetadata, brightnessMappingStrategy,
+ injector);
}
/**
@@ -833,6 +888,8 @@
public final WakelockController wakelockController;
public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
public final HighBrightnessModeMetadata hbmMetadata;
+ public final BrightnessMappingStrategy brightnessMappingStrategy;
+ public final DisplayPowerController2.Injector injector;
DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
@@ -840,7 +897,9 @@
AutomaticBrightnessController automaticBrightnessController,
WakelockController wakelockController,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
- HighBrightnessModeMetadata hbmMetadata) {
+ HighBrightnessModeMetadata hbmMetadata,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ DisplayPowerController2.Injector injector) {
this.dpc = dpc;
this.display = display;
this.displayPowerState = displayPowerState;
@@ -850,6 +909,8 @@
this.wakelockController = wakelockController;
this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
this.hbmMetadata = hbmMetadata;
+ this.brightnessMappingStrategy = brightnessMappingStrategy;
+ this.injector = injector;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 0a1bf1c..c021ef6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -21,11 +21,14 @@
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -676,6 +679,7 @@
@Test
public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+ // New display device
setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
@@ -715,6 +719,56 @@
verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
}
+ @Test
+ public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+ float lux = 2000;
+ float brightness = 0.4f;
+ float nits = 500;
+ when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+ when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+ when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+ when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1);
+ clearInvocations(mHolder.injector);
+
+ // New display device
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+ advanceTime(1);
+
+ verify(mHolder.injector).getAutomaticBrightnessController(
+ any(AutomaticBrightnessController.Callbacks.class),
+ any(Looper.class),
+ eq(mSensorManagerMock),
+ any(),
+ eq(mHolder.brightnessMappingStrategy),
+ anyInt(),
+ anyFloat(),
+ anyFloat(),
+ anyFloat(),
+ anyInt(),
+ anyInt(),
+ anyLong(),
+ anyLong(),
+ anyBoolean(),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ any(HysteresisLevels.class),
+ eq(mContextSpy),
+ any(HighBrightnessModeController.class),
+ any(BrightnessThrottler.class),
+ isNull(),
+ anyInt(),
+ anyInt(),
+ eq(lux),
+ eq(brightness)
+ );
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -799,9 +853,9 @@
final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
mock(ScreenOffBrightnessSensorController.class);
- DisplayPowerController.Injector injector = new TestInjector(displayPowerState, animator,
+ DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
- screenOffBrightnessSensorController);
+ screenOffBrightnessSensorController));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -819,7 +873,7 @@
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, screenOffBrightnessSensorController,
- hbmMetadata);
+ hbmMetadata, brightnessMappingStrategy, injector);
}
/**
@@ -835,13 +889,17 @@
public final AutomaticBrightnessController automaticBrightnessController;
public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
public final HighBrightnessModeMetadata hbmMetadata;
+ public final BrightnessMappingStrategy brightnessMappingStrategy;
+ public final DisplayPowerController.Injector injector;
DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display,
DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
DualRampAnimator<DisplayPowerState> animator,
AutomaticBrightnessController automaticBrightnessController,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
- HighBrightnessModeMetadata hbmMetadata) {
+ HighBrightnessModeMetadata hbmMetadata,
+ BrightnessMappingStrategy brightnessMappingStrategy,
+ DisplayPowerController.Injector injector) {
this.dpc = dpc;
this.display = display;
this.displayPowerState = displayPowerState;
@@ -850,6 +908,8 @@
this.automaticBrightnessController = automaticBrightnessController;
this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
this.hbmMetadata = hbmMetadata;
+ this.brightnessMappingStrategy = brightnessMappingStrategy;
+ this.injector = injector;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index d5aa7fe..9a7ee4d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -47,6 +47,7 @@
import android.os.HandlerThread;
import android.os.PowerManager;
import android.os.Process;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.internal.util.IndentingPrintWriter;
@@ -56,6 +57,7 @@
import com.android.server.pm.dex.DexoptOptions;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -126,6 +128,10 @@
@Before
public void setUp() throws Exception {
+ // These tests are only applicable to the legacy BackgroundDexOptService and cannot be run
+ // when ART Service is enabled.
+ Assume.assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false));
+
when(mInjector.getCallingUid()).thenReturn(Process.FIRST_APPLICATION_UID);
when(mInjector.getContext()).thenReturn(mContext);
when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 70b5ac0..386fd3e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -674,13 +674,13 @@
}
protected void expectDisplayAssignedToUser(@UserIdInt int userId, int displayId) {
- expectWithMessage("getDisplayAssignedToUser(%s)", userId)
- .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(displayId);
+ expectWithMessage("getMainDisplayAssignedToUser(%s)", userId)
+ .that(mMediator.getMainDisplayAssignedToUser(userId)).isEqualTo(displayId);
}
protected void expectNoDisplayAssignedToUser(@UserIdInt int userId) {
- expectWithMessage("getDisplayAssignedToUser(%s)", userId)
- .that(mMediator.getDisplayAssignedToUser(userId)).isEqualTo(INVALID_DISPLAY);
+ expectWithMessage("getMainDisplayAssignedToUser(%s)", userId)
+ .that(mMediator.getMainDisplayAssignedToUser(userId)).isEqualTo(INVALID_DISPLAY);
}
protected void expectDisplaysAssignedToUserContainsDisplayId(
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
index 84a61c7..a9b68eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
@@ -136,17 +136,30 @@
mEconomicPolicy.getMinSatiatedConsumptionLimit());
assertEquals(EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getMaxSatiatedConsumptionLimit());
+
final String pkgRestricted = "com.pkg.restricted";
when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(0, mEconomicPolicy.getMinSatiatedBalance(0, pkgRestricted));
assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
- assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
- mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
+
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance(0, pkgExempted));
+
+ final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
+ when(mIrs.isHeadlessSystemApp(eq(pkgHeadlessSystemApp))).thenReturn(true);
+ assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
+ assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance(0, pkgHeadlessSystemApp));
+
assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
}
@Test
@@ -156,6 +169,8 @@
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(25));
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(8));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
@@ -168,6 +183,9 @@
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
+ when(mIrs.isHeadlessSystemApp(eq(pkgHeadlessSystemApp))).thenReturn(true);
+ assertEquals(arcToCake(8), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
assertEquals(arcToCake(7), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
@@ -179,6 +197,8 @@
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(-5));
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(-1));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(-3));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
@@ -191,6 +211,9 @@
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
+ when(mIrs.isHeadlessSystemApp(eq(pkgHeadlessSystemApp))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
// Test min+max reversed.
@@ -199,6 +222,8 @@
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(3));
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(12));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
@@ -207,6 +232,7 @@
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
index cad608f..d66e74a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -189,6 +189,10 @@
setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(11));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(8));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(6));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(4));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(3));
setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(2));
@@ -202,6 +206,9 @@
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
+ when(mIrs.isHeadlessSystemApp(eq(pkgHeadlessSystemApp))).thenReturn(true);
+ assertEquals(arcToCake(10), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index ebf760c..22c7310 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -149,6 +149,13 @@
assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
mEconomicPolicy.getMaxSatiatedBalance(0, pkgExempted));
+ final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
+ when(mIrs.isHeadlessSystemApp(eq(pkgHeadlessSystemApp))).thenReturn(true);
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance(0, pkgHeadlessSystemApp));
+
final String pkgUpdater = "com.pkg.updater";
when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
assertEquals(5 * EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES
@@ -177,6 +184,8 @@
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(25));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(5));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
arcToCake(1));
@@ -191,6 +200,9 @@
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(6), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
+ when(mIrs.isHeadlessSystemApp(eq(pkgHeadlessSystemApp))).thenReturn(true);
+ assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
assertEquals(arcToCake(4), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
final String pkgUpdater = "com.pkg.updater";
when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(3);
@@ -206,6 +218,8 @@
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(-5));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(-3));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
arcToCake(-4));
@@ -220,6 +234,9 @@
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
+ when(mIrs.isHeadlessSystemApp(eq(pkgHeadlessSystemApp))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
final String pkgUpdater = "com.pkg.updater";
when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
@@ -232,6 +249,8 @@
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(3));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+ arcToCake(12));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
@@ -240,6 +259,7 @@
assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 24b003c..caa2e36 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -18,6 +18,7 @@
import static android.database.sqlite.SQLiteDatabase.deleteDatabase;
+import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -719,6 +720,41 @@
}
@SmallTest
+ public void testStartAddAccountSessionWhereAuthenticatorReturnsIntentWithProhibitedFlags()
+ throws Exception {
+ unlockSystemUser();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+ when(mMockPackageManager.resolveActivityAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+ when(mMockPackageManager.checkSignatures(
+ anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Response response = new Response(latch, mMockAccountManagerResponse);
+ Bundle options = createOptionsWithAccountName(
+ AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE);
+ int prohibitedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+ options.putInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, prohibitedFlags);
+
+ mAms.startAddAccountSession(
+ response, // response
+ AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+ "authTokenType",
+ null, // requiredFeatures
+ true, // expectActivityLaunch
+ options); // optionsIn
+ waitForLatch(latch);
+
+ verify(mMockAccountManagerResponse).onError(
+ eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), contains("invalid intent"));
+ }
+
+ @SmallTest
public void testStartAddAccountSessionError() throws Exception {
unlockSystemUser();
Bundle options = createOptionsWithAccountName(
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
index 73f30d9..b98a6a8 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
@@ -17,9 +17,6 @@
import android.accounts.Account;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Constants shared between test AccountAuthenticators and AccountManagerServiceTest.
*/
@@ -31,6 +28,8 @@
"account_manager_service_test:account_status_token_key";
public static final String KEY_ACCOUNT_PASSWORD =
"account_manager_service_test:account_password_key";
+ public static final String KEY_INTENT_FLAGS =
+ "account_manager_service_test:intent_flags_key";
public static final String KEY_OPTIONS_BUNDLE =
"account_manager_service_test:option_bundle_key";
public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com";
diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
index 8106364..924443e 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
@@ -24,8 +24,6 @@
import android.content.Intent;
import android.os.Bundle;
-import com.android.frameworks.servicestests.R;
-
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -270,11 +268,13 @@
String accountName = null;
Bundle sessionBundle = null;
String password = null;
+ int intentFlags = 0;
if (options != null) {
accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME);
sessionBundle = options.getBundle(
AccountManagerServiceTestFixtures.KEY_ACCOUNT_SESSION_BUNDLE);
password = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_PASSWORD);
+ intentFlags = options.getInt(AccountManagerServiceTestFixtures.KEY_INTENT_FLAGS, 0);
}
Bundle result = new Bundle();
@@ -302,6 +302,7 @@
intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
eventualActivityResultData);
intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+ intent.setFlags(intentFlags);
result.putParcelable(AccountManager.KEY_INTENT, intent);
} else {
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index 8f07238..3ad24de 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -80,7 +80,9 @@
asAdapter = mMockAudioSystem;
}
mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
- false /*headTrackingEnabledByDefault*/);
+ true /*binauralEnabledDefault*/,
+ true /*transauralEnabledDefault*/,
+ false /*headTrackingEnabledDefault*/);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
new file mode 100644
index 0000000..5adf391
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 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.server.biometrics.log;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.WakeReason;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class BiometricFrameworkStatsLoggerTest {
+
+ @Test
+ public void testConvertsWakeReason_whenEmpty() {
+ final OperationContextExt ctx = new OperationContextExt();
+
+ final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+ final int[] reasonDetails = BiometricFrameworkStatsLogger
+ .toProtoWakeReasonDetails(ctx);
+
+ assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+ assertThat(reasonDetails).isEmpty();
+ }
+
+ @Test
+ public void testConvertsWakeReason_whenPowerReason() {
+ final OperationContext context = new OperationContext();
+ context.wakeReason = WakeReason.WAKE_MOTION;
+ final OperationContextExt ctx = new OperationContextExt(context);
+
+ final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+ final int[] reasonDetails = BiometricFrameworkStatsLogger
+ .toProtoWakeReasonDetails(new OperationContextExt(context));
+
+ assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION);
+ assertThat(reasonDetails).isEmpty();
+ }
+
+ @Test
+ public void testConvertsWakeReason_whenFaceReason() {
+ final OperationContext context = new OperationContext();
+ context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
+ AuthenticateReason.Face.ASSISTANT_VISIBLE);
+ final OperationContextExt ctx = new OperationContextExt(context);
+
+ final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+ final int[] reasonDetails = BiometricFrameworkStatsLogger
+ .toProtoWakeReasonDetails(ctx);
+
+ assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+ assertThat(reasonDetails).asList().containsExactly(
+ BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE);
+ }
+
+ @Test
+ public void testConvertsWakeReason_whenVendorReason() {
+ final OperationContext context = new OperationContext();
+ context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
+ new AuthenticateReason.Vendor());
+ final OperationContextExt ctx = new OperationContextExt(context);
+
+ final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+ final int[] reasonDetails = BiometricFrameworkStatsLogger
+ .toProtoWakeReasonDetails(ctx);
+
+ assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN);
+ assertThat(reasonDetails).isEmpty();
+ }
+
+
+ @Test
+ public void testConvertsWakeReason_whenPowerAndFaceReason() {
+ final OperationContext context = new OperationContext();
+ context.wakeReason = WakeReason.WAKE_KEY;
+ context.authenticateReason = AuthenticateReason.faceAuthenticateReason(
+ AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN);
+ final OperationContextExt ctx = new OperationContextExt(context);
+
+ final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+ final int[] reasonDetails = BiometricFrameworkStatsLogger
+ .toProtoWakeReasonDetails(ctx);
+
+ assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_KEY);
+ assertThat(reasonDetails).asList().containsExactly(
+ BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN);
+ }
+
+ @Test
+ public void testConvertsWakeReason_whenPowerAndVendorReason() {
+ final OperationContext context = new OperationContext();
+ context.wakeReason = WakeReason.LID;
+ context.authenticateReason = AuthenticateReason.vendorAuthenticateReason(
+ new AuthenticateReason.Vendor());
+ final OperationContextExt ctx = new OperationContextExt(context);
+
+ final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx);
+ final int[] reasonDetails = BiometricFrameworkStatsLogger
+ .toProtoWakeReasonDetails(ctx);
+
+ assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_LID);
+ assertThat(reasonDetails).isEmpty();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index aaabb28..4f74ef8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5002,6 +5002,8 @@
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "true", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TELEPHONY,
+ FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "true", false);
// Even if the caller is the managed profile, the current user is the user 0
when(getServices().iactivityManager.getCurrentUser())
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
@@ -5064,6 +5066,8 @@
verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TELEPHONY,
+ FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 94d30bb..6d2ce7f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -34,7 +34,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -65,12 +67,14 @@
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.MessageQueue;
import android.os.Process;
+import android.os.RemoteException;
import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayCutout;
@@ -1024,11 +1028,14 @@
}
@Test
- public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() throws Exception {
+ public void testCreateVirtualDisplay_setContentRecordingSessionSuccess()
+ throws RemoteException {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
when(mMockWindowManagerInternal
.setContentRecordingSession(any(ContentRecordingSession.class)))
.thenReturn(true);
+ IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
VIRTUAL_DISPLAY_NAME, 600, 800, 320);
@@ -1042,17 +1049,19 @@
DisplayManagerService.BinderService binderService = displayManager.new BinderService();
final int displayId = binderService.createVirtualDisplay(builder.build(),
- mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ mMockAppToken /* callback */, projection, PACKAGE_NAME);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
}
@Test
- public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws Exception {
+ public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
when(mMockWindowManagerInternal
.setContentRecordingSession(any(ContentRecordingSession.class)))
.thenReturn(false);
+ IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
VIRTUAL_DISPLAY_NAME, 600, 800, 320);
@@ -1066,11 +1075,96 @@
DisplayManagerService.BinderService binderService = displayManager.new BinderService();
final int displayId = binderService.createVirtualDisplay(builder.build(),
- mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ mMockAppToken /* callback */, projection, PACKAGE_NAME);
assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
}
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags() {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ // Set no flags for the VirtualDisplay.
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ // Pass in a null projection.
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ // VirtualDisplay is created but not for mirroring.
+ assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag() {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ // Set a non-mirroring flag for the VirtualDisplay.
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+ builder.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ // Pass in a null projection.
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ // VirtualDisplay is created but not for mirroring.
+ assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag()
+ throws RemoteException {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mMockWindowManagerInternal
+ .setContentRecordingSession(any(ContentRecordingSession.class)))
+ .thenReturn(true);
+ IMediaProjection projection = mock(IMediaProjection.class);
+ doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+ // Set no flags for the VirtualDisplay.
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ // Pass in a non-null projection.
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+ // VirtualDisplay is created for mirroring.
+ assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+ verify(mMockWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+ }
+
/**
* Tests that the virtual display is created with
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 1b02799..db5a469 100644
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -2300,7 +2300,7 @@
// We don't expect any interaction with DeviceConfig when the director is initialized
// because we explicitly avoid doing this as this can lead to a latency spike in the
// startup of DisplayManagerService
- // Verify all the loaded values are from DisplayDeviceConfig
+ // Verify all the loaded values are from config.xml
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
0.0);
@@ -2332,6 +2332,7 @@
when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75);
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+ // Verify the new values are from the freshly loaded DisplayDeviceConfig.
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
0.0);
@@ -2362,6 +2363,7 @@
// Need to wait for the property change to propagate to the main thread.
waitForIdleSync();
+ // Verify the values are loaded from the DeviceConfig.
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
0.0);
@@ -2377,6 +2379,35 @@
new int[]{20});
assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70);
assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80);
+
+ // Reset the DeviceConfig
+ config.setDefaultPeakRefreshRate(null);
+ config.setRefreshRateInHighZone(null);
+ config.setRefreshRateInLowZone(null);
+ config.setLowAmbientBrightnessThresholds(new int[]{});
+ config.setLowDisplayBrightnessThresholds(new int[]{});
+ config.setHighDisplayBrightnessThresholds(new int[]{});
+ config.setHighAmbientBrightnessThresholds(new int[]{});
+ config.setRefreshRateInHbmHdr(null);
+ config.setRefreshRateInHbmSunlight(null);
+ waitForIdleSync();
+
+ // verify the new values now fallback to DisplayDeviceConfig
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
+ 0.0);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
+ assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
+ assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+ new int[]{210});
+ assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+ new int[]{2100});
+ assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+ new int[]{25});
+ assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+ new int[]{30});
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
}
@Test
@@ -2536,18 +2567,18 @@
super.addOnPropertiesChangedListener(namespace, executor, listener);
}
- void setRefreshRateInLowZone(int fps) {
+ void setRefreshRateInLowZone(Integer fps) {
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE,
String.valueOf(fps));
}
- void setRefreshRateInHbmSunlight(int fps) {
+ void setRefreshRateInHbmSunlight(Integer fps) {
putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps));
}
- void setRefreshRateInHbmHdr(int fps) {
+ void setRefreshRateInHbmHdr(Integer fps) {
putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
}
@@ -2583,13 +2614,13 @@
thresholds);
}
- void setRefreshRateInHighZone(int fps) {
+ void setRefreshRateInHighZone(Integer fps) {
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE,
String.valueOf(fps));
}
- void setDefaultPeakRefreshRate(int fps) {
+ void setDefaultPeakRefreshRate(Integer fps) {
putPropertyAndNotify(
DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
String.valueOf(fps));
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
index 6f89ff0..2a9c18c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
@@ -157,6 +157,20 @@
checkAllColumns_latest();
}
+ @Test
+ public void onUpgradeToV7_ignoresDuplicateColumnError() throws Exception {
+ mDatabaseHelper.onCreate(mDatabase);
+ mDatabaseHelper.onUpgrade(mDatabase, 6, 7);
+ checkAllColumns_latest();
+ }
+
+ @Test
+ public void onUpgradeToV7_recreatesDatabaseAfterFailure() throws Exception {
+ mDatabaseHelper.onCreate(mDatabase);
+ mDatabaseHelper.onUpgrade(mDatabase, 1, 7);
+ checkAllColumns_latest();
+ }
+
private boolean isRootOfTrustTableAvailable() {
ContentValues values = new ContentValues();
values.put(RootOfTrustEntry.COLUMN_NAME_USER_ID, TEST_USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 86878c53..8487903 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -232,7 +232,6 @@
long mElapsedRealtime;
boolean mIsAppIdleEnabled = true;
boolean mIsCharging;
- boolean mIsRestrictedBucketEnabled = true;
List<String> mNonIdleWhitelistApps = new ArrayList<>();
boolean mDisplayOn;
DisplayManager.DisplayListener mDisplayListener;
@@ -316,11 +315,6 @@
}
@Override
- boolean isRestrictedBucketEnabled() {
- return mIsRestrictedBucketEnabled;
- }
-
- @Override
File getDataSystemDirectory() {
return new File(getContext().getFilesDir(), Long.toString(sRandom.nextLong()));
}
@@ -1355,50 +1349,6 @@
@Test
@FlakyTest(bugId = 185169504)
- public void testRestrictedBucketDisabled() throws Exception {
- mInjector.mIsRestrictedBucketEnabled = false;
- // Get the controller to read the new value. Capturing the ContentObserver isn't possible
- // at the moment.
- mController.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
-
- reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
-
- // Nothing should be able to put it into the RESTRICTED bucket.
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_TIMEOUT);
- assertNotBucket(STANDBY_BUCKET_RESTRICTED);
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_PREDICTED);
- assertNotBucket(STANDBY_BUCKET_RESTRICTED);
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_SYSTEM);
- assertNotBucket(STANDBY_BUCKET_RESTRICTED);
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_USER);
- assertNotBucket(STANDBY_BUCKET_RESTRICTED);
- }
-
- @Test
- @FlakyTest(bugId = 185169504)
- public void testRestrictedBucket_EnabledToDisabled() throws Exception {
- reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
- mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
- REASON_MAIN_FORCED_BY_SYSTEM);
- assertBucket(STANDBY_BUCKET_RESTRICTED);
-
- mInjector.mIsRestrictedBucketEnabled = false;
- // Get the controller to read the new value. Capturing the ContentObserver isn't possible
- // at the moment.
- mController.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
-
- mController.checkIdleStates(USER_ID);
- assertNotBucket(STANDBY_BUCKET_RESTRICTED);
- }
-
- @Test
- @FlakyTest(bugId = 185169504)
public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception {
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 91d4f8f..d73a3b8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -15,9 +15,11 @@
*/
package com.android.server.notification;
-import static org.hamcrest.Matchers.contains;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
@@ -43,13 +45,14 @@
import android.telecom.TelecomManager;
import android.test.suitebuilder.annotation.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.server.UiServiceTestCase;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -58,7 +61,7 @@
import java.util.List;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class NotificationComparatorTest extends UiServiceTestCase {
@Mock Context mContext;
@Mock TelecomManager mTm;
@@ -92,9 +95,24 @@
private NotificationRecord mRecordColorized;
private NotificationRecord mRecordColorizedCall;
+ @Parameterized.Parameters(name = "sortByInterruptiveness={0}")
+ public static Boolean[] getSortByInterruptiveness() {
+ return new Boolean[] { true, false };
+ }
+
+ @Parameterized.Parameter
+ public boolean mSortByInterruptiveness;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
+ if (flag.mSysPropKey.equals(NO_SORT_BY_INTERRUPTIVENESS.mSysPropKey)) {
+ return !mSortByInterruptiveness;
+ }
+ return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
+ };
+
int userId = UserHandle.myUserId();
when(mContext.getResources()).thenReturn(getContext().getResources());
@@ -126,7 +144,7 @@
new StatusBarNotification(callPkg,
callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid,
nonInterruptiveNotif,
- new UserHandle(userId), "", 2000), getDefaultChannel());
+ new UserHandle(userId), "", 2001), getDefaultChannel());
mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
mRecordMinCallNonInterruptive.setInterruptive(false);
@@ -228,7 +246,7 @@
.setColorized(true).setColor(Color.WHITE)
.build();
mRecordCheaterColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
- pkg2, 1, "cheater", uid2, uid2, n11, new UserHandle(userId),
+ pkg2, 1, "cheaterColorized", uid2, uid2, n11, new UserHandle(userId),
"", 9258), getDefaultChannel());
mRecordCheaterColorized.setSystemImportance(NotificationManager.IMPORTANCE_LOW);
@@ -262,6 +280,11 @@
mRecordColorizedCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
}
+ @After
+ public void tearDown() {
+ SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+ }
+
@Test
public void testOrdering() {
final List<NotificationRecord> expected = new ArrayList<>();
@@ -281,8 +304,13 @@
expected.add(mNoMediaSessionMedia);
expected.add(mRecordCheater);
expected.add(mRecordCheaterColorized);
- expected.add(mRecordMinCall);
- expected.add(mRecordMinCallNonInterruptive);
+ if (mSortByInterruptiveness) {
+ expected.add(mRecordMinCall);
+ expected.add(mRecordMinCallNonInterruptive);
+ } else {
+ expected.add(mRecordMinCallNonInterruptive);
+ expected.add(mRecordMinCall);
+ }
List<NotificationRecord> actual = new ArrayList<>();
actual.addAll(expected);
@@ -290,14 +318,18 @@
Collections.sort(actual, new NotificationComparator(mContext));
- assertThat(actual, contains(expected.toArray()));
+ assertThat(actual).containsExactlyElementsIn(expected).inOrder();
}
@Test
public void testRankingScoreOverrides() {
NotificationComparator comp = new NotificationComparator(mContext);
NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
- assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
+ if (mSortByInterruptiveness) {
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
+ } else {
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
+ }
when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 39060cb..02c030d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5841,6 +5841,57 @@
}
@Test
+ public void testVisualDifference_sameImages() {
+ Icon large = Icon.createWithResource(mContext, 1);
+ Notification n1 = new Notification.Builder(mContext, "channel")
+ .setSmallIcon(1).setLargeIcon(large).build();
+ Notification n2 = new Notification.Builder(mContext, "channel")
+ .setSmallIcon(1).setLargeIcon(large).build();
+
+ NotificationRecord r1 = notificationToRecord(n1);
+ NotificationRecord r2 = notificationToRecord(n2);
+
+ assertThat(mService.isVisuallyInterruptive(r1, r2)).isFalse();
+ }
+
+ @Test
+ public void testVisualDifference_differentSmallImage() {
+ Icon large = Icon.createWithResource(mContext, 1);
+ Notification n1 = new Notification.Builder(mContext, "channel")
+ .setSmallIcon(1).setLargeIcon(large).build();
+ Notification n2 = new Notification.Builder(mContext, "channel")
+ .setSmallIcon(2).setLargeIcon(large).build();
+
+ NotificationRecord r1 = notificationToRecord(n1);
+ NotificationRecord r2 = notificationToRecord(n2);
+
+ assertThat(mService.isVisuallyInterruptive(r1, r2)).isTrue();
+ }
+
+ @Test
+ public void testVisualDifference_differentLargeImage() {
+ Icon large1 = Icon.createWithResource(mContext, 1);
+ Icon large2 = Icon.createWithResource(mContext, 2);
+ Notification n1 = new Notification.Builder(mContext, "channel")
+ .setSmallIcon(1).setLargeIcon(large1).build();
+ Notification n2 = new Notification.Builder(mContext, "channel")
+ .setSmallIcon(1).setLargeIcon(large2).build();
+
+ NotificationRecord r1 = notificationToRecord(n1);
+ NotificationRecord r2 = notificationToRecord(n2);
+
+ assertThat(mService.isVisuallyInterruptive(r1, r2)).isTrue();
+ }
+
+ private NotificationRecord notificationToRecord(Notification n) {
+ return new NotificationRecord(
+ mContext,
+ new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, n,
+ UserHandle.getUserHandleForUid(mUid), null, 0),
+ mock(NotificationChannel.class));
+ }
+
+ @Test
public void testHideAndUnhideNotificationsOnSuspendedPackageBroadcast() {
// post 2 notification from this package
final NotificationRecord notif1 = generateNotificationRecord(
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 9ca2876..986fb71 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -29,6 +29,7 @@
srcs: [
"src/**/*.java",
+ ":FrameworksCoreTestDoubles-sources",
],
static_libs: [
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
new file mode 100644
index 0000000..8694094
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 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.server.soundtrigger_middleware;
+
+import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.ActivityThread;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
+import android.media.soundtrigger.PhraseRecognitionEvent;
+import android.media.soundtrigger.PhraseRecognitionExtra;
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.os.BatteryStatsInternal;
+import android.os.Process;
+import android.os.RemoteException;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.FakeLatencyTracker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(JUnit4.class)
+public class SoundTriggerMiddlewareLoggingTest {
+ private FakeLatencyTracker mLatencyTracker;
+ @Mock
+ private BatteryStatsInternal mBatteryStatsInternal;
+ @Mock
+ private ISoundTriggerMiddlewareInternal mDelegateMiddleware;
+ @Mock
+ private ISoundTriggerCallback mISoundTriggerCallback;
+ @Mock
+ private ISoundTriggerModule mSoundTriggerModule;
+ private SoundTriggerMiddlewareLogging mSoundTriggerMiddlewareLogging;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG);
+
+ Identity identity = new Identity();
+ identity.uid = Process.myUid();
+ identity.pid = Process.myPid();
+ identity.packageName = ActivityThread.currentOpPackageName();
+ IdentityContext.create(identity);
+
+ mLatencyTracker = FakeLatencyTracker.create();
+ mLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, -1);
+ mSoundTriggerMiddlewareLogging = new SoundTriggerMiddlewareLogging(mLatencyTracker,
+ () -> mBatteryStatsInternal,
+ mDelegateMiddleware);
+ }
+
+ @After
+ public void tearDown() {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testSetUpAndTearDown() {
+ }
+
+ @Test
+ public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
+ throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
+
+ assertThat(mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
+ }
+
+ @Test
+ public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
+ long firstTriggerSessionStartTime = mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION);
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS, Optional.of(100) /* keyphraseId */);
+ assertThat(mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION)).isGreaterThan(-1);
+ assertThat(mLatencyTracker.getActiveActionStartTime(
+ ACTION_SHOW_VOICE_INTERACTION)).isNotEqualTo(firstTriggerSessionStartTime);
+ }
+
+ @Test
+ public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
+ throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.ABORTED, Optional.of(100) /* keyphraseId */);
+
+ assertThat(
+ mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
+ -1);
+ }
+
+ @Test
+ public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
+ throws RemoteException {
+ ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
+ ISoundTriggerCallback.class);
+ mSoundTriggerMiddlewareLogging.attach(0, mISoundTriggerCallback);
+ verify(mDelegateMiddleware).attach(anyInt(), soundTriggerCallbackCaptor.capture());
+
+ triggerPhraseRecognitionEvent(soundTriggerCallbackCaptor.getValue(),
+ RecognitionStatus.SUCCESS, Optional.empty() /* keyphraseId */);
+
+ assertThat(
+ mLatencyTracker.getActiveActionStartTime(ACTION_SHOW_VOICE_INTERACTION)).isEqualTo(
+ -1);
+ }
+
+ private void triggerPhraseRecognitionEvent(ISoundTriggerCallback callback,
+ @RecognitionStatus int triggerEventStatus, Optional<Integer> optionalKeyphraseId)
+ throws RemoteException {
+ // trigger a phrase recognition to start a latency tracker session
+ PhraseRecognitionEvent successEventWithKeyphraseId = new PhraseRecognitionEvent();
+ successEventWithKeyphraseId.common = new RecognitionEvent();
+ successEventWithKeyphraseId.common.status = triggerEventStatus;
+ if (optionalKeyphraseId.isPresent()) {
+ PhraseRecognitionExtra recognitionExtra = new PhraseRecognitionExtra();
+ recognitionExtra.id = optionalKeyphraseId.get();
+ successEventWithKeyphraseId.phraseExtras =
+ new PhraseRecognitionExtra[]{recognitionExtra};
+ }
+ callback.onPhraseRecognition(0 /* modelHandle */, successEventWithKeyphraseId,
+ 0 /* captureSession */);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index a17e124..dfc453f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -775,7 +775,7 @@
// Assume the task is at the topmost position
assertFalse(rootTask.isTopRootTaskInDisplayArea());
- doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
+ doReturn(taskDisplayArea.getHomeActivity()).when(taskDisplayArea).topRunningActivity();
// Use the task as target to resume.
mRootWindowContainer.resumeFocusedTasksTopActivities();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2cc46a9..e4a591e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -108,7 +109,6 @@
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -353,6 +353,33 @@
}
@Test
+ public void testApplyStrategyToTranslucentActivitiesRetainsWindowConfigurationProperties() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ WindowConfiguration translucentWinConf = translucentActivity.getWindowConfiguration();
+ translucentActivity.setActivityType(ACTIVITY_TYPE_STANDARD);
+ translucentActivity.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ translucentActivity.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ translucentActivity.setAlwaysOnTop(true);
+
+ mTask.addChild(translucentActivity);
+
+ // We check the WIndowConfiguration properties
+ translucentWinConf = translucentActivity.getWindowConfiguration();
+ assertEquals(ACTIVITY_TYPE_STANDARD, translucentActivity.getActivityType());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getDisplayWindowingMode());
+ assertTrue(translucentWinConf.isAlwaysOnTop());
+ }
+
+ @Test
public void testApplyStrategyToMultipleTranslucentActivities() {
mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
setUpDisplaySizeWithApp(2000, 1000);
@@ -419,7 +446,7 @@
.setLaunchedFromUid(mActivity.getUid())
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
- doReturn(true).when(translucentActivity).fillsParent();
+ doReturn(false).when(translucentActivity).fillsParent();
mTask.addChild(translucentActivity);
// It should not be in SCM
assertFalse(translucentActivity.inSizeCompatMode());
@@ -604,11 +631,11 @@
// The scale is 2000/2500=0.8. The horizontal centered offset is (1000-(1000*0.8))/2=100.
final float scale = (float) display.mBaseDisplayHeight / currentBounds.height();
final int offsetX = (int) (display.mBaseDisplayWidth - (origBounds.width() * scale)) / 2;
- assertEquals(offsetX, currentBounds.left);
+ final int screenX = mActivity.getBounds().left;
+ assertEquals(offsetX, screenX);
- // The position of configuration bounds should be the same as compat bounds.
- assertEquals(mActivity.getBounds().left, currentBounds.left);
- assertEquals(mActivity.getBounds().top, currentBounds.top);
+ // The position of configuration bounds should be in app space.
+ assertEquals(screenX, (int) (currentBounds.left * scale + 0.5f));
// Activity is sandboxed to the offset size compat bounds.
assertActivityMaxBoundsSandboxed();
@@ -638,7 +665,7 @@
// The size should still be in portrait [100, 0 - 1100, 2500] = 1000x2500.
assertEquals(origBounds.width(), currentBounds.width());
assertEquals(origBounds.height(), currentBounds.height());
- assertEquals(offsetX, currentBounds.left);
+ assertEquals(offsetX, mActivity.getBounds().left);
assertScaled();
// Activity is sandboxed due to size compat mode.
assertActivityMaxBoundsSandboxed();
@@ -801,9 +828,11 @@
assertEquals(origAppBounds.height(), appBounds.height());
// The activity is 1000x1400 and the display is 2500x1000.
assertScaled();
- // The position in configuration should be global coordinates.
- assertEquals(mActivity.getBounds().left, currentBounds.left);
- assertEquals(mActivity.getBounds().top, currentBounds.top);
+ final float scale = mActivity.getCompatScale();
+ // The position in configuration should be in app coordinates.
+ final Rect screenBounds = mActivity.getBounds();
+ assertEquals(screenBounds.left, (int) (currentBounds.left * scale + 0.5f));
+ assertEquals(screenBounds.top, (int) (currentBounds.top * scale + 0.5f));
// Activity max bounds are sandboxed due to size compat mode.
assertActivityMaxBoundsSandboxed();
@@ -2025,7 +2054,7 @@
float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
}
@Test
@@ -2050,7 +2079,7 @@
float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
}
@Test
@@ -2076,7 +2105,7 @@
float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
}
@Test
@@ -2102,7 +2131,89 @@
float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+ public void testOverrideSplitScreenAspectRatio_splitScreenActivityInPortrait_notLetterboxed() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ final int screenWidth = 1800;
+ final int screenHeight = 1000;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Simulate real display with top insets.
+ final int topInset = 30;
+ activity.mDisplayContent.getWindowConfiguration()
+ .setAppBounds(0, topInset, screenWidth, screenHeight);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, activity.getDisplayContent());
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight);
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
+
+ // Unresizable portrait-only activity.
+ prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity should have the aspect ratio of a split screen activity and occupy exactly one
+ // half of the screen, so there is no letterbox
+ float expectedAspectRatio = 1f * screenHeight / getExpectedSplitSize(screenWidth);
+ final Rect afterBounds = activity.getBounds();
+ final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
+ assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertFalse(activity.areBoundsLetterboxed());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+ public void testOverrideSplitScreenAspectRatio_splitScreenActivityInLandscape_notLetterboxed() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ final int screenWidth = 1000;
+ final int screenHeight = 1800;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Simulate real display with top insets.
+ final int leftInset = 30;
+ activity.mDisplayContent.getWindowConfiguration()
+ .setAppBounds(leftInset, 0, screenWidth, screenHeight);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, activity.getDisplayContent());
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
+
+ // Unresizable landscape-only activity.
+ prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Activity should have the aspect ratio of a split screen activity and occupy exactly one
+ // half of the screen, so there is no letterbox
+ float expectedAspectRatio = 1f * screenWidth / getExpectedSplitSize(screenHeight);
+ final Rect afterBounds = activity.getBounds();
+ final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
+ assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertFalse(activity.areBoundsLetterboxed());
}
@Test
@@ -3002,12 +3113,44 @@
assertTrue(mActivity.inSizeCompatMode());
// Vertical reachability is disabled because the app does not match parent width
- assertNotEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds()
- .width());
+ assertNotEquals(mActivity.getScreenResolvedBounds().width(),
+ mActivity.mDisplayContent.getBounds().width());
assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
}
@Test
+ public void testIsVerticalReachabilityEnabled_emptyBounds_true() {
+ setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Set up activity with empty bounds to mock loading of app
+ mActivity.getWindowConfiguration().setBounds(null);
+ assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
+
+ // Vertical reachability is still enabled as resolved bounds is not empty
+ assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsHorizontalReachabilityEnabled_emptyBounds_true() {
+ setUpDisplaySizeWithApp(/* dw */ 2800, /* dh */ 1000);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Set up activity with empty bounds to mock loading of app
+ mActivity.getWindowConfiguration().setBounds(null);
+ assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
+
+ // Horizontal reachability is still enabled as resolved bounds is not empty
+ assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ }
+
+ @Test
public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() {
setUpDisplaySizeWithApp(2800, 1000);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -3023,8 +3166,8 @@
assertTrue(mActivity.inSizeCompatMode());
// Horizontal reachability is disabled because the app does not match parent height
- assertNotEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds()
- .height());
+ assertNotEquals(mActivity.getScreenResolvedBounds().height(),
+ mActivity.mDisplayContent.getBounds().height());
assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
}
@@ -3044,8 +3187,8 @@
assertTrue(mActivity.inSizeCompatMode());
// Horizontal reachability is enabled because the app matches parent height
- assertEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds()
- .height());
+ assertEquals(mActivity.getScreenResolvedBounds().height(),
+ mActivity.mDisplayContent.getBounds().height());
assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
}
@@ -3065,7 +3208,8 @@
assertTrue(mActivity.inSizeCompatMode());
// Vertical reachability is enabled because the app matches parent width
- assertEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds().width());
+ assertEquals(mActivity.getScreenResolvedBounds().width(),
+ mActivity.mDisplayContent.getBounds().width());
assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
}
@@ -3587,7 +3731,6 @@
@Test
public void testUpdateResolvedBoundsVerticalPosition_tabletop() {
-
// Set up a display in portrait with a fixed-orientation LANDSCAPE app
setUpDisplaySizeWithApp(1400, 2800);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -3609,16 +3752,15 @@
setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
assertEquals(letterboxNoFold, mActivity.getBounds());
-
}
@Test
- public void testUpdateResolvedBoundsHorizontalPosition_book() {
-
+ public void testUpdateResolvedBoundsHorizontalPosition_bookModeEnabled() {
// Set up a display in landscape with a fixed-orientation PORTRAIT app
setUpDisplaySizeWithApp(2800, 1400);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+ mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true);
+ mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
1.0f /*letterboxVerticalPositionMultiplier*/);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
@@ -3636,7 +3778,28 @@
setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
assertEquals(letterboxNoFold, mActivity.getBounds());
+ }
+ @Test
+ public void testUpdateResolvedBoundsHorizontalPosition_bookModeDisabled_centered() {
+ // Set up a display in landscape with a fixed-orientation PORTRAIT app
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
+ prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT);
+
+ Rect letterboxNoFold = new Rect(1000, 0, 1800, 1400);
+ assertEquals(letterboxNoFold, mActivity.getBounds());
+
+ // Make the activity full-screen
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ // Stay centered and bounds don't change
+ setFoldablePosture(true /* isHalfFolded */, false /* isTabletop */);
+ assertEquals(letterboxNoFold, mActivity.getBounds());
+
+ setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
+ assertEquals(letterboxNoFold, mActivity.getBounds());
}
private void setFoldablePosture(ActivityRecord activity, boolean isHalfFolded,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 378e8be..99ab715 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -398,6 +398,8 @@
public void testOnActivityReparentedToTask_activityNotInOrganizerProcess_useTemporaryToken() {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
+ final WindowProcessController organizerProc = mSystemServicesTestRule.addProcess(
+ "pkg.organizer", DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME, pid, uid);
mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
@@ -436,6 +438,15 @@
// The temporary token can only be used once.
assertNull(mController.getReparentActivityFromTemporaryToken(mIOrganizer,
change.getActivityToken()));
+
+ // The organizer process should also have visible state by the visible activity in a
+ // different process.
+ activity.setVisibleRequested(true);
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ assertTrue(organizerProc.hasVisibleActivities());
+ activity.setVisibleRequested(false);
+ activity.setState(ActivityRecord.State.STOPPED, "test");
+ assertFalse(organizerProc.hasVisibleActivities());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 1d0715a..2a2641e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -314,7 +314,7 @@
}
@Override
- public int applyKeyguardOcclusionChange(boolean notify) {
+ public int applyKeyguardOcclusionChange() {
return 0;
}
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 1a1af3b..2975e1e 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -1362,12 +1362,10 @@
}
}
- // Need to create new version to prevent double counting existing stats due
- // to new broadcast
private void logToStatsdComplianceWarnings(PortInfo portInfo) {
- if (portInfo.mUsbPortStatus == null) {
- FrameworkStatsLog.write(FrameworkStatsLog.USB_COMPLIANCE_WARNINGS_REPORTED,
- portInfo.mUsbPort.getId(), new int[0]);
+ // Don't report if there isn't anything to report
+ if (portInfo.mUsbPortStatus == null
+ || portInfo.mUsbPortStatus.getComplianceWarnings().length == 0) {
return;
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 70af337..04c1c04 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -44,6 +44,7 @@
import android.content.pm.ResolveInfo;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.ModelParams;
+import android.hardware.soundtrigger.ConversionUtil;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -63,6 +64,7 @@
import android.media.soundtrigger.ISoundTriggerDetectionService;
import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -72,6 +74,8 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -88,11 +92,13 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
+import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;
/**
@@ -215,14 +221,29 @@
}
}
+ // Must be called with cleared binder context.
+ private static List<ModuleProperties> listUnderlyingModuleProperties(
+ Identity originatorIdentity) {
+ Identity middlemanIdentity = new Identity();
+ middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
+ var service = ISoundTriggerMiddlewareService.Stub.asInterface(
+ ServiceManager.waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE));
+ try {
+ return Arrays.stream(service.listModulesAsMiddleman(middlemanIdentity,
+ originatorIdentity))
+ .map(desc -> ConversionUtil.aidl2apiModuleDescriptor(desc))
+ .collect(Collectors.toList());
+ } catch (RemoteException e) {
+ throw new ServiceSpecificException(SoundTrigger.STATUS_DEAD_OBJECT);
+ }
+ }
+
private SoundTriggerHelper newSoundTriggerHelper(ModuleProperties moduleProperties) {
Identity middlemanIdentity = new Identity();
middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
Identity originatorIdentity = IdentityContext.getNonNull();
- ArrayList<ModuleProperties> moduleList = new ArrayList<>();
- SoundTrigger.listModulesAsMiddleman(moduleList, middlemanIdentity,
- originatorIdentity);
+ List<ModuleProperties> moduleList = listUnderlyingModuleProperties(originatorIdentity);
// Don't fail existing CTS tests which run without a ST module
final int moduleId = (moduleProperties != null) ?
@@ -241,12 +262,8 @@
moduleId, statusListener, null /* handler */,
middlemanIdentity, originatorIdentity),
moduleId,
- () -> {
- ArrayList<ModuleProperties> modulePropList = new ArrayList<>();
- SoundTrigger.listModulesAsMiddleman(modulePropList, middlemanIdentity,
- originatorIdentity);
- return modulePropList;
- });
+ () -> listUnderlyingModuleProperties(originatorIdentity)
+ );
}
class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
@@ -276,12 +293,7 @@
public List<ModuleProperties> listModuleProperties(@NonNull Identity originatorIdentity) {
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
- Identity middlemanIdentity = new Identity();
- middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
- ArrayList<ModuleProperties> moduleList = new ArrayList<>();
- SoundTrigger.listModulesAsMiddleman(moduleList, middlemanIdentity,
- originatorIdentity);
- return moduleList;
+ return listUnderlyingModuleProperties(originatorIdentity);
}
}
}
@@ -1647,17 +1659,10 @@
@Override
public List<ModuleProperties> listModuleProperties(Identity originatorIdentity) {
- ArrayList<ModuleProperties> moduleList = new ArrayList<>();
try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
originatorIdentity)) {
- Identity middlemanIdentity = new Identity();
- middlemanIdentity.uid = Binder.getCallingUid();
- middlemanIdentity.pid = Binder.getCallingPid();
- middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
- SoundTrigger.listModulesAsMiddleman(moduleList, middlemanIdentity,
- originatorIdentity);
+ return listUnderlyingModuleProperties(originatorIdentity);
}
- return moduleList;
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 7d5750e..2f8d17d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -26,6 +26,7 @@
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.RecognitionConfig;
import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -36,6 +37,8 @@
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.LatencyTracker;
import com.android.server.LocalServices;
@@ -44,6 +47,7 @@
import java.util.Date;
import java.util.LinkedList;
import java.util.Objects;
+import java.util.function.Supplier;
/**
* An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and
@@ -71,12 +75,23 @@
public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
private static final String TAG = "SoundTriggerMiddlewareLogging";
private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
- private final @NonNull Context mContext;
+ private final @NonNull LatencyTracker mLatencyTracker;
+ private final @NonNull Supplier<BatteryStatsInternal> mBatteryStatsInternalSupplier;
public SoundTriggerMiddlewareLogging(@NonNull Context context,
@NonNull ISoundTriggerMiddlewareInternal delegate) {
+ this(LatencyTracker.getInstance(context),
+ () -> BatteryStatsHolder.INSTANCE,
+ delegate);
+ }
+
+ @VisibleForTesting
+ public SoundTriggerMiddlewareLogging(@NonNull LatencyTracker latencyTracker,
+ @NonNull Supplier<BatteryStatsInternal> batteryStatsInternalSupplier,
+ @NonNull ISoundTriggerMiddlewareInternal delegate) {
mDelegate = delegate;
- mContext = context;
+ mLatencyTracker = latencyTracker;
+ mBatteryStatsInternalSupplier = batteryStatsInternalSupplier;
}
@Override
@@ -294,7 +309,7 @@
public void onRecognition(int modelHandle, RecognitionEvent event, int captureSession)
throws RemoteException {
try {
- BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger(
+ mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
mCallbackDelegate.onRecognition(modelHandle, event, captureSession);
logVoidReturn("onRecognition", modelHandle, event);
@@ -309,7 +324,7 @@
int captureSession)
throws RemoteException {
try {
- BatteryStatsHolder.INSTANCE.noteWakingSoundTrigger(
+ mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
startKeyphraseEventLatencyTracking(event);
mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
@@ -361,26 +376,6 @@
logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args);
}
- /**
- * Starts the latency tracking log for keyphrase hotword invocation.
- * The measurement covers from when the SoundTrigger HAL emits an event to when the
- * {@link android.service.voice.VoiceInteractionSession} system UI view is shown.
- */
- private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) {
- String latencyTrackerTag = null;
- if (event.phraseExtras.length > 0) {
- latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id;
- }
- LatencyTracker latencyTracker = LatencyTracker.getInstance(mContext);
- // To avoid adding cancel to all of the different failure modes between here and
- // showing the system UI, we defensively cancel once.
- // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel
- // here if any error occurs.
- latencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
- latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION,
- latencyTrackerTag);
- }
-
@Override
public IBinder asBinder() {
return mCallbackDelegate.asBinder();
@@ -399,6 +394,29 @@
LocalServices.getService(BatteryStatsInternal.class);
}
+ /**
+ * Starts the latency tracking log for keyphrase hotword invocation.
+ * The measurement covers from when the SoundTrigger HAL emits an event to when the
+ * {@link android.service.voice.VoiceInteractionSession} system UI view is shown.
+ *
+ * <p>The session is only started if the {@link PhraseRecognitionEvent} has a status of
+ * {@link RecognitionStatus#SUCCESS}
+ */
+ private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) {
+ if (event.common.status != RecognitionStatus.SUCCESS
+ || ArrayUtils.isEmpty(event.phraseExtras)) {
+ return;
+ }
+
+ String latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id;
+ // To avoid adding cancel to all the different failure modes between here and
+ // showing the system UI, we defensively cancel once.
+ // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel
+ // here if any error occurs.
+ mLatencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION,
+ latencyTrackerTag);
+ }
////////////////////////////////////////////////////////////////////////////////////////////////
// Actual logging logic below.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
index c35d90f..f7b66a2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -34,10 +34,13 @@
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE;
+import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
+import android.content.Context;
import android.service.voice.HotwordDetector;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.LatencyTracker;
/**
* A utility class for logging hotword statistics event.
@@ -116,6 +119,46 @@
metricsDetectorType, event, uid, streamSizeBytes, bundleSizeBytes, streamCount);
}
+ /**
+ * Starts a {@link LatencyTracker} log for the time it takes to show the
+ * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger.
+ *
+ * @see LatencyTracker
+ *
+ * @param tag Extra tag to separate different sessions from each other.
+ */
+ public static void startHotwordTriggerToUiLatencySession(Context context, String tag) {
+ LatencyTracker.getInstance(context).onActionStart(ACTION_SHOW_VOICE_INTERACTION, tag);
+ }
+
+ /**
+ * Completes a {@link LatencyTracker} log for the time it takes to show the
+ * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger.
+ *
+ * <p>Completing this session will result in logging metric data.</p>
+ *
+ * @see LatencyTracker
+ */
+ public static void stopHotwordTriggerToUiLatencySession(Context context) {
+ LatencyTracker.getInstance(context).onActionEnd(ACTION_SHOW_VOICE_INTERACTION);
+ }
+
+ /**
+ * Cancels a {@link LatencyTracker} log for the time it takes to show the
+ * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger.
+ *
+ * <p>Cancels typically occur when the VoiceInteraction session UI is shown for reasons outside
+ * of a {@link android.hardware.soundtrigger.SoundTrigger.RecognitionEvent} such as an
+ * invocation from an external source or service.</p>
+ *
+ * <p>Canceling this session will not result in logging metric data.
+ *
+ * @see LatencyTracker
+ */
+ public static void cancelHotwordTriggerToUiLatencySession(Context context) {
+ LatencyTracker.getInstance(context).onActionCancel(ACTION_SHOW_VOICE_INTERACTION);
+ }
+
private static int getCreateMetricsDetectorType(int detectorType) {
switch (detectorType) {
case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE:
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1a76295..e1da2ca 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -97,7 +97,6 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.LatencyTracker;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -443,6 +442,10 @@
final int callingUid = Binder.getCallingUid();
final long caller = Binder.clearCallingIdentity();
try {
+ // HotwordDetector trigger uses VoiceInteractionService#showSession
+ // We need to cancel here because UI is not being shown due to a SoundTrigger
+ // HAL event.
+ HotwordMetricsLogger.cancelHotwordTriggerToUiLatencySession(mContext);
mImpl.showSessionLocked(options,
VoiceInteractionSession.SHOW_SOURCE_ACTIVITY, attributionTag,
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -994,6 +997,13 @@
Slog.w(TAG, "showSessionFromSession without running voice interaction service");
return false;
}
+ // If the token is null, then the request to show the session is not coming from
+ // the active VoiceInteractionService session.
+ // We need to cancel here because UI is not being shown due to a SoundTrigger
+ // HAL event.
+ if (token == null) {
+ HotwordMetricsLogger.cancelHotwordTriggerToUiLatencySession(mContext);
+ }
final long caller = Binder.clearCallingIdentity();
try {
return mImpl.showSessionLocked(sessionArgs, flags, attributionTag, null, null);
@@ -1862,6 +1872,11 @@
final long caller = Binder.clearCallingIdentity();
try {
+ // HotwordDetector trigger uses VoiceInteractionService#showSession
+ // We need to cancel here because UI is not being shown due to a SoundTrigger
+ // HAL event.
+ HotwordMetricsLogger.cancelHotwordTriggerToUiLatencySession(mContext);
+
return mImpl.showSessionLocked(args,
sourceFlags
| VoiceInteractionSession.SHOW_WITH_ASSIST
@@ -2521,8 +2536,11 @@
public void onVoiceSessionWindowVisibilityChanged(boolean visible)
throws RemoteException {
if (visible) {
- LatencyTracker.getInstance(mContext)
- .onActionEnd(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
+ // The AlwaysOnHotwordDetector trigger latency is always completed here even
+ // if the reason the window was shown was not due to a SoundTrigger HAL
+ // event. It is expected that the latency will be canceled if shown for
+ // other invocation reasons, and this call becomes a noop.
+ HotwordMetricsLogger.stopHotwordTriggerToUiLatencySession(mContext);
}
}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 0325ba6..f9b76f4 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
@@ -255,15 +256,26 @@
* Show switch to managed profile dialog if subscription is associated with managed profile.
*
* @param context Context object
- * @param subId subscription id
+ * @param subId subscription id
+ * @param callingUid uid for the calling app
+ * @param callingPackage package name of the calling app
*/
- public static void showErrorIfSubscriptionAssociatedWithManagedProfile(Context context,
- int subId) {
+ public static void showSwitchToManagedProfileDialogIfAppropriate(Context context,
+ int subId, int callingUid, String callingPackage) {
if (!isSwitchToManagedProfileDialogFlagEnabled()) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
+ // We only want to show this dialog, while user actually trying to send the message from
+ // a messaging app, in other cases this dialog don't make sense.
+ if (!TelephonyUtils.isUidForeground(context, callingUid)
+ || !TelephonyUtils.isPackageSMSRoleHolderForUser(context, callingPackage,
+ callingUserHandle)) {
+ return;
+ }
+
SubscriptionManager subscriptionManager = context.getSystemService(
SubscriptionManager.class);
UserHandle associatedUserHandle = subscriptionManager.getSubscriptionUserHandle(subId);
@@ -295,22 +307,25 @@
"enable_switch_to_managed_profile_dialog", false);
}
- /**
- * Check if the process with given uid is foreground.
- *
- * @param context context
- * @param uid the caller uid
- * @return true if the process with uid is foreground, false otherwise.
- */
- public static boolean isUidForeground(Context context, int uid) {
- final long token = Binder.clearCallingIdentity();
- try {
- ActivityManager am = context.getSystemService(ActivityManager.class);
- boolean result = am != null && am.getUidImportance(uid)
- == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
- return result;
- } finally {
- Binder.restoreCallingIdentity(token);
+ private static boolean isUidForeground(Context context, int uid) {
+ ActivityManager am = context.getSystemService(ActivityManager.class);
+ boolean result = am != null && am.getUidImportance(uid)
+ == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+ return result;
+ }
+
+ private static boolean isPackageSMSRoleHolderForUser(Context context, String callingPackage,
+ UserHandle user) {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ final List<String> smsRoleHolder = roleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_SMS, user);
+
+ // ROLE_SMS is an exclusive role per user, so there would just be one entry in the
+ // retuned list if not empty
+ if (!smsRoleHolder.isEmpty() && callingPackage.equals(smsRoleHolder.get(0))) {
+ return true;
}
+ return false;
+
}
}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7aa1334..ba4a54e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4444,6 +4444,22 @@
"min_udp_port_4500_nat_timeout_sec_int";
/**
+ * The preferred IKE protocol for ESP packets.
+ *
+ * This will be used by Android platform VPNs to select preferred encapsulation type and IP
+ * protocol type. The possible customization values are:
+ *
+ * AUTO IP VERSION and ENCAPSULATION TYPE SELECTION : "0"
+ * IPv4 UDP : "40"
+ * IPv6 ESP : "61"
+ *
+ * See the {@code PREFERRED_IKE_PROTOCOL_} constants in
+ * {@link com.android.server.connectivity.Vpn}.
+ * @hide
+ */
+ public static final String KEY_PREFERRED_IKE_PROTOCOL_INT = "preferred_ike_protocol_int";
+
+ /**
* Specifies whether the system should prefix the EAP method to the anonymous identity.
* The following prefix will be added if this key is set to TRUE:
* EAP-AKA: "0"
@@ -10176,6 +10192,7 @@
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
sDefaults.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, 300);
+ sDefaults.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, 0);
// Default wifi configurations.
sDefaults.putAll(Wifi.getDefaults());
sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index 7c794473..e8a8576 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,6 +34,7 @@
/**
* @hide
*/
+ @UnsupportedAppUsage
public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) {
mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
mSatelliteElevationDegrees = satelliteElevationDegrees;
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index df80159..87c8db3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -45,6 +46,7 @@
/**
* @hide
*/
+ @UnsupportedAppUsage
public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
mSupportedRadioTechnologies = supportedRadioTechnologies == null
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index d3cb8a0..44f56f3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +33,7 @@
/**
* @hide
*/
+ @UnsupportedAppUsage
public SatelliteDatagram(@NonNull byte[] data) {
mData = data;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index f237ada..d8a6faf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.telephony.ILongConsumer;
@@ -36,6 +37,7 @@
* datagramId to Telephony. If the callback is not received within five minutes,
* Telephony will resend the datagram.
*/
+ @UnsupportedAppUsage
void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
int pendingCount, @NonNull ILongConsumer callback);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e32566d..7d82fd8 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -82,6 +83,7 @@
* @param context The context the SatelliteManager belongs to.
* @hide
*/
+ @UnsupportedAppUsage
public SatelliteManager(@Nullable Context context) {
this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
@@ -127,6 +129,7 @@
* {@link #requestIsSatelliteEnabled(Executor, OutcomeReceiver)}.
* @hide
*/
+ @UnsupportedAppUsage
public static final String KEY_SATELLITE_ENABLED = "satellite_enabled";
/**
@@ -134,6 +137,7 @@
* {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}.
* @hide
*/
+ @UnsupportedAppUsage
public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
/**
@@ -141,6 +145,7 @@
* {@link #requestIsSatelliteSupported(Executor, OutcomeReceiver)}.
* @hide
*/
+ @UnsupportedAppUsage
public static final String KEY_SATELLITE_SUPPORTED = "satellite_supported";
/**
@@ -148,6 +153,7 @@
* {@link #requestSatelliteCapabilities(Executor, OutcomeReceiver)}.
* @hide
*/
+ @UnsupportedAppUsage
public static final String KEY_SATELLITE_CAPABILITIES = "satellite_capabilities";
/**
@@ -155,6 +161,7 @@
* {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
* @hide
*/
+ @UnsupportedAppUsage
public static final String KEY_SATELLITE_PROVISIONED = "satellite_provisioned";
/**
@@ -162,6 +169,7 @@
* {@link #requestIsSatelliteCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}.
* @hide
*/
+ @UnsupportedAppUsage
public static final String KEY_SATELLITE_COMMUNICATION_ALLOWED =
"satellite_communication_allowed";
@@ -170,6 +178,7 @@
* {@link #requestTimeForNextSatelliteVisibility(Executor, OutcomeReceiver)}.
* @hide
*/
+ @UnsupportedAppUsage
public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
/**
@@ -340,6 +349,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener) {
@@ -382,6 +392,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -436,6 +447,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -488,6 +500,7 @@
*
* @throws IllegalStateException if the Telephony process is not currently available.
*/
+ @UnsupportedAppUsage
public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -541,6 +554,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -707,6 +721,7 @@
*/
public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
+ /** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
@@ -732,6 +747,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener,
@NonNull SatelliteTransmissionUpdateCallback callback) {
@@ -801,6 +817,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void stopSatelliteTransmissionUpdates(
@NonNull SatelliteTransmissionUpdateCallback callback,
@NonNull @CallbackExecutor Executor executor,
@@ -856,6 +873,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void provisionSatelliteService(@NonNull String token, @NonNull String regionId,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor executor,
@@ -895,7 +913,7 @@
* {@link SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean)}
* should report as deprovisioned.
* For provisioning satellite service, refer to
- * {@link #provisionSatelliteService(String, CancellationSignal, Executor, Consumer)}.
+ * {@link #provisionSatelliteService(String, String, CancellationSignal, Executor, Consumer)}
*
* @param token The token of the device/subscription to be deprovisioned.
* @param resultListener Listener for the {@link SatelliteError} result of the operation.
@@ -904,6 +922,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void deprovisionSatelliteService(@NonNull String token,
@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener) {
@@ -943,6 +962,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
@SatelliteError public int registerForSatelliteProvisionStateChanged(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteProvisionStateCallback callback) {
@@ -985,6 +1005,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void unregisterForSatelliteProvisionStateChanged(
@NonNull SatelliteProvisionStateCallback callback) {
Objects.requireNonNull(callback);
@@ -1023,6 +1044,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -1074,6 +1096,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
@SatelliteError public int registerForSatelliteModemStateChanged(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteStateCallback callback) {
@@ -1113,6 +1136,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
Objects.requireNonNull(callback);
ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
@@ -1147,6 +1171,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
@SatelliteError public int registerForSatelliteDatagram(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteDatagramCallback callback) {
@@ -1190,6 +1215,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
Objects.requireNonNull(callback);
ISatelliteDatagramCallback internalCallback =
@@ -1227,6 +1253,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(executor);
@@ -1279,6 +1306,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void sendSatelliteDatagram(@DatagramType int datagramType,
@NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
@NonNull @CallbackExecutor Executor executor,
@@ -1324,6 +1352,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -1381,6 +1410,7 @@
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @UnsupportedAppUsage
public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
Objects.requireNonNull(executor);
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index a62eb8b..20875af 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,6 +16,8 @@
package android.telephony.satellite;
+import android.compat.annotation.UnsupportedAppUsage;
+
/**
* A callback class for monitoring satellite provision state change events.
*
@@ -28,5 +30,6 @@
* @param provisioned The new provision state. {@code true} means satellite is provisioned
* {@code false} means satellite is not provisioned.
*/
+ @UnsupportedAppUsage
void onSatelliteProvisionStateChanged(boolean provisioned);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index d9ecaa3..3312e3a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,6 +16,8 @@
package android.telephony.satellite;
+import android.compat.annotation.UnsupportedAppUsage;
+
/**
* A callback class for monitoring satellite modem state change events.
*
@@ -26,5 +28,6 @@
* Called when satellite modem state changes.
* @param state The new satellite modem state.
*/
+ @UnsupportedAppUsage
void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index d4fe57a..17fff44 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A callback class for monitoring satellite position update and datagram transfer state change
@@ -30,6 +31,7 @@
*
* @param pointingInfo The pointing info containing the satellite location.
*/
+ @UnsupportedAppUsage
void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
/**
@@ -39,6 +41,7 @@
* @param sendPendingCount The number of datagrams that are currently being sent.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
+ @UnsupportedAppUsage
void onSendDatagramStateChanged(
@SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
@SatelliteManager.SatelliteError int errorCode);
@@ -50,6 +53,7 @@
* @param receivePendingCount The number of datagrams that are currently pending to be received.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
+ @UnsupportedAppUsage
void onReceiveDatagramStateChanged(
@SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
@SatelliteManager.SatelliteError int errorCode);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 5dc2dd7..47b2cda 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -40,20 +39,27 @@
* Launch an app [testApp] and wait animation to complete
* Press back button
* ```
+ *
* To run only the presubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
* ```
+ *
* To run only the postsubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
* ```
+ *
* To run only the flaky assertions add: `--
+ *
* ```
* --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -65,7 +71,6 @@
* ```
*/
@RequiresDevice
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
index 9fa84019..70eedd9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.close
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -25,7 +24,6 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index b042a14..d8abb4e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.close
import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -40,20 +39,27 @@
* Launch an app [testApp] and wait animation to complete
* Press home button
* ```
+ *
* To run only the presubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
* ```
+ *
* To run only the postsubmit assertions add: `--
+ *
* ```
* --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
* --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
* ```
+ *
* To run only the flaky assertions add: `--
+ *
* ```
* --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -65,7 +71,6 @@
* ```
*/
@RequiresDevice
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
index 136995a..c74f54b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.close
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -25,7 +24,6 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
index 3289bc6..ac05c76 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
@@ -41,4 +41,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
index ccbe74f..09c17b1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
@@ -18,7 +18,6 @@
import android.platform.test.annotations.FlakyTest
import android.tools.common.NavBar
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -29,7 +28,6 @@
import org.junit.runners.Parameterized
/** Some assertions will fail because of b/264415996 */
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index d0dc42f..5cacb04 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -18,14 +18,13 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,6 +41,7 @@
* Make sure no apps are running on the device
* Launch an app [testApp] and wait animation to complete
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -53,7 +53,6 @@
* ```
*/
@RequiresDevice
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
index f75d9ee..f77f968 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -27,7 +26,6 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -49,4 +47,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
index 4aa78d4..8b4a613 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
@@ -44,4 +44,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
new file mode 100644
index 0000000..d90b3ca
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.server.wm.flicker.launch
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromNotificationWarmCfArm(flicker: FlickerTest) :
+ OpenAppFromNotificationWarm(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 00d7544..66e0f06 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,7 +19,6 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -44,6 +43,7 @@
* Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
* complete (only this action is traced)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -55,7 +55,6 @@
* ```
*/
@RequiresDevice
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
index ff24190..8139e1f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.launch
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -26,7 +25,6 @@
import org.junit.runners.Parameterized
/** Some assertions will fail because of b/264415996 */
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 9ab6156..14df84e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -22,7 +22,6 @@
import android.tools.common.NavBar
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -48,6 +47,7 @@
* Lock the device.
* Launch an app on top of the lock screen [testApp] and wait animation to complete
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -59,7 +59,6 @@
* ```
*/
@RequiresDevice
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index cdd2d45..cfc8e46 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -18,7 +18,6 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -42,6 +41,7 @@
* Press home
* Relaunch an app [testApp] and wait animation to complete (only this action is traced)
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
@@ -53,7 +53,6 @@
* ```
*/
@RequiresDevice
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
index 9679059..b47c931 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.launch
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
@@ -25,7 +24,6 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -43,4 +41,4 @@
return FlickerTestFactory.nonRotationTests()
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
index 786bb32..e876e57 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
@@ -24,10 +24,10 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import android.view.KeyEvent
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,6 +44,7 @@
* Make sure no apps are running on the device
* Launch an app [testApp] and wait animation to complete
* ```
+ *
* Notes:
* ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index b848e63..6cbb975 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -27,13 +27,13 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.R
-import com.android.server.wm.flicker.helpers.setRotation
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
-import android.tools.device.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp
index e5eb3c7..7af76e1 100644
--- a/tests/componentalias/Android.bp
+++ b/tests/componentalias/Android.bp
@@ -16,6 +16,9 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// TODO: Delete this file. It's no longer needed, but removing it on udc-dev will cause
+// a conflict on master.
+
java_defaults {
name: "ComponentAliasTests_defaults",
static_libs: [
@@ -34,54 +37,3 @@
],
platform_apis: true, // We use hidden APIs in the test.
}
-
-// We build three APKs from the exact same source files, so these APKs contain the exact same tests.
-// And we run the tests on each APK, so that we can test various situations:
-// - When the alias is in the same package, target in the same package.
-// - When the alias is in the same package, target in another package.
-// - When the alias is in another package, which also contains the target.
-// - When the alias is in another package, and the target is in yet another package.
-// etc etc...
-
-android_test {
- name: "ComponentAliasTests",
- defaults: [
- "ComponentAliasTests_defaults",
- ],
- package_name: "android.content.componentalias.tests",
- manifest: "AndroidManifest.xml",
- additional_manifests: [
- "AndroidManifest_main.xml",
- "AndroidManifest_service_aliases.xml",
- "AndroidManifest_service_targets.xml",
- ],
- test_config_template: "AndroidTest-template.xml",
-}
-
-android_test {
- name: "ComponentAliasTests1",
- defaults: [
- "ComponentAliasTests_defaults",
- ],
- package_name: "android.content.componentalias.tests.sub1",
- manifest: "AndroidManifest.xml",
- additional_manifests: [
- "AndroidManifest_sub1.xml",
- "AndroidManifest_service_targets.xml",
- ],
- test_config_template: "AndroidTest-template.xml",
-}
-
-android_test {
- name: "ComponentAliasTests2",
- defaults: [
- "ComponentAliasTests_defaults",
- ],
- package_name: "android.content.componentalias.tests.sub2",
- manifest: "AndroidManifest.xml",
- additional_manifests: [
- "AndroidManifest_sub2.xml",
- "AndroidManifest_service_targets.xml",
- ],
- test_config_template: "AndroidTest-template.xml",
-}
diff --git a/tests/componentalias/AndroidManifest.xml b/tests/componentalias/AndroidManifest.xml
deleted file mode 100755
index 7bb83a3..0000000
--- a/tests/componentalias/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 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="android.content.componentalias.tests" >
-
- <application>
- <uses-library android:name="android.test.runner" />
- <property android:name="com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" android:value="true" />
- </application>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_main.xml b/tests/componentalias/AndroidManifest_main.xml
deleted file mode 100755
index 70e817e..0000000
--- a/tests/componentalias/AndroidManifest_main.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 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="android.content.componentalias.tests" >
-
- <application>
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.content.componentalias.tests" >
- </instrumentation>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_service_aliases.xml b/tests/componentalias/AndroidManifest_service_aliases.xml
deleted file mode 100644
index c96f173..0000000
--- a/tests/componentalias/AndroidManifest_service_aliases.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 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="android.content.componentalias.tests" >
- <application>
- <!--
- Note the alias components are essentially just placeholders, so the APKs don't have to
- have the implementation classes.
- -->
- <service android:name=".s.Alias00" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.s.Target00" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_00" /></intent-filter>
- </service>
- <service android:name=".s.Alias01" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target01" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_01" /></intent-filter>
- </service>
- <service android:name=".s.Alias02" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target02" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_02" /></intent-filter>
- </service>
- <service android:name=".s.Alias03" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target03" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_03" /></intent-filter>
- </service>
- <service android:name=".s.Alias04" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target04" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_04" /></intent-filter>
- </service>
-
- <receiver android:name=".b.Alias00" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.b.Target00" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Alias01" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target01" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Alias02" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target02" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Alias03" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target03" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Alias04" android:exported="true" android:enabled="true" >
- <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target04" />
- <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter>
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_service_targets.xml b/tests/componentalias/AndroidManifest_service_targets.xml
deleted file mode 100644
index 24c0432..0000000
--- a/tests/componentalias/AndroidManifest_service_targets.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 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="android.content.componentalias.tests" >
- <application>
- <service android:name=".s.Target00" android:exported="true" android:enabled="true" >
- </service>
- <service android:name=".s.Target01" android:exported="true" android:enabled="true" >
- </service>
- <service android:name=".s.Target02" android:exported="true" android:enabled="true" >
- </service>
- <service android:name=".s.Target03" android:exported="true" android:enabled="true" >
- </service>
- <service android:name=".s.Target04" android:exported="true" android:enabled="true" >
- </service>
-
- <!--
- Due to http://go/intents-match-intent-filters-guide, the target intent has to have
- an intent filter that matches the original intent. (modulo the package name)
- This restriction shouldn't exist in the final version.
- -->
- <receiver android:name=".b.Target00" android:exported="true" android:enabled="true" >
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Target01" android:exported="true" android:enabled="true" >
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Target02" android:exported="true" android:enabled="true" >
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Target03" android:exported="true" android:enabled="true" >
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- <receiver android:name=".b.Target04" android:exported="true" android:enabled="true" >
- <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter>
- <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_sub1.xml b/tests/componentalias/AndroidManifest_sub1.xml
deleted file mode 100755
index 21616f5..0000000
--- a/tests/componentalias/AndroidManifest_sub1.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 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="android.content.componentalias.tests" >
-
- <application>
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.content.componentalias.tests.sub1" >
- </instrumentation>
-</manifest>
diff --git a/tests/componentalias/AndroidManifest_sub2.xml b/tests/componentalias/AndroidManifest_sub2.xml
deleted file mode 100755
index c11b0cd..0000000
--- a/tests/componentalias/AndroidManifest_sub2.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 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="android.content.componentalias.tests" >
-
- <application>
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.content.componentalias.tests.sub2" >
- </instrumentation>
-</manifest>
diff --git a/tests/componentalias/AndroidTest-template.xml b/tests/componentalias/AndroidTest-template.xml
deleted file mode 100644
index afdfe79..0000000
--- a/tests/componentalias/AndroidTest-template.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 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.
--->
-<configuration>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="ComponentAliasTests.apk" />
- <option name="test-file-name" value="ComponentAliasTests1.apk" />
- <option name="test-file-name" value="ComponentAliasTests2.apk" />
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <!-- Exempt the helper APKs from the BG restriction, so they can start BG services. -->
- <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests" />
- <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub1" />
- <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub2" />
-
- <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests" />
- <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub1" />
- <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub2" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="{PACKAGE}" />
- <option name="runtime-hint" value="2m" />
- <option name="isolated-storage" value="false" />
- </test>
-</configuration>
diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
deleted file mode 100644
index 99322ee..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Build;
-import android.provider.DeviceConfig;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestUtils;
-
-import org.junit.AfterClass;
-import org.junit.Assume;
-import org.junit.Before;
-
-import java.util.function.Consumer;
-
-public class BaseComponentAliasTest {
- protected static final Context sContext = InstrumentationRegistry.getTargetContext();
-
- protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
- DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS);
- @Before
- public void enableComponentAliasWithCompatFlag() throws Exception {
- Assume.assumeTrue(Build.isDebuggable());
- ShellUtils.runShellCommand(
- "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
- sDeviceConfig.set("enable_experimental_component_alias", "");
- sDeviceConfig.set("component_alias_overrides", "");
-
- // Make sure the feature is actually enabled, and the aliases are loaded.
- TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
- String out = ShellUtils.runShellCommand("dumpsys activity component-alias");
-
- return out.contains("Enabled: true")
- && out.contains("android.content.componentalias.tests/.b.Alias04")
- && out.contains("android.content.componentalias.tests/.s.Alias04");
- });
- ShellUtils.runShellCommand("am wait-for-broadcast-idle");
- }
-
- @AfterClass
- public static void restoreDeviceConfig() throws Exception {
- ShellUtils.runShellCommand(
- "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
- sDeviceConfig.close();
- }
-
- protected static void log(String message) {
- Log.i(ComponentAliasTestCommon.TAG, "[" + sContext.getPackageName() + "] " + message);
- }
-
- /**
- * Defines a test target.
- */
- public static class Combo {
- public final ComponentName alias;
- public final ComponentName target;
- public final String action;
-
- public Combo(ComponentName alias, ComponentName target, String action) {
- this.alias = alias;
- this.target = target;
- this.action = action;
- }
-
- @Override
- public String toString() {
- return "Combo{"
- + "alias=" + toString(alias)
- + ", target=" + toString(target)
- + ", action='" + action + '\''
- + '}';
- }
-
- private static String toString(ComponentName cn) {
- return cn == null ? "[null]" : cn.flattenToShortString();
- }
-
- public void apply(Consumer<Combo> callback) {
- log("Testing for: " + this);
- callback.accept(this);
- }
- }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java
deleted file mode 100644
index 7d5e0b9..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.ComponentName;
-import android.content.Intent;
-
-import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
-
-import org.junit.Test;
-
-import java.util.function.Consumer;
-
-public class ComponentAliasBroadcastTest extends BaseComponentAliasTest {
- private void forEachCombo(Consumer<Combo> callback) {
- new Combo(
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias00"),
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Target00"),
- MAIN_PACKAGE + ".IS_RECEIVER_00").apply(callback);
-
- new Combo(
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias01"),
- new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".b.Target01"),
- MAIN_PACKAGE + ".IS_RECEIVER_01").apply(callback);
- new Combo(
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias02"),
- new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".b.Target02"),
- MAIN_PACKAGE + ".IS_RECEIVER_02").apply(callback);
- }
-
- @Test
- public void testBroadcast_explicitComponentName() {
- forEachCombo((c) -> {
- Intent i = new Intent().setComponent(c.alias);
- i.setAction("ACTION_BROADCAST");
- ComponentAliasMessage m;
-
- try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
- log("Sending: " + i);
- sContext.sendBroadcast(i);
-
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onReceive");
- assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString());
-
- // The broadcast intent will always have the receiving component name set.
- assertThat(m.getIntent().getComponent()).isEqualTo(c.target);
-
- receiver.ensureNoMoreMessages();
- }
- });
- }
-
- @Test
- public void testBroadcast_explicitPackageName() {
- forEachCombo((c) -> {
- // In this test, we only set the package name to the intent.
- // If the alias and target are the same package, the intent will be sent to both of them
- // *and* the one to the alias is redirected to the target, so the target will receive
- // the intent twice. This case is haled at *1 below.
-
-
- Intent i = new Intent().setPackage(c.alias.getPackageName());
- i.setAction(c.action);
- ComponentAliasMessage m;
-
- try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
- log("Sending broadcast: " + i);
- sContext.sendBroadcast(i);
-
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onReceive");
- assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString());
- assertThat(m.getIntent().getComponent()).isEqualTo(c.target);
-
- // *1 -- if the alias and target are in the same package, we expect one more
- // message.
- if (c.alias.getPackageName().equals(c.target.getPackageName())) {
- m = receiver.waitForNextMessage();
- assertThat(m.getMethodName()).isEqualTo("onReceive");
- assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString());
- assertThat(m.getIntent().getComponent()).isEqualTo(c.target);
- }
- receiver.ensureNoMoreMessages();
- }
- });
- }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java
deleted file mode 100644
index ee20379..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestUtils;
-
-import org.junit.AfterClass;
-import org.junit.Assume;
-import org.junit.Test;
-
-public class ComponentAliasEnableWithDeviceConfigTest {
- protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
- DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS);
-
- @AfterClass
- public static void restoreDeviceConfig() throws Exception {
- sDeviceConfig.close();
- }
-
- @Test
- public void enableComponentAliasWithCompatFlag() throws Exception {
- Assume.assumeTrue(Build.isDebuggable());
-
- sDeviceConfig.set("component_alias_overrides", "");
-
- // First, disable with both compat-id and device config.
- ShellUtils.runShellCommand(
- "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
- sDeviceConfig.set("enable_experimental_component_alias", "");
-
- TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
- return ShellUtils.runShellCommand("dumpsys activity component-alias")
- .indexOf("Enabled: false") > 0;
- });
-
- // Then, enable by device config.
- sDeviceConfig.set("enable_experimental_component_alias", "true");
-
- // Make sure the feature is actually enabled.
- TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
- return ShellUtils.runShellCommand("dumpsys activity component-alias")
- .indexOf("Enabled: true") > 0;
- });
- }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
deleted file mode 100644
index d41696f..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
-/**
- * Parcelabe containing a "message" that's meant to be delivered via BroadcastMessenger.
- *
- * To add a new field, just add a private member field, and run:
- * codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
- */
-@DataClass(
- genConstructor = false,
- genSetters = true,
- genToString = true,
- genAidl = false)
-public final class ComponentAliasMessage implements Parcelable {
- public ComponentAliasMessage() {
- }
-
- @Nullable
- private String mMessage;
-
- @Nullable
- private String mMethodName;
-
- @Nullable
- private String mSenderIdentity;
-
- @Nullable
- private Intent mIntent;
-
- @Nullable
- private ComponentName mComponent;
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- public @Nullable String getMessage() {
- return mMessage;
- }
-
- @DataClass.Generated.Member
- public @Nullable String getMethodName() {
- return mMethodName;
- }
-
- @DataClass.Generated.Member
- public @Nullable String getSenderIdentity() {
- return mSenderIdentity;
- }
-
- @DataClass.Generated.Member
- public @Nullable Intent getIntent() {
- return mIntent;
- }
-
- @DataClass.Generated.Member
- public @Nullable ComponentName getComponent() {
- return mComponent;
- }
-
- @DataClass.Generated.Member
- public @NonNull ComponentAliasMessage setMessage(@NonNull String value) {
- mMessage = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ComponentAliasMessage setMethodName(@NonNull String value) {
- mMethodName = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ComponentAliasMessage setSenderIdentity(@NonNull String value) {
- mSenderIdentity = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ComponentAliasMessage setIntent(@NonNull Intent value) {
- mIntent = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull ComponentAliasMessage setComponent(@NonNull ComponentName value) {
- mComponent = value;
- return this;
- }
-
- @Override
- @DataClass.Generated.Member
- public String toString() {
- // You can override field toString logic by defining methods like:
- // String fieldNameToString() { ... }
-
- return "ComponentAliasMessage { " +
- "message = " + mMessage + ", " +
- "methodName = " + mMethodName + ", " +
- "senderIdentity = " + mSenderIdentity + ", " +
- "intent = " + mIntent + ", " +
- "component = " + mComponent +
- " }";
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- byte flg = 0;
- if (mMessage != null) flg |= 0x1;
- if (mMethodName != null) flg |= 0x2;
- if (mSenderIdentity != null) flg |= 0x4;
- if (mIntent != null) flg |= 0x8;
- if (mComponent != null) flg |= 0x10;
- dest.writeByte(flg);
- if (mMessage != null) dest.writeString(mMessage);
- if (mMethodName != null) dest.writeString(mMethodName);
- if (mSenderIdentity != null) dest.writeString(mSenderIdentity);
- if (mIntent != null) dest.writeTypedObject(mIntent, flags);
- if (mComponent != null) dest.writeTypedObject(mComponent, flags);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- /* package-private */ ComponentAliasMessage(@NonNull Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- String message = (flg & 0x1) == 0 ? null : in.readString();
- String methodName = (flg & 0x2) == 0 ? null : in.readString();
- String senderIdentity = (flg & 0x4) == 0 ? null : in.readString();
- Intent intent = (flg & 0x8) == 0 ? null : (Intent) in.readTypedObject(Intent.CREATOR);
- ComponentName component = (flg & 0x10) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
-
- this.mMessage = message;
- this.mMethodName = methodName;
- this.mSenderIdentity = senderIdentity;
- this.mIntent = intent;
- this.mComponent = component;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<ComponentAliasMessage> CREATOR
- = new Parcelable.Creator<ComponentAliasMessage>() {
- @Override
- public ComponentAliasMessage[] newArray(int size) {
- return new ComponentAliasMessage[size];
- }
-
- @Override
- public ComponentAliasMessage createFromParcel(@NonNull Parcel in) {
- return new ComponentAliasMessage(in);
- }
- };
-
- @DataClass.Generated(
- time = 1630098801203L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java",
- inputSignatures = "private @android.annotation.Nullable java.lang.String mMessage\nprivate @android.annotation.Nullable java.lang.String mMethodName\nprivate @android.annotation.Nullable java.lang.String mSenderIdentity\nprivate @android.annotation.Nullable android.content.Intent mIntent\nprivate @android.annotation.Nullable android.content.ComponentName mComponent\nclass ComponentAliasMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true, genToString=true, genAidl=false)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java
deleted file mode 100644
index 0899886..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ShellUtils;
-
-import org.junit.AfterClass;
-import org.junit.Assume;
-import org.junit.Test;
-
-/**
- * Test to make sure component-alias can't be enabled on user builds.
- */
-public class ComponentAliasNotSupportedOnUserBuildTest {
- protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper(
- DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS);
-
- @AfterClass
- public static void restoreDeviceConfig() throws Exception {
- sDeviceConfig.close();
- }
-
- @Test
- public void enableComponentAliasWithCompatFlag() throws Exception {
- Assume.assumeFalse(Build.isDebuggable());
-
- // Try to enable it by both the device config and compat-id.
- sDeviceConfig.set("enable_experimental_component_alias", "true");
- ShellUtils.runShellCommand(
- "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android");
-
- // Sleep for an arbitrary amount of time, so the config would sink in, if there was
- // no "not on user builds" check.
-
- Thread.sleep(5000);
-
- // Make sure the feature is still disabled.
- assertThat(ShellUtils.runShellCommand("dumpsys activity component-alias")
- .indexOf("Enabled: false") > 0).isTrue();
- }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java
deleted file mode 100644
index f0ff088..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.componentalias.tests;
-
-import static android.content.Context.BIND_AUTO_CREATE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE;
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.hamcrest.core.IsNot.not;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-
-import com.android.compatibility.common.util.BroadcastMessenger;
-import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestUtils;
-
-import org.junit.Assume;
-import org.junit.Test;
-
-import java.util.function.Consumer;
-
-/**
- * Test for the experimental "Component alias" feature.
- *
- * Note this test exercises the relevant APIs, but don't actually check if the aliases are
- * resolved.
- *
- * Note all the helper APKs are battery-exempted (via AndroidTest.xml), so they can run
- * BG services.
- */
-public class ComponentAliasServiceTest extends BaseComponentAliasTest {
- /**
- * Service connection used throughout the tests. It sends a message for each callback via
- * the messenger.
- */
- private static final ServiceConnection sServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- log("onServiceConnected: " + name);
-
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity("sServiceConnection")
- .setMethodName("onServiceConnected")
- .setComponent(name);
-
- BroadcastMessenger.send(sContext, TAG, m);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- log("onServiceDisconnected: " + name);
-
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity("sServiceConnection")
- .setMethodName("onServiceDisconnected")
- .setComponent(name);
-
- BroadcastMessenger.send(sContext, TAG, m);
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- log("onBindingDied: " + name);
-
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity("sServiceConnection")
- .setMethodName("onBindingDied");
-
- BroadcastMessenger.send(sContext, TAG, m);
- }
-
- @Override
- public void onNullBinding(ComponentName name) {
- log("onNullBinding: " + name);
-
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity("sServiceConnection")
- .setMethodName("onNullBinding");
-
- BroadcastMessenger.send(sContext, TAG, m);
- }
- };
-
- private void testStartAndStopService_common(
- Intent originalIntent,
- ComponentName componentNameForClient,
- ComponentName componentNameForTarget) {
-
- ComponentAliasMessage m;
-
- try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
- // Start the service.
- ComponentName result = sContext.startService(originalIntent);
- assertThat(result).isEqualTo(componentNameForClient);
-
- // Check
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onStartCommand");
- // The app sees the rewritten intent.
- assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget);
-
- // Verify the original intent.
- assertThat(m.getIntent().getOriginalIntent().getComponent())
- .isEqualTo(originalIntent.getComponent());
- assertThat(m.getIntent().getOriginalIntent().getPackage())
- .isEqualTo(originalIntent.getPackage());
-
- // Stop the service.
- sContext.stopService(originalIntent);
-
- // Check
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onDestroy");
-
- receiver.ensureNoMoreMessages();
- }
- }
-
- private void forEachCombo(Consumer<Combo> callback) {
- new Combo(
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias00"),
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target00"),
- MAIN_PACKAGE + ".IS_ALIAS_00").apply(callback);
- new Combo(
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"),
- new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".s.Target01"),
- MAIN_PACKAGE + ".IS_ALIAS_01").apply(callback);
- new Combo(
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"),
- new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"),
- MAIN_PACKAGE + ".IS_ALIAS_02").apply(callback);
- }
-
- @Test
- public void testStartAndStopService_explicitComponentName() {
- forEachCombo((c) -> {
- Intent i = new Intent().setComponent(c.alias);
- testStartAndStopService_common(i, c.alias, c.target);
- });
- }
-
- @Test
- public void testStartAndStopService_explicitPackageName() {
- forEachCombo((c) -> {
- Intent i = new Intent().setPackage(c.alias.getPackageName());
- i.setAction(c.action);
-
- testStartAndStopService_common(i, c.alias, c.target);
- });
- }
-
- @Test
- public void testStartAndStopService_override() throws Exception {
- Intent i = new Intent().setPackage(MAIN_PACKAGE);
- i.setAction(MAIN_PACKAGE + ".IS_ALIAS_01");
-
- // Change some of the aliases from what's defined in <meta-data>.
-
- ComponentName aliasA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01");
- ComponentName targetA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target02");
-
- ComponentName aliasB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02");
- ComponentName targetB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target01");
-
- sDeviceConfig.set("component_alias_overrides",
- aliasA.flattenToShortString() + ":" + targetA.flattenToShortString()
- + ","
- + aliasB.flattenToShortString() + ":" + targetB.flattenToShortString());
-
- TestUtils.waitUntil("Wait until component alias is actually enabled", () -> {
- return ShellUtils.runShellCommand("dumpsys activity component-alias")
- .indexOf(aliasA.flattenToShortString()
- + " -> " + targetA.flattenToShortString()) > 0;
- });
-
-
- testStartAndStopService_common(i, aliasA, targetA);
- }
-
- private void testBindAndUnbindService_common(
- Intent originalIntent,
- ComponentName componentNameForClient,
- ComponentName componentNameForTarget) {
- ComponentAliasMessage m;
-
- try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
- // Bind to the service.
- assertThat(sContext.bindService(
- originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue();
-
- // Check the target side behavior.
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onBind");
- // The app sees the rewritten intent.
- assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget);
-
- // Verify the original intent.
- assertThat(m.getIntent().getOriginalIntent().getComponent())
- .isEqualTo(originalIntent.getComponent());
- assertThat(m.getIntent().getOriginalIntent().getPackage())
- .isEqualTo(originalIntent.getPackage());
-
- // Check the client side behavior.
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onServiceConnected");
- // The app sees the rewritten intent.
- assertThat(m.getComponent()).isEqualTo(componentNameForClient);
-
- // Unbind.
- sContext.unbindService(sServiceConnection);
-
- // Check the target side behavior.
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onDestroy");
-
- // Note onServiceDisconnected() won't be called in this case.
- receiver.ensureNoMoreMessages();
- }
- }
-
- @Test
- public void testBindService_explicitComponentName() {
- forEachCombo((c) -> {
- Intent i = new Intent().setComponent(c.alias);
-
- testBindAndUnbindService_common(i, c.alias, c.target);
- });
-
- }
-
- @Test
- public void testBindService_explicitPackageName() {
- forEachCombo((c) -> {
- Intent i = new Intent().setPackage(c.alias.getPackageName());
- i.setAction(c.action);
-
- testBindAndUnbindService_common(i, c.alias, c.target);
- });
- }
-
- /**
- * Make sure, when the service process is killed, the client will get a callback with the
- * right component name.
- */
- @Test
- public void testBindService_serviceKilled() {
-
- // We need to kill SUB2_PACKAGE, don't run it for this package.
- Assume.assumeThat(sContext.getPackageName(), not(SUB2_PACKAGE));
-
- Intent originalIntent = new Intent().setPackage(MAIN_PACKAGE);
- originalIntent.setAction(MAIN_PACKAGE + ".IS_ALIAS_02");
-
- final ComponentName componentNameForClient =
- new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02");
- final ComponentName componentNameForTarget =
- new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02");
-
- ComponentAliasMessage m;
-
- try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) {
- // Bind to the service.
- assertThat(sContext.bindService(
- originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue();
-
- // Check the target side behavior.
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onBind");
-
- m = receiver.waitForNextMessage();
- assertThat(m.getMethodName()).isEqualTo("onServiceConnected");
- assertThat(m.getComponent()).isEqualTo(componentNameForClient);
- // We don't need to check all the fields because these are tested else where.
-
- // Now kill the service process.
- ShellUtils.runShellCommand("su 0 killall %s", SUB2_PACKAGE);
-
- // Check the target side behavior.
- m = receiver.waitForNextMessage();
-
- assertThat(m.getMethodName()).isEqualTo("onServiceDisconnected");
- assertThat(m.getComponent()).isEqualTo(componentNameForClient);
-
- receiver.ensureNoMoreMessages();
- }
- }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java
deleted file mode 100644
index 165d728..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests;
-
-public final class ComponentAliasTestCommon {
- private ComponentAliasTestCommon() {
- }
-
- public static final String TAG = "ComponentAliasTest";
-
- public static final String MAIN_PACKAGE = "android.content.componentalias.tests";
-
- public static final String SUB1_PACKAGE = "android.content.componentalias.tests.sub1";
- public static final String SUB2_PACKAGE = "android.content.componentalias.tests.sub2";
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java
deleted file mode 100644
index 1d05e72..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.b;
-
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.componentalias.tests.ComponentAliasMessage;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BroadcastMessenger;
-
-public class BaseReceiver extends BroadcastReceiver {
- private String getMyIdentity(Context context) {
- return (new ComponentName(context.getPackageName(), this.getClass().getCanonicalName()))
- .flattenToShortString();
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.i(TAG, "onReceive: on " + getMyIdentity(context) + " intent=" + intent);
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity(getMyIdentity(context))
- .setMethodName("onReceive")
- .setIntent(intent);
- BroadcastMessenger.send(context, TAG, m);
- }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java
deleted file mode 100644
index 8fb4e91..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.b;
-
-import android.content.componentalias.tests.s.BaseService;
-
-public class Target00 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java
deleted file mode 100644
index 06f7a13..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.b;
-
-public class Target01 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java
deleted file mode 100644
index df7579d..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.b;
-
-public class Target02 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java
deleted file mode 100644
index 5ae5521..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.b;
-
-public class Target03 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java
deleted file mode 100644
index f9b9886..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.b;
-
-public class Target04 extends BaseReceiver {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java b/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java
deleted file mode 100644
index 535d9b8..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.s;
-
-import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG;
-
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.componentalias.tests.ComponentAliasMessage;
-import android.os.Binder;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BroadcastMessenger;
-
-public class BaseService extends Service {
- private String getMyIdentity() {
- return (new ComponentName(this.getPackageName(), this.getClass().getCanonicalName()))
- .flattenToShortString();
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i(TAG, "onStartCommand: on " + getMyIdentity() + " intent=" + intent);
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity(getMyIdentity())
- .setMethodName("onStartCommand")
- .setIntent(intent);
- BroadcastMessenger.send(this, TAG, m);
-
- return START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- Log.i(TAG, "onDestroy: on " + getMyIdentity());
-
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity(getMyIdentity())
- .setMethodName("onDestroy");
- BroadcastMessenger.send(this, TAG, m);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- Log.i(TAG, "onBind: on " + getMyIdentity() + " intent=" + intent);
-
- ComponentAliasMessage m = new ComponentAliasMessage()
- .setSenderIdentity(getMyIdentity())
- .setMethodName("onBind")
- .setIntent(intent);
- BroadcastMessenger.send(this, TAG, m);
-
- return new Binder();
- }
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java
deleted file mode 100644
index 64b91f5..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.s;
-
-public class Target00 extends BaseService {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java
deleted file mode 100644
index bd58999..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.s;
-
-public class Target01 extends BaseService {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java
deleted file mode 100644
index 0ddf818..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.s;
-
-public class Target02 extends BaseService {
-}
diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java
deleted file mode 100644
index 0dbc050..0000000
--- a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.content.componentalias.tests.s;
-
-public class Target03 extends BaseService {
-}
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
index 7375c16..43f2122 100644
--- a/tools/lint/fix/Android.bp
+++ b/tools/lint/fix/Android.bp
@@ -23,9 +23,8 @@
python_binary_host {
name: "lint_fix",
- main: "lint_fix.py",
- srcs: ["lint_fix.py"],
- libs: ["soong_lint_fix"],
+ main: "soong_lint_fix.py",
+ srcs: ["soong_lint_fix.py"],
}
python_library_host {
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
index 367d0bc..a5ac2be 100644
--- a/tools/lint/fix/README.md
+++ b/tools/lint/fix/README.md
@@ -5,9 +5,12 @@
## What is this?
It's a python script that runs the framework linter,
-and then copies modified files back into the source tree.\
+and then (optionally) copies modified files back into the source tree.\
Why python, you ask? Because python is cool ¯\_(ツ)_/¯.
+Incidentally, this exposes a much simpler way to run individual lint checks
+against individual modules, so it's useful beyond applying fixes.
+
## Why?
Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
@@ -17,30 +20,11 @@
## How do I run it?
**WARNING: You probably want to commit/stash any changes to your working tree before doing this...**
-From this directory, run `python lint_fix.py -h`.
-The script's help output explains things that are omitted here.
-
-Alternatively, there is a python binary target you can build to make this
-available anywhere in your tree:
```
+source build/envsetup.sh
+lunch cf_x86_64_phone-userdebug # or any lunch target
m lint_fix
lint_fix -h
```
-**Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first.
-
-Example: `lint_fix frameworks/base/services/core/services.core.unboosted UseEnforcePermissionAnnotation --dry-run`
-```shell
-(
-export ANDROID_LINT_CHECK=UseEnforcePermissionAnnotation;
-cd $ANDROID_BUILD_TOP;
-source build/envsetup.sh;
-rm out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html;
-m out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint/lint-report.html;
-cd out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/lint;
-unzip suggested-fixes.zip -d suggested-fixes;
-cd suggested-fixes;
-find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1' --;
-rm -rf suggested-fixes
-)
-```
+The script's help output explains things that are omitted here.
diff --git a/tools/lint/fix/lint_fix.py b/tools/lint/fix/lint_fix.py
deleted file mode 100644
index 1c83f7b..0000000
--- a/tools/lint/fix/lint_fix.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright (C) 2023 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.
-#
-# 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.
-
-from soong_lint_fix import SoongLintFix
-
-SoongLintFix().run()
diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py
index 3308df6..cd4d778d 100644
--- a/tools/lint/fix/soong_lint_fix.py
+++ b/tools/lint/fix/soong_lint_fix.py
@@ -13,14 +13,21 @@
# limitations under the License.
import argparse
+import json
import os
+import shutil
import subprocess
import sys
+import zipfile
ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT")
+PRODUCT_OUT = ANDROID_PRODUCT_OUT.removeprefix(f"{ANDROID_BUILD_TOP}/")
+
+SOONG_UI = "build/soong/soong_ui.bash"
PATH_PREFIX = "out/soong/.intermediates"
PATH_SUFFIX = "android_common/lint"
-FIX_DIR = "suggested-fixes"
+FIX_ZIP = "suggested-fixes.zip"
class SoongLintFix:
"""
@@ -28,14 +35,12 @@
apply lint fixes to the platform via the necessary
combination of soong and shell commands.
- It provides some basic hooks for experimental code
- to tweak the generation of the resulting shell script.
+ It breaks up these operations into a few "private" methods
+ that are intentionally exposed so experimental code can tweak behavior.
- By default, it will apply lint fixes using the intermediate `suggested-fixes`
- directory that soong creates during its invocation of lint.
-
- The default argument parser configures a number of command line arguments to
- facilitate running lint via soong.
+ The entry point, `run`, will apply lint fixes using the
+ intermediate `suggested-fixes` directory that soong creates during its
+ invocation of lint.
Basic usage:
```
@@ -45,99 +50,95 @@
```
"""
def __init__(self):
- self._commands = None
+ self._parser = _setup_parser()
self._args = None
+ self._kwargs = None
self._path = None
self._target = None
- self._parser = _setup_parser()
- def add_argument(self, *args, **kwargs):
- """
- If necessary, add arguments to the underlying argparse.ArgumentParser before running
- """
- self._parser.add_argument(*args, **kwargs)
-
-
- def run(self, add_setup_commands=None, override_fix_commands=None):
+ def run(self, additional_setup=None, custom_fix=None):
"""
Run the script
- :param add_setup_commands: OPTIONAL function to add additional setup commands
- passed the command line arguments, path, and build target
- must return a list of strings (the additional commands)
- :param override_fix_commands: OPTIONAL function to override the fix commands
- passed the command line arguments, path, and build target
- must return a list of strings (the fix commands)
"""
self._setup()
- if add_setup_commands:
- self._commands += add_setup_commands(self._args, self._path, self._target)
-
- self._add_lint_report_commands()
+ self._find_module()
+ self._lint()
if not self._args.no_fix:
- if override_fix_commands:
- self._commands += override_fix_commands(self._args, self._path, self._target)
- else:
- self._commands += [
- f"cd {self._path}",
- f"unzip {FIX_DIR}.zip -d {FIX_DIR}",
- f"cd {FIX_DIR}",
- # Find all the java files in the fix directory, excluding the ./out subdirectory,
- # and copy them back into the same path within the tree.
- f"find . -path ./out -prune -o -name '*.java' -print | xargs -n 1 sh -c 'cp $1 $ANDROID_BUILD_TOP/$1 || exit 255' --",
- f"rm -rf {FIX_DIR}"
- ]
+ self._fix()
-
- if self._args.dry_run:
- print(self._get_commands_str())
- else:
- self._execute()
-
+ if self._args.print:
+ self._print()
def _setup(self):
self._args = self._parser.parse_args()
- self._commands = []
- self._path = f"{PATH_PREFIX}/{self._args.build_path}/{PATH_SUFFIX}"
- self._target = f"{self._path}/lint-report.html"
-
- if not self._args.dry_run:
- self._commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"]
-
+ env = os.environ.copy()
if self._args.check:
- self._commands += [f"export ANDROID_LINT_CHECK={self._args.check}"]
+ env["ANDROID_LINT_CHECK"] = self._args.check
+ if self._args.lint_module:
+ env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._args.lint_module
+
+ self._kwargs = {
+ "env": env,
+ "executable": "/bin/bash",
+ "shell": True,
+ }
+
+ os.chdir(ANDROID_BUILD_TOP)
- def _add_lint_report_commands(self):
- self._commands += [
- "cd $ANDROID_BUILD_TOP",
- "source build/envsetup.sh",
- # remove the file first so soong doesn't think there is no work to do
- f"rm {self._target}",
- # remove in case there are fixes from a prior run,
- # that we don't want applied if this run fails
- f"rm {self._path}/{FIX_DIR}.zip",
- f"m {self._target}",
- ]
+ def _find_module(self):
+ print("Refreshing soong modules...")
+ try:
+ os.mkdir(ANDROID_PRODUCT_OUT)
+ except OSError:
+ pass
+ subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs)
+ print("done.")
+
+ with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f:
+ module_info = json.load(f)
+
+ if self._args.module not in module_info:
+ sys.exit(f"Module {self._args.module} not found!")
+
+ module_path = module_info[self._args.module]["path"][0]
+ print(f"Found module {module_path}/{self._args.module}.")
+
+ self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}"
+ self._target = f"{self._path}/lint-report.txt"
- def _get_commands_str(self):
- prefix = "(\n"
- delimiter = ";\n"
- suffix = "\n)"
- return f"{prefix}{delimiter.join(self._commands)}{suffix}"
+ def _lint(self):
+ print("Cleaning up any old lint results...")
+ try:
+ os.remove(f"{self._target}")
+ os.remove(f"{self._path}/{FIX_ZIP}")
+ except FileNotFoundError:
+ pass
+ print("done.")
+
+ print(f"Generating {self._target}")
+ subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs)
+ print("done.")
- def _execute(self, with_echo=True):
- if with_echo:
- exec_commands = []
- for c in self._commands:
- exec_commands.append(f'echo "{c}"')
- exec_commands.append(c)
- self._commands = exec_commands
+ def _fix(self):
+ print("Copying suggested fixes to the tree...")
+ with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip:
+ for name in zip.namelist():
+ if name.startswith("out") or not name.endswith(".java"):
+ continue
+ with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst:
+ shutil.copyfileobj(src, dst)
+ print("done.")
- subprocess.call(self._get_commands_str(), executable='/bin/bash', shell=True)
+
+ def _print(self):
+ print("### lint-report.txt ###", end="\n\n")
+ with open(self._target, "r") as f:
+ print(f.read())
def _setup_parser():
@@ -147,23 +148,26 @@
2. Run lint on the specified target.
3. Copy the modified files, from soong's intermediate directory, back into the tree.
- **Gotcha**: You must have run `source build/envsetup.sh` and `lunch`
- so that the `ANDROID_BUILD_TOP` environment variable has been set.
- Alternatively, set it manually in your shell.
+ **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first.
""", formatter_class=argparse.RawTextHelpFormatter)
- parser.add_argument('build_path', metavar='build_path', type=str,
- help='The build module to run '
- '(e.g. frameworks/base/framework-minus-apex or '
- 'frameworks/base/services/core/services.core.unboosted)')
+ parser.add_argument('module',
+ help='The soong build module to run '
+ '(e.g. framework-minus-apex or services.core.unboosted)')
- parser.add_argument('--check', metavar='check', type=str,
+ parser.add_argument('--check',
help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.')
- parser.add_argument('--dry-run', dest='dry_run', action='store_true',
- help='Just print the resulting shell script instead of running it.')
+ parser.add_argument('--lint-module',
+ help='Specific lint module to run. Passed to the ANDROID_LINT_CHECK_EXTRA_MODULES environment variable.')
- parser.add_argument('--no-fix', dest='no_fix', action='store_true',
+ parser.add_argument('--no-fix', action='store_true',
help='Just build and run the lint, do NOT apply the fixes.')
+ parser.add_argument('--print', action='store_true',
+ help='Print the contents of the generated lint-report.txt at the end.')
+
return parser
+
+if __name__ == "__main__":
+ SoongLintFix().run()
\ No newline at end of file
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index b4f6a1c..935bade 100644
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -35,7 +35,8 @@
CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE,
CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
- PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
+ // TODO: Currently crashes due to OOM issue
+ // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
)
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index f7560a7..75b0073 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -321,6 +321,34 @@
)
}
+ fun testDoesDetectIssuesShortStringsNotAllowed() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass121 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteral() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass121.java:6: Error: The method \
+ TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
+ which differs from the overridden method Stub.testMethodAnyLiteral: \
+ @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+ The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteral() {}
+ ~~~~~~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.addLineContinuation()
+ )
+ }
+
/* Stubs */
// A service with permission annotation on the method.
diff --git a/wifi/TEST_MAPPING b/wifi/TEST_MAPPING
index 94e4f4d..14f5af3 100644
--- a/wifi/TEST_MAPPING
+++ b/wifi/TEST_MAPPING
@@ -3,5 +3,15 @@
{
"name": "FrameworksWifiNonUpdatableApiTests"
}
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsWifiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported"
+ }
+ ]
+ }
]
}