diff options
483 files changed, 10914 insertions, 5092 deletions
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 69fe85e37b4b..430a1e25e123 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 android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; /** @@ -264,7 +265,7 @@ class Alarm { 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 @@ class Alarm { 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 c76a43fa38f5..3772960e8ac4 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 @@ public class AlarmManagerService extends SystemService { 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 @@ public class AlarmManagerService extends SystemService { } 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 @@ public class AlarmManagerService extends SystemService { + " 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 @@ public class AlarmManagerService extends SystemService { } 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 @@ public class AlarmManagerService extends SystemService { } } - 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 @@ public class AlarmManagerService extends SystemService { 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 89eb1a9a7650..4477e94b77f1 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 @@ class JobConcurrencyManager { 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 08810b549e62..ea146403546b 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.Activity; 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService } // 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService + " 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 4c339ac66160..7388fff17735 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 @@ public final class JobServiceContext implements ServiceConnection { 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 a6d064c76044..4d8b3e147880 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_CONSUMPTION_LIMIT_C 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_CONSUMPTION_LIMIT; 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 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { }; private long mMinSatiatedBalanceExempted; + private long mMinSatiatedBalanceHeadlessSystemApp; private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; private long mInitialSatiatedConsumptionLimit; @@ -179,6 +182,9 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { 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 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { 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 ffb2c03fb02b..1c915bc17604 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 @@ public class InternalResourceService extends SystemService { @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 @@ public class InternalResourceService extends SystemService { } } + 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 @@ public class InternalResourceService extends SystemService { } 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 c2a6e43ba930..526e87683d30 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_CONSUMPTION_LIMIT_C 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_CONSUMPTION_LIMIT; 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 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { }; private long mMinSatiatedBalanceExempted; + private long mMinSatiatedBalanceHeadlessSystemApp; private long mMinSatiatedBalanceOther; private long mMinSatiatedBalanceIncrementalAppUpdater; private long mMaxSatiatedBalance; @@ -194,6 +197,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { final long baseBalance; if (mIrs.isPackageExempted(userId, pkgName)) { baseBalance = mMinSatiatedBalanceExempted; + } else if (mIrs.isHeadlessSystemApp(pkgName)) { + baseBalance = mMinSatiatedBalanceHeadlessSystemApp; } else { baseBalance = mMinSatiatedBalanceOther; } @@ -276,9 +281,14 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { 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/core/api/current.txt b/core/api/current.txt index 70dcd542d715..b91144b3a045 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -83,6 +83,7 @@ package android { 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"; @@ -13669,7 +13670,9 @@ package android.credentials { 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); } @@ -13734,6 +13737,16 @@ package android.credentials { 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>); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 51863bba480a..7107bf7419e5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2251,7 +2251,7 @@ package android.os { 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 d0ce70133414..12026aa3f72a 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -72,6 +72,20 @@ public abstract class Animator implements Cloneable { 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 @@ public abstract class Animator implements Cloneable { * @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 abstract class Animator implements Cloneable { 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 @@ public abstract class Animator implements Cloneable { 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 @@ public abstract class Animator implements Cloneable { } /** + * 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 @@ public abstract class Animator implements Cloneable { 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 257adfe390d6..60659dc12342 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -32,6 +32,7 @@ import java.util.Collection; 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim */ 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } } 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } } - mPlayingSet.clear(); } endAnimation(); } @@ -662,6 +660,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim super.pause(); if (!previouslyPaused && mPaused) { mPauseTime = -1; + callOnPlayingSet(Animator::pause); } } @@ -676,6 +675,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (mPauseTime >= 0) { addAnimationCallback(0); } + callOnPlayingSet(Animator::resume); } } @@ -716,6 +716,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } } - 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim 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 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim 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 7009725aa32b..5d69f8b80799 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -199,13 +199,6 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio } } - 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio 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 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio 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 981f14020370..929c07bc1dc5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -234,7 +234,7 @@ public class ActivityManager { * 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 class ActivityManager { 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 class ActivityManager { 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 @@ public class ActivityManager { @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 b96f8c94aaa3..0293bb53d3f0 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -474,6 +474,12 @@ public abstract class ActivityManagerInternal { 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 2879062248a8..403acad6bba1 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -879,6 +879,8 @@ interface IActivityManager { /** 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 ac928116481f..4dc7253c27dc 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1151,7 +1151,7 @@ public class Instrumentation { } 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/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 0bdc222f323c..1df860258d82 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/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b9c671a8f3ea..21e2a131bcac 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -10134,25 +10134,9 @@ public abstract class PackageManager { /** * 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 493a4ff3c4cf..5579d2263d06 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 @@ public final class CredentialManager { } /** - * 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 @@ public final class CredentialManager { } } - 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 @@ public final class CredentialManager { } } + /** @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 @@ public final class CredentialManager { 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 @@ public final class CredentialManager { 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/GetPendingCredentialResponse.java b/core/java/android/credentials/GetPendingCredentialResponse.java deleted file mode 100644 index 9005d9dfa031..000000000000 --- 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 af8e7b413ea9..5fde96b0b9ff 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -27,7 +27,7 @@ import android.credentials.UnregisterCredentialDescriptionRequest; 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 @@ interface ICredentialManager { @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 index 4ab0f998adae..c918aec62349 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 000000000000..81e906859cb8 --- /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 index 1cdd637c7511..217dac8cd136 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 000000000000..9afd44f09a8c --- /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/display/HdrConversionMode.java b/core/java/android/hardware/display/HdrConversionMode.java index 49e5eff95a25..5fccb5e6d9db 100644 --- a/core/java/android/hardware/display/HdrConversionMode.java +++ b/core/java/android/hardware/display/HdrConversionMode.java @@ -29,9 +29,6 @@ import java.lang.annotation.RetentionPolicy; /** * 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 3fffb3ca2c7b..03d6d91d1e78 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -161,9 +161,8 @@ public final class VirtualDisplayConfig implements Parcelable { } /** - * 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 diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index b478a37984e0..af09a0662795 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -1295,19 +1295,31 @@ public final class FileUtils { * 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 fcebb455d09d..8e1d2d6c97e6 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -134,7 +134,7 @@ interface IUserManager { 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/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java index 221e89a6a76f..310ceb3aeb91 100644 --- a/core/java/android/os/PermissionEnforcer.java +++ b/core/java/android/os/PermissionEnforcer.java @@ -18,9 +18,11 @@ package android.os; 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 @@ import android.permission.PermissionCheckerManager; 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 @@ public class PermissionEnforcer { 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 @@ public class PermissionEnforcer { 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 @@ public class PermissionEnforcer { 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 86e678d9da97..b3604da49f5e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3056,14 +3056,14 @@ public class UserManager { } /** - * 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 b117a9afb6f5..6f2a915cee46 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -141,12 +141,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba 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 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba 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 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } mZenMode = mNotificationManager.getZenMode(); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { mVolumeGroupId = getVolumeGroupIdForLegacyStreamType(mStreamType); mAttributes = getAudioAttributesForLegacyStreamType( mStreamType); @@ -213,6 +217,12 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba 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 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba onInitSample(); } break; + case MSG_UPDATE_SLIDER_MAYBE_LATER: + onUpdateSliderMaybeLater(); + break; default: Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what); } @@ -353,6 +366,21 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba : 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 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba postStopSample(); mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); mReceiver.setListening(false); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { unregisterVolumeGroupCb(); } mSeekBar.setOnSeekBarChangeListener(null); @@ -442,7 +470,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]), false, mVolumeObserver); mReceiver.setListening(true); - if (hasAudioProductStrategies()) { + if (mDeviceHasProductStrategies) { registerVolumeGroupCb(); } } @@ -466,6 +494,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba 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 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba 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 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } } 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/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index ea86fcc519b9..8069414c3e6e 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -357,16 +357,6 @@ public final class CredentialProviderInfoFactory { Log.e(TAG, "Failed to get capabilities: ", e); } - try { - String[] discovered = - metadata.getStringArray(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; diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index a1c5593579c6..ff6dffdaaf5d 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -293,6 +293,9 @@ public final class HotwordDetectedResult implements Parcelable { * * <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 @@ public final class HotwordDetectedResult implements Parcelable { * * <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 @@ public final class HotwordDetectedResult implements Parcelable { * * <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 @@ public final class HotwordDetectedResult implements Parcelable { } @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/util/DataUnit.java b/core/java/android/util/DataUnit.java index cf045b8aec06..cc33af32ba93 100644 --- a/core/java/android/util/DataUnit.java +++ b/core/java/android/util/DataUnit.java @@ -36,9 +36,11 @@ public enum DataUnit { 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 6201b3a91eff..bc514b091409 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -229,9 +229,9 @@ public class FeatureFlagUtils { 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/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java index c66c70af0656..c1c13171f83c 100644 --- a/core/java/android/view/ContentRecordingSession.java +++ b/core/java/android/view/ContentRecordingSession.java @@ -25,7 +25,6 @@ import android.os.IBinder; 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 @@ public final class ContentRecordingSession implements Parcelable { * 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 @@ public final class ContentRecordingSession implements Parcelable { } /** - * 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 @@ public final class ContentRecordingSession implements Parcelable { /* 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 @@ public final class ContentRecordingSession implements Parcelable { } 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 @@ public final class ContentRecordingSession implements Parcelable { * 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 @@ public final class ContentRecordingSession implements Parcelable { * 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 @@ public final class ContentRecordingSession implements Parcelable { return "ContentRecordingSession { " + "displayId = " + mDisplayId + ", " + "contentToRecord = " + recordContentToString(mContentToRecord) + ", " + - "tokenToRecord = " + mTokenToRecord + + "tokenToRecord = " + mTokenToRecord + ", " + + "waitingToRecord = " + mWaitingToRecord + " }"; } @@ -275,7 +304,8 @@ public final class ContentRecordingSession implements Parcelable { 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 @@ public final class ContentRecordingSession implements Parcelable { _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 @@ public final class ContentRecordingSession implements Parcelable { // 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 @@ public final class ContentRecordingSession implements Parcelable { // 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 @@ public final class ContentRecordingSession implements Parcelable { } 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 @@ public final class ContentRecordingSession implements Parcelable { 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 @@ public final class ContentRecordingSession implements Parcelable { * 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 @@ public final class ContentRecordingSession implements Parcelable { 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 @@ public final class ContentRecordingSession implements Parcelable { } @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 6b604422ffba..77f3b1da92b4 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -151,14 +151,16 @@ public class HandwritingInitiator { // 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 @@ public class HandwritingInitiator { 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 @@ public class HandwritingInitiator { * 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 @@ public class HandwritingInitiator { mShouldInitHandwriting = true; mHasInitiatedHandwriting = false; + mHasPreparedHandwritingDelegation = false; mExceedHandwritingSlop = false; } } diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java index 867d2b462565..a2e0326c84b1 100644 --- a/core/java/android/view/InsetsFrameProvider.java +++ b/core/java/android/view/InsetsFrameProvider.java @@ -293,7 +293,7 @@ public class InsetsFrameProvider implements Parcelable { public static void calculateInsetsFrame(Rect displayFrame, Rect containerBounds, Rect displayCutoutSafe, Rect inOutFrame, int source, Insets insetsSize, @WindowManager.LayoutParams.PrivateFlags int privateFlags, - Insets displayCutoutSafeInsetsSize) { + Insets displayCutoutSafeInsetsSize, Rect givenContentInsets) { boolean extendByCutout = false; if (source == InsetsFrameProvider.SOURCE_DISPLAY) { inOutFrame.set(displayFrame); @@ -301,16 +301,20 @@ public class InsetsFrameProvider implements Parcelable { inOutFrame.set(containerBounds); } else { extendByCutout = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0; - } - if (insetsSize == null) { - return; + if (givenContentInsets != null) { + inOutFrame.inset(givenContentInsets); + } } if (displayCutoutSafeInsetsSize != null) { sTmpRect2.set(inOutFrame); } - calculateInsetsFrame(inOutFrame, insetsSize); + if (insetsSize != null) { + calculateInsetsFrame(inOutFrame, insetsSize); + } - if (extendByCutout) { + if (extendByCutout && insetsSize != null) { + // Only extend if the insets size is not null. Otherwise, the frame has already been + // extended by the display cutout during layout process. WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, inOutFrame, sTmpRect); } diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index b574ecf32dd8..66fd794e64fc 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -15,6 +15,10 @@ siyamed@google.com # 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 4895aed60a3a..0db52aaa8b3d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3896,10 +3896,12 @@ public final class SurfaceControl implements Parcelable { 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/SurfaceView.java b/core/java/android/view/SurfaceView.java index d8b6b7b25a24..b46a68c1d5fd 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -851,14 +851,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId(); - // Only control visibility if we're not hardware-accelerated. Otherwise we'll - // let renderthread drive since offscreen SurfaceControls should not be visible. - if (!isHardwareAccelerated()) { - if (mViewVisibility) { - surfaceUpdateTransaction.show(mSurfaceControl); - } else { - surfaceUpdateTransaction.hide(mSurfaceControl); - } + if (mViewVisibility) { + surfaceUpdateTransaction.show(mSurfaceControl); + } else { + surfaceUpdateTransaction.hide(mSurfaceControl); } updateBackgroundVisibility(surfaceUpdateTransaction); @@ -1421,10 +1417,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private final Rect mRTLastReportedPosition = new Rect(); + private final Point mRTLastReportedSurfaceSize = new Point(); private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener { private final int mRtSurfaceWidth; private final int mRtSurfaceHeight; + private boolean mRtFirst = true; private final SurfaceControl.Transaction mPositionChangedTransaction = new SurfaceControl.Transaction(); @@ -1435,6 +1433,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { + if (!mRtFirst && (mRTLastReportedPosition.left == left + && mRTLastReportedPosition.top == top + && mRTLastReportedPosition.right == right + && mRTLastReportedPosition.bottom == bottom + && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth + && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight)) { + return; + } + mRtFirst = false; try { if (DEBUG_POSITION) { Log.d(TAG, String.format( @@ -1445,8 +1452,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } synchronized (mSurfaceControlLock) { if (mSurfaceControl == null) return; - mRTLastReportedPosition.set(left, top, right, bottom); + mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight); onSetSurfacePositionAndScale(mPositionChangedTransaction, mSurfaceControl, mRTLastReportedPosition.left /*positionLeft*/, mRTLastReportedPosition.top /*positionTop*/, @@ -1454,8 +1461,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall / (float) mRtSurfaceWidth /*postScaleX*/, mRTLastReportedPosition.height() / (float) mRtSurfaceHeight /*postScaleY*/); - - mPositionChangedTransaction.show(mSurfaceControl); + if (mViewVisibility) { + // b/131239825 + mPositionChangedTransaction.show(mSurfaceControl); + } } applyOrMergeTransaction(mPositionChangedTransaction, frameNumber); } catch (Exception ex) { @@ -1481,6 +1490,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall System.identityHashCode(this), frameNumber)); } mRTLastReportedPosition.setEmpty(); + mRTLastReportedSurfaceSize.set(-1, -1); // positionLost can be called while UI thread is un-paused. synchronized (mSurfaceControlLock) { diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING index ecb98f9ce801..1e39716988a9 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 aec3487910d8..6cd894113ca6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -163,7 +163,6 @@ import android.view.translation.ViewTranslationCallback; 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 d1f9fbded003..152fa08d2a9d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -11365,9 +11365,13 @@ public final class ViewRootImpl implements ViewParent, 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 9d82b7900689..b17d2b947c66 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -2648,7 +2648,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * 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 7da69e7d819b..e267a7f1e248 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -365,7 +365,10 @@ public class AutofillFeatureFlags { } /** - * 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 508c20a37215..781a4b63f889 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -687,11 +687,11 @@ public final class AutofillManager { // 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 @@ public final class AutofillManager { 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 @@ public final class AutofillManager { } /** + * 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 final class AutofillManager { 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 @@ public final class AutofillManager { 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 @@ public final class AutofillManager { } /** - * 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 @@ public final class AutofillManager { } /** - * 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 @@ public final class AutofillManager { 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 e7207fa0dfa0..c4d43bcfdb24 100644 --- a/core/java/android/view/inputmethod/HandwritingGesture.java +++ b/core/java/android/view/inputmethod/HandwritingGesture.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; 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.concurrent.Executor; 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 5353bc6983ee..715fc9abe4ba 100644 --- a/core/java/android/webkit/HttpAuthHandler.java +++ b/core/java/android/webkit/HttpAuthHandler.java @@ -41,6 +41,9 @@ public class HttpAuthHandler extends Handler { * 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 @@ public class HttpAuthHandler extends Handler { /** * 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 @@ public class HttpAuthHandler extends Handler { * 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 7b6e1a370479..55f09f110f88 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -455,6 +455,9 @@ public class WebViewClient { * {@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 dce54329da8e..088065d2f77d 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3193,47 +3193,66 @@ public class Editor { 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 @@ public class Editor { .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/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java index 3478b0f05198..954f68633e57 100644 --- a/core/java/android/window/WindowMetricsController.java +++ b/core/java/android/window/WindowMetricsController.java @@ -54,9 +54,6 @@ import java.util.function.Supplier; * @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 @@ public final class WindowMetricsController { 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 0d02683868c5..a1d571fc4350 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/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java index 2f3b662af6a7..65fbb03bf967 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 @@ public final class Histogram { /** * 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 @@ public final class Histogram { /** * Logs increment sample count for automatically calculated bin + * * @param sample value * @hide */ @@ -52,6 +58,7 @@ public final class Histogram { 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 @@ public final class Histogram { * 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 @@ public final class Histogram { 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 @@ public final class Histogram { 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 @@ public final class Histogram { 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/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 5cab674eab05..1172f7ba447a 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -1297,6 +1297,17 @@ public class TransitionAnimation { 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 ad1fdbae037d..ec525f09fa88 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -85,7 +85,7 @@ public enum ProtoLogGroup implements IProtoLogGroup { 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/jni/OWNERS b/core/jni/OWNERS index bd85874b195f..bce533281186 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -5,6 +5,9 @@ per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhij # 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 d62f1cfcbddf..ce806a0fcc08 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -2296,7 +2296,9 @@ pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, // 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 0f1e558c98e1..4c6272365fcd 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4493,6 +4493,12 @@ <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 9185bae4e992..4d09076779a3 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 7b82b4de3979..bb6dc468b163 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 000000000000..ef77b341dbaa --- /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 77ba94ea0db7..cf0aee573908 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 000000000000..28e6733b77c1 --- /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 000000000000..8e05a52d791e --- /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 16a8bb7280a4..710a70a32955 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 0ba01e9cd948..423af837bed6 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 b53502d2a666..cd25726ac730 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" /> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index dc7565894f8f..31e5f3265823 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 19fa7a3b0a2d..0600b3802fe1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -755,6 +755,11 @@ we rely on gravity to determine the effective orientation. --> <bool name="config_deskDockEnablesAccelerometer">true</bool> + <!-- Control whether nosensor and locked orientation requests are respected from the app when + config_deskDockEnablesAccelerometer is set to false. + TODO(b/274763533): Consider making true by default and removing this. --> + <bool name="config_deskRespectsNoSensorAndLockedWithoutAccelerometer">false</bool> + <!-- Car dock behavior --> <!-- The number of degrees to rotate the display when the device is in a car dock. @@ -5498,6 +5503,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 80bf7955c030..b1b1edf85a09 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/symbols.xml b/core/res/res/values/symbols.xml index 251db6d87850..25be28cb65ed 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1692,6 +1692,7 @@ <java-symbol type="bool" name="config_carDockEnablesAccelerometer" /> <java-symbol type="bool" name="config_customUserSwitchUi" /> <java-symbol type="bool" name="config_deskDockEnablesAccelerometer" /> + <java-symbol type="bool" name="config_deskRespectsNoSensorAndLockedWithoutAccelerometer" /> <java-symbol type="bool" name="config_disableMenuKeyInLockScreen" /> <java-symbol type="bool" name="config_enableCarDockHomeLaunch" /> <java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" /> @@ -4487,6 +4488,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 +4516,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 +5038,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 e96de582022a..bdbf96b97c1e 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -358,6 +358,8 @@ please see themes_device_defaults.xml. <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 ba0579171c43..aea01783ee68 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 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @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 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @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/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java index 7a1de0c5d4fe..a7538701807a 100644 --- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java +++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java @@ -435,9 +435,11 @@ public class AnimatorSetActivityTest { 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 000000000000..43266a51502b --- /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 dee0a3ecdbe0..a53d57f0383c 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.Test; 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 @@ public class ValueAnimatorTests { }); } + @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 81cd4da4f425..8cc88ea230a1 100644 --- a/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java +++ b/core/tests/coretests/src/android/animation/ViewPropertyAnimatorTest.java @@ -135,11 +135,15 @@ public class ViewPropertyAnimatorTest { * @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/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java index e31d5aef9b69..6f0c3d306bd5 100644 --- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java +++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java @@ -141,8 +141,9 @@ public class CredentialManagerTest { @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 000000000000..4e5906427af4 --- /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 91fbe0034133..394ff0ae9a2e 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -54,6 +54,7 @@ import static org.junit.Assert.fail; 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 @@ public class FileUtilsTest { @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; - - 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(G16, roundStorageSize(G16)); - assertEquals(G32, roundStorageSize(G16 + 1)); - assertEquals(G32, roundStorageSize(G32 - 1)); - assertEquals(G32, roundStorageSize(G32)); - assertEquals(G64, roundStorageSize(G32 + 1)); + 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(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(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 ec296b76c374..034cbddc0144 100644 --- a/core/tests/coretests/src/android/util/DataUnitTest.java +++ b/core/tests/coretests/src/android/util/DataUnitTest.java @@ -26,11 +26,13 @@ public class DataUnitTest extends TestCase { 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 df96a7d4568a..b3fe5c8addfd 100644 --- a/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java +++ b/core/tests/coretests/src/android/view/ContentRecordingSessionTest.java @@ -89,20 +89,22 @@ public class ContentRecordingSessionTest { } @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 fccb177dad4f..8ae63816ba16 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 @@ public class HandwritingInitiatorTest { } @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/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java new file mode 100644 index 000000000000..ee62d7528818 --- /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 9fa6d0634fbe..037dbb32c2f8 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.runner.RunWith; 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 @@ public class UniformOptionsTest { } @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 @@ public class UniformOptionsTest { } @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 @@ public class UniformOptionsTest { } @Test - @SmallTest public void testBinIndexForRangeEqual5() { Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10); assertEquals(4, options.getBinsCount()); @@ -87,7 +81,6 @@ public class UniformOptionsTest { } @Test - @SmallTest public void testBinIndexForRangeEqual10() { Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101); assertEquals(0, options.getBinForSample(0)); @@ -101,7 +94,6 @@ public class UniformOptionsTest { } @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 1a5db2f192cf..59277203ea71 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 f4e94ad0e04b..af6492609157 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 5549f88b65e0..a73010ba0e41 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 24fea014287d..99bebb8b9812 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -210,12 +210,16 @@ public abstract class ColorSpace { 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 @@ public abstract class ColorSpace { 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 @@ public abstract class ColorSpace { 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 @@ public abstract class ColorSpace { 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 @@ public abstract class ColorSpace { * </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 @@ public abstract class ColorSpace { /** 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 @@ public abstract class ColorSpace { * @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 abstract class ColorSpace { */ 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 @@ public abstract class ColorSpace { 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 @@ public abstract class ColorSpace { 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 @@ public abstract class ColorSpace { } 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 51f99ec637da..60898ef80a7e 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -914,6 +914,7 @@ public final class ImageDecoder implements AutoCloseable { 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/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml index 27e0b184f427..5d7771366bec 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 000000000000..022594982ca3 --- /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 000000000000..3e0297ab612b --- /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 000000000000..35562b650994 --- /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 f9aeb6a8448a..ac13eaeda6f5 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 index 29cf1512e2e5..5ab159cdf264 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/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 0897712e2037..848f28c51f7a 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 bc58e202d80d..9ed281fb3686 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 2aaf9245594d..a8480ebbf64d 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 ad6e618c8a94..b158cdd4b0d3 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 1dd09f5986ae..954d34fdfa5a 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 b5d4eb195933..a7757c649c89 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 5c95f9e5ff23..f3f80b830cff 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 f9ff29cd8a41..e7fa212334a8 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 b54e60d1d11a..10216675887b 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 11090e02a927..b2b761973fb0 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 1eca52ae7701..a09a647d4a0b 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 8493434f87af..0a0b30dc8407 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 e9c78fc26f07..33f533398531 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 8493434f87af..0a0b30dc8407 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 8493434f87af..0a0b30dc8407 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 466a2bf885ba..69823f990f3b 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 b0175913a442..b7b31f459335 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 986a2c856cd1..5ef402c35bfa 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 b84c4f2ae8cb..e083be417a90 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 a2972193d5a3..e0922e4d5f5a 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 b379428861e3..e847f6e05187 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 7e3eae0fb41a..c86711e44618 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 f3a31620f696..8de29c13c800 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 134fb9712098..d71824f4ca9f 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 f0a8deebccf4..de4cac24aab8 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 ea43370d7581..667464f9523b 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 df2f2df6df19..8cd0bc5d2f3e 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 a6301b3ce0ed..ae5e1823bbea 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 b9259703daa5..9963acea93bc 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 e1d1de4aad99..02cf02f870d0 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 de952add248a..b60c2314ab5e 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 762f3a84ad5b..29ff6c3abdbe 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 f9582ffbc39c..a67e066d0274 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 2b30aabab88f..0b88d7ac2c88 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 27b89b7e869f..683a90333b4a 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 6aae84c3df2b..c3114100b410 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 3f2a17be4971..be8e247a8cbe 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 ae85c1f6b492..d6a1f1626b64 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 133eedb16b64..385dc327bfad 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 c38609edaed8..561806c64fef 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 e0b414100b9d..c0c9eb7e247f 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 f85fe1b06328..a18d1f1d9042 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 6f225998a319..b891c5906ba4 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 5a88cd098489..2b6adc16e85c 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 6b164e96de78..3afa9fbc624b 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 2d02ed5f8f20..21e89c3666b3 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 64002834b233..e781f4522efb 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 680bd5dd47f6..ef7773b9ca31 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 c30b6f158bd2..b034990ee845 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 00f4aa0d4b63..c739ba07020f 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 c30b6f158bd2..b034990ee845 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 4cfb1fa953dd..cbe11dd9ac45 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 741a5d141ccb..7602282a94a5 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 4b89ab132780..c9081848e31e 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 6eb83edc8116..ed7feb936b7c 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 e804c523346b..0b64d5d13d40 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 9aafa8705a0b..8f44eeb83a2e 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 6316d52b57d0..1bd744a27d00 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 46bd638a9393..f2f3bbc99bf8 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 512bd528dd65..581a5abeeee9 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 e53386956785..44afb58e7ada 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 426e83a560cd..d287e9d81d96 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 f4373e6fb634..5599351e323a 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 8c07814042e2..548981568342 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 bbb363932d55..bb144a7f5992 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 caca25a1a76c..4b2ee183c8d7 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 7787e774763e..1a6e46c60170 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 6fb70006e67f..2a03b03d86f1 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 680ad5101366..88ca10d20521 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/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java index cb1a6e7ace6b..ac6e4c2a6521 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 @@ public class TabletopModeController implements */ 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 b447a543989e..5459094fd9ef 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 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange 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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 3d5230d5cf90..57b5b8f24fad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -82,6 +82,7 @@ import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; @@ -520,6 +521,9 @@ public abstract class WMShellBaseModule { desktopModeTaskRepository, mainExecutor)); } + @BindsOptionalOf + abstract RecentsTransitionHandler optionalRecentsTransitionHandler(); + // // Shell transitions // @@ -803,6 +807,7 @@ public abstract class WMShellBaseModule { Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformComponents> freeformComponents, Optional<RecentTasksController> recentTasksOptional, + Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional, Optional<OneHandedController> oneHandedControllerOptional, Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional, Optional<ActivityEmbeddingController> activityEmbeddingOptional, 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 7a83d101578f..eb7c32fe8227 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 @@ -83,6 +83,7 @@ import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -528,9 +529,20 @@ public abstract class WMShellModule { ShellInit shellInit, Optional<SplitScreenController> splitScreenOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, + Optional<RecentsTransitionHandler> recentsTransitionHandler, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, - pipTouchHandlerOptional); + pipTouchHandlerOptional, recentsTransitionHandler); + } + + @WMSingleton + @Provides + static RecentsTransitionHandler provideRecentsTransitionHandler( + ShellInit shellInit, + Transitions transitions, + Optional<RecentTasksController> recentTasksController) { + return new RecentsTransitionHandler(shellInit, transitions, + recentTasksController.orElse(null)); } // @@ -659,13 +671,17 @@ public abstract class WMShellModule { 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 c9c0e40f616c..ad334b5f2dc8 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.RemoteException; 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 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } 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 000000000000..015d5c1705e7 --- /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 31c5e33f21e3..5696dfc5069e 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.WindowContainerToken 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 @@ import java.util.function.Consumer /** 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 @@ class DesktopTasksController( } /** + * 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/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 748f4a190b00..582616d99954 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb // 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb 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/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java index 23988a62735d..a7171fd5b220 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 @@ public class PipSizeSpecHandler { */ @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 @@ public class PipSizeSpecHandler { 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/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index 1a6c1d65db03..4048c5b8feab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -17,6 +17,11 @@ package com.android.wm.shell.recents; import android.app.ActivityManager.RunningTaskInfo; +import android.app.IApplicationThread; +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.view.IRecentsAnimationRunner; import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -45,4 +50,10 @@ interface IRecentTasks { * Gets the set of running tasks. */ RunningTaskInfo[] getRunningTasks(int maxNum) = 4; + + /** + * Starts a recents transition. + */ + oneway void startRecentsTransition(in PendingIntent intent, in Intent fillIn, in Bundle options, + IApplicationThread appThread, IRecentsAnimationRunner listener) = 5; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 0d9faa3c6f83..c5bfd8753994 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,13 +24,18 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RE import android.app.ActivityManager; import android.app.ActivityTaskManager; +import android.app.IApplicationThread; +import android.app.PendingIntent; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.view.IRecentsAnimationRunner; import androidx.annotation.BinderThread; import androidx.annotation.NonNull; @@ -79,6 +84,7 @@ public class RecentTasksController implements TaskStackListenerCallback, private final TaskStackListenerImpl mTaskStackListener; private final RecentTasksImpl mImpl = new RecentTasksImpl(); private final ActivityTaskManager mActivityTaskManager; + private RecentsTransitionHandler mTransitionHandler = null; private IRecentTasksListener mListener; private final boolean mIsDesktopMode; @@ -150,6 +156,10 @@ public class RecentTasksController implements TaskStackListenerCallback, mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this)); } + void setTransitionHandler(RecentsTransitionHandler handler) { + mTransitionHandler = handler; + } + /** * Adds a split pair. This call does not validate the taskIds, only that they are not the same. */ @@ -492,5 +502,18 @@ public class RecentTasksController implements TaskStackListenerCallback, true /* blocking */); return tasks[0]; } + + @Override + public void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, + IApplicationThread appThread, IRecentsAnimationRunner listener) { + if (mController.mTransitionHandler == null) { + Slog.e(TAG, "Used shell-transitions startRecentsTransition without" + + " shell-transitions"); + return; + } + executeRemoteCallWithTaskPermission(mController, "startRecentsTransition", + (controller) -> controller.mTransitionHandler.startRecentsTransition( + intent, fillIn, options, appThread, listener)); + } } -}
\ No newline at end of file +} 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 new file mode 100644 index 000000000000..db75be75788a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -0,0 +1,780 @@ +/* + * 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.recents; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; +import static android.view.WindowManager.TRANSIT_SLEEP; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.IApplicationThread; +import android.app.PendingIntent; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Slog; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.window.PictureInPictureSurfaceTransaction; +import android.window.TaskSnapshot; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; + +import java.util.ArrayList; + +/** + * Handles the Recents (overview) animation. Only one of these can run at a time. A recents + * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored. + */ +public class RecentsTransitionHandler implements Transitions.TransitionHandler { + private static final String TAG = "RecentsTransitionHandler"; + + private final Transitions mTransitions; + private final ShellExecutor mExecutor; + private IApplicationThread mAnimApp = null; + private final ArrayList<RecentsController> mControllers = new ArrayList<>(); + + /** + * List of other handlers which might need to mix recents with other things. These are checked + * in the order they are added. Ideally there should only be one. + */ + private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>(); + + public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, + @Nullable RecentTasksController recentTasksController) { + mTransitions = transitions; + mExecutor = transitions.getMainExecutor(); + if (!Transitions.ENABLE_SHELL_TRANSITIONS) return; + if (recentTasksController == null) return; + shellInit.addInitCallback(() -> { + recentTasksController.setTransitionHandler(this); + transitions.addHandler(this); + }, this); + } + + /** Register a mixer handler. {@see RecentsMixedHandler}*/ + public void addMixer(RecentsMixedHandler mixer) { + mMixers.add(mixer); + } + + /** Unregister a Mixed Handler */ + public void removeMixer(RecentsMixedHandler mixer) { + mMixers.remove(mixer); + } + + void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, + IApplicationThread appThread, IRecentsAnimationRunner listener) { + // only care about latest one. + mAnimApp = appThread; + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.sendPendingIntent(intent, fillIn, options); + final RecentsController controller = new RecentsController(listener); + RecentsMixedHandler mixer = null; + Transitions.TransitionHandler mixedHandler = null; + for (int i = 0; i < mMixers.size(); ++i) { + mixedHandler = mMixers.get(i).handleRecentsRequest(wct); + if (mixedHandler != null) { + mixer = mMixers.get(i); + break; + } + } + final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, + mixedHandler == null ? this : mixedHandler); + if (mixer != null) { + mixer.setRecentsTransition(transition); + } + if (transition == null) { + controller.cancel(); + return; + } + controller.setTransition(transition); + mControllers.add(controller); + } + + @Override + public WindowContainerTransaction handleRequest(IBinder transition, + TransitionRequestInfo request) { + // do not directly handle requests. Only entry point should be via startRecentsTransition + return null; + } + + private int findController(IBinder transition) { + for (int i = mControllers.size() - 1; i >= 0; --i) { + if (mControllers.get(i).mTransition == transition) return i; + } + return -1; + } + + @Override + public boolean startAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction, + Transitions.TransitionFinishCallback finishCallback) { + final int controllerIdx = findController(transition); + if (controllerIdx < 0) return false; + final RecentsController controller = mControllers.get(controllerIdx); + Transitions.setRunningRemoteTransitionDelegate(mAnimApp); + mAnimApp = null; + if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { + return false; + } + return true; + } + + @Override + public void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + Transitions.TransitionFinishCallback finishCallback) { + final int targetIdx = findController(mergeTarget); + if (targetIdx < 0) return; + final RecentsController controller = mControllers.get(targetIdx); + controller.merge(info, t, finishCallback); + } + + @Override + public void onTransitionConsumed(IBinder transition, boolean aborted, + SurfaceControl.Transaction finishTransaction) { + final int idx = findController(transition); + if (idx < 0) return; + mControllers.get(idx).cancel(); + } + + /** There is only one of these and it gets reset on finish. */ + private class RecentsController extends IRecentsAnimationController.Stub { + private IRecentsAnimationRunner mListener; + private IBinder.DeathRecipient mDeathHandler; + private Transitions.TransitionFinishCallback mFinishCB = null; + private SurfaceControl.Transaction mFinishTransaction = null; + + /** + * List of tasks that we are switching away from via this transition. Upon finish, these + * pausing tasks will become invisible. + * These need to be ordered since the order must be restored if there is no task-switch. + */ + private ArrayList<TaskState> mPausingTasks = null; + + /** + * List of tasks that we are switching to. Upon finish, these will remain visible and + * on top. + */ + private ArrayList<TaskState> mOpeningTasks = null; + + private WindowContainerToken mPipTask = null; + private WindowContainerToken mRecentsTask = null; + private int mRecentsTaskId = -1; + private TransitionInfo mInfo = null; + private boolean mOpeningSeparateHome = false; + private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null; + private PictureInPictureSurfaceTransaction mPipTransaction = null; + private IBinder mTransition = null; + private boolean mKeyguardLocked = false; + private boolean mWillFinishToHome = false; + + /** The animation is idle, waiting for the user to choose a task to switch to. */ + private static final int STATE_NORMAL = 0; + + /** The user chose a new task to switch to and the animation is animating to it. */ + private static final int STATE_NEW_TASK = 1; + + /** The latest state that the recents animation is operating in. */ + private int mState = STATE_NORMAL; + + RecentsController(IRecentsAnimationRunner listener) { + mListener = listener; + mDeathHandler = () -> mExecutor.execute(() -> { + if (mListener == null) return; + if (mFinishCB != null) { + finish(mWillFinishToHome, false /* leaveHint */); + } + }); + try { + mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */); + } catch (RemoteException e) { + mListener = null; + } + } + + void setTransition(IBinder transition) { + mTransition = transition; + } + + void cancel() { + // restoring (to-home = false) involves submitting more WM changes, so by default, use + // toHome = true when canceling. + cancel(true /* toHome */); + } + + void cancel(boolean toHome) { + 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(); + } + } + + /** + * Sends a cancel message to the recents animation with snapshots. Used to trigger a + * "replace-with-screenshot" like behavior. + */ + private boolean sendCancelWithSnapshots() { + int[] taskIds = null; + TaskSnapshot[] snapshots = null; + if (mPausingTasks.size() > 0) { + taskIds = new int[mPausingTasks.size()]; + snapshots = new TaskSnapshot[mPausingTasks.size()]; + try { + for (int i = 0; i < mPausingTasks.size(); ++i) { + snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot( + mPausingTasks.get(0).mTaskInfo.taskId); + } + } catch (RemoteException e) { + taskIds = null; + snapshots = null; + } + } + try { + mListener.onAnimationCanceled(taskIds, snapshots); + } catch (RemoteException e) { + Slog.e(TAG, "Error canceling recents animation", e); + return false; + } + return true; + } + + void cleanUp() { + if (mListener != null && mDeathHandler != null) { + mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */); + mDeathHandler = null; + } + mListener = null; + mFinishCB = null; + // clean-up leash surfacecontrols and anything that might reference them. + if (mLeashMap != null) { + for (int i = 0; i < mLeashMap.size(); ++i) { + mLeashMap.valueAt(i).release(); + } + mLeashMap = null; + } + mFinishTransaction = null; + mPausingTasks = null; + mOpeningTasks = null; + mInfo = null; + mTransition = null; + mControllers.remove(this); + } + + boolean start(TransitionInfo info, SurfaceControl.Transaction t, + SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) { + if (mListener == null || mTransition == null) { + cleanUp(); + return false; + } + // First see if this is a valid recents transition. + boolean hasPausingTasks = false; + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (TransitionUtil.isWallpaper(change)) continue; + if (TransitionUtil.isClosingType(change.getMode())) { + hasPausingTasks = true; + continue; + } + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { + mRecentsTask = taskInfo.token; + mRecentsTaskId = taskInfo.taskId; + } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { + mRecentsTask = taskInfo.token; + mRecentsTaskId = taskInfo.taskId; + } + } + 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."); + cleanUp(); + return false; + } + + mInfo = info; + mFinishCB = finishCB; + mFinishTransaction = finishT; + mPausingTasks = new ArrayList<>(); + mOpeningTasks = new ArrayList<>(); + mLeashMap = new ArrayMap<>(); + mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0; + + final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); + final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>(); + TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter(); + // About layering: we divide up the "layer space" into 3 regions (each the size of + // the change count). This lets us categorize things into above/below/between + // while maintaining their relative ordering. + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (TransitionUtil.isWallpaper(change)) { + final RemoteAnimationTarget target = TransitionUtil.newTarget(change, + // wallpapers go into the "below" layer space + info.getChanges().size() - i, info, t, mLeashMap); + wallpapers.add(target); + // Make all the wallpapers opaque since we want them visible from the start + t.setAlpha(target.leash, 1); + } else if (leafTaskFilter.test(change)) { + // start by putting everything into the "below" layer space. + final RemoteAnimationTarget target = TransitionUtil.newTarget(change, + info.getChanges().size() - i, info, t, mLeashMap); + apps.add(target); + if (TransitionUtil.isClosingType(change.getMode())) { + // raise closing (pausing) task to "above" layer so it isn't covered + t.setLayer(target.leash, info.getChanges().size() * 3 - i); + mPausingTasks.add(new TaskState(change, target.leash)); + if (taskInfo.pictureInPictureParams != null + && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { + mPipTask = taskInfo.token; + } + } else if (taskInfo != null + && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { + // There's a 3p launcher, so make sure recents goes above that. + t.setLayer(target.leash, info.getChanges().size() * 3 - i); + } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { + // do nothing + } else if (TransitionUtil.isOpeningType(change.getMode())) { + mOpeningTasks.add(new TaskState(change, target.leash)); + } + } + } + t.apply(); + try { + mListener.onAnimationStart(this, + apps.toArray(new RemoteAnimationTarget[apps.size()]), + wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), + new Rect(0, 0, 0, 0), new Rect()); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting recents animation", e); + cancel(); + } + return true; + } + + @SuppressLint("NewApi") + void merge(TransitionInfo info, SurfaceControl.Transaction t, + Transitions.TransitionFinishCallback finishCallback) { + if (mFinishCB == null) { + // This was no-op'd (likely a repeated start) and we've already sent finish. + return; + } + if (info.getType() == TRANSIT_SLEEP) { + // A sleep event means we need to stop animations immediately, so cancel here. + cancel(); + return; + } + ArrayList<TransitionInfo.Change> openingTasks = null; + ArrayList<TransitionInfo.Change> closingTasks = null; + mOpeningSeparateHome = false; + TransitionInfo.Change recentsOpening = null; + boolean foundRecentsClosing = false; + 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 != null && mRecentsTask.equals(change.getContainer())) { + recentsOpening = change; + } else if (isLeafTask) { + if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { + // This is usually a 3p launcher + mOpeningSeparateHome = true; + } + if (openingTasks == null) { + openingTasks = new ArrayList<>(); + } + openingTasks.add(change); + } + } else if (TransitionUtil.isClosingType(change.getMode())) { + if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) { + foundRecentsClosing = true; + } else if (isLeafTask) { + if (closingTasks == null) { + closingTasks = new ArrayList<>(); + } + closingTasks.add(change); + } + } else if (change.getMode() == TRANSIT_CHANGE) { + // Finish recents animation if the display is changed, so the default + // transition handler can play the animation such as rotation effect. + if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) { + cancel(mWillFinishToHome); + return; + } + hasChangingApp = true; + } + } + if (hasChangingApp && foundRecentsClosing) { + // This happens when a visible app is expanding (usually PiP). In this case, + // that transition probably has a special-purpose animation, so finish recents + // now and let it do its animation (since recents is going to be occluded). + sendCancelWithSnapshots(); + mExecutor.executeDelayed( + () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0); + return; + } + if (recentsOpening != null) { + // the recents task re-appeared. This happens if the user gestures before the + // task-switch (NEW_TASK) animation finishes. + if (mState == STATE_NORMAL) { + Slog.e(TAG, "Returning to recents while recents is already idle."); + } + if (closingTasks == null || closingTasks.size() == 0) { + Slog.e(TAG, "Returning to recents without closing any opening tasks."); + } + // Setup may hide it initially since it doesn't know that overview was still active. + t.show(recentsOpening.getLeash()); + t.setAlpha(recentsOpening.getLeash(), 1.f); + mState = STATE_NORMAL; + } + boolean didMergeThings = false; + if (closingTasks != null) { + // Cancelling a task-switch. Move the tasks back to mPausing from mOpening + for (int i = 0; i < closingTasks.size(); ++i) { + final TransitionInfo.Change change = closingTasks.get(i); + int openingIdx = TaskState.indexOf(mOpeningTasks, change); + if (openingIdx < 0) { + Slog.e(TAG, "Back to existing recents animation from an unrecognized " + + "task: " + change.getTaskInfo().taskId); + continue; + } + mPausingTasks.add(mOpeningTasks.remove(openingIdx)); + didMergeThings = true; + } + } + RemoteAnimationTarget[] appearedTargets = null; + if (openingTasks != null && openingTasks.size() > 0) { + // Switching to some new tasks, add to mOpening and remove from mPausing. Also, + // enter NEW_TASK state since this will start the switch-to animation. + final int layer = mInfo.getChanges().size() * 3; + appearedTargets = new RemoteAnimationTarget[openingTasks.size()]; + for (int i = 0; i < openingTasks.size(); ++i) { + final TransitionInfo.Change change = openingTasks.get(i); + int pausingIdx = TaskState.indexOf(mPausingTasks, change); + if (pausingIdx >= 0) { + // Something is showing/opening a previously-pausing app. + appearedTargets[i] = TransitionUtil.newTarget( + change, layer, mPausingTasks.get(pausingIdx).mLeash); + mOpeningTasks.add(mPausingTasks.remove(pausingIdx)); + // Setup hides opening tasks initially, so make it visible again (since we + // are already showing it). + t.show(change.getLeash()); + t.setAlpha(change.getLeash(), 1.f); + } else { + // We are receiving new opening tasks, so convert to onTasksAppeared. + appearedTargets[i] = TransitionUtil.newTarget( + change, layer, info, t, mLeashMap); + // reparent into the original `mInfo` since that's where we are animating. + final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); + t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash()); + t.setLayer(appearedTargets[i].leash, layer); + mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash)); + } + } + didMergeThings = true; + mState = STATE_NEW_TASK; + } + 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; + } + // At this point, we are accepting the merge. + t.apply(); + // not using the incoming anim-only surfaces + info.releaseAnimSurfaces(); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + if (appearedTargets == null) return; + try { + mListener.onTasksAppeared(appearedTargets); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending appeared tasks to recents animation", e); + } + } + + /** 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 { + return ActivityTaskManager.getService().takeTaskSnapshot(taskId); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to screenshot task", e); + } + return null; + } + + @Override + public void setInputConsumerEnabled(boolean enabled) { + mExecutor.execute(() -> { + if (mFinishCB == null || !enabled) return; + // transient launches don't receive focus automatically. Since we are taking over + // the gesture now, take focus explicitly. + // This also moves recents back to top if the user gestured before a switch + // animation finished. + try { + ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set focused task", e); + } + }); + } + + @Override + public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { + } + + @Override + public void setFinishTaskTransaction(int taskId, + PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { + mExecutor.execute(() -> { + if (mFinishCB == null) return; + mPipTransaction = finishTransaction; + }); + } + + @Override + @SuppressLint("NewApi") + public void finish(boolean toHome, boolean sendUserLeaveHint) { + mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint)); + } + + private void finishInner(boolean toHome, boolean sendUserLeaveHint) { + if (mFinishCB == null) { + Slog.e(TAG, "Duplicate call to finish"); + return; + } + final Transitions.TransitionFinishCallback finishCB = mFinishCB; + mFinishCB = null; + + final SurfaceControl.Transaction t = mFinishTransaction; + final WindowContainerTransaction wct = new WindowContainerTransaction(); + + if (mKeyguardLocked && mRecentsTask != null) { + if (toHome) wct.reorder(mRecentsTask, true /* toTop */); + else wct.restoreTransientOrder(mRecentsTask); + } + if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { + // The gesture is returning to the pausing-task(s) rather than continuing with + // recents, so end the transition by moving the app back to the top (and also + // re-showing it's task). + for (int i = mPausingTasks.size() - 1; i >= 0; --i) { + // reverse order so that index 0 ends up on top + wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */); + t.show(mPausingTasks.get(i).mTaskSurface); + } + if (!mKeyguardLocked && mRecentsTask != null) { + wct.restoreTransientOrder(mRecentsTask); + } + } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) { + // Special situation where 3p launcher was changed during recents (this happens + // during tapltests...). Here we get both "return to home" AND "home opening". + // This is basically going home, but we have to restore the recents and home order. + for (int i = 0; i < mOpeningTasks.size(); ++i) { + final TaskState state = mOpeningTasks.get(i); + if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) { + // Make sure it is on top. + wct.reorder(state.mToken, true /* onTop */); + } + t.show(state.mTaskSurface); + } + for (int i = mPausingTasks.size() - 1; i >= 0; --i) { + t.hide(mPausingTasks.get(i).mTaskSurface); + } + if (!mKeyguardLocked && mRecentsTask != null) { + wct.restoreTransientOrder(mRecentsTask); + } + } else { + // The general case: committing to recents, going home, or switching tasks. + for (int i = 0; i < mOpeningTasks.size(); ++i) { + t.show(mOpeningTasks.get(i).mTaskSurface); + } + for (int i = 0; i < mPausingTasks.size(); ++i) { + if (!sendUserLeaveHint) { + // This means recents is not *actually* finishing, so of course we gotta + // do special stuff in WMCore to accommodate. + wct.setDoNotPip(mPausingTasks.get(i).mToken); + } + // Since we will reparent out of the leashes, pre-emptively hide the child + // surface to match the leash. Otherwise, there will be a flicker before the + // visibility gets committed in Core when using split-screen (in splitscreen, + // the leaf-tasks are not "independent" so aren't hidden by normal setup). + t.hide(mPausingTasks.get(i).mTaskSurface); + } + if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) { + t.show(mInfo.getChange(mPipTask).getLeash()); + PictureInPictureSurfaceTransaction.apply(mPipTransaction, + mInfo.getChange(mPipTask).getLeash(), t); + mPipTask = null; + mPipTransaction = null; + } + } + cleanUp(); + finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */); + } + + @Override + public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { + } + + @Override + public void cleanupScreenshot() { + } + + @Override + public void setWillFinishToHome(boolean willFinishToHome) { + mExecutor.execute(() -> { + mWillFinishToHome = willFinishToHome; + }); + } + + /** + * @see IRecentsAnimationController#removeTask + */ + @Override + public boolean removeTask(int taskId) { + return false; + } + + /** + * @see IRecentsAnimationController#detachNavigationBarFromApp + */ + @Override + public void detachNavigationBarFromApp(boolean moveHomeToTop) { + mExecutor.execute(() -> { + if (mTransition == null) return; + try { + ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to detach the navigation bar from app", e); + } + }); + } + + /** + * @see IRecentsAnimationController#animateNavigationBarToApp(long) + */ + @Override + public void animateNavigationBarToApp(long duration) { + } + }; + + /** Utility class to track the state of a task as-seen by recents. */ + private static class TaskState { + WindowContainerToken mToken; + ActivityManager.RunningTaskInfo mTaskInfo; + + /** The surface/leash of the task provided by Core. */ + SurfaceControl mTaskSurface; + + /** The (local) animation-leash created for this task. */ + SurfaceControl mLeash; + + TaskState(TransitionInfo.Change change, SurfaceControl leash) { + mToken = change.getContainer(); + mTaskInfo = change.getTaskInfo(); + mTaskSurface = change.getLeash(); + mLeash = leash; + } + + static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) { + for (int i = list.size() - 1; i >= 0; --i) { + if (list.get(i).mToken.equals(change.getContainer())) { + return i; + } + } + return -1; + } + + public String toString() { + return "" + mToken + " : " + mLeash; + } + } + + /** + * An interface for a mixed handler to receive information about recents requests (since these + * come into this handler directly vs from WMCore request). + */ + public interface RecentsMixedHandler { + /** + * Called when a recents request comes in. The handler can add operations to outWCT. If + * the handler wants to "accept" the transition, it should return itself; otherwise, it + * should return `null`. + * + * If a mixed-handler accepts this recents, it will be the de-facto handler for this + * transition and is required to call the associated {@link #startAnimation}, + * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods. + */ + Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT); + + /** + * Reports the transition token associated with the accepted recents request. If there was + * a problem starting the request, this will be called with `null`. + */ + void setRecentsTransition(@Nullable IBinder transition); + } +} 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 def945e53f9b..33cbdac67061 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 @@ -1335,7 +1335,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 a841b7f96d3c..d6f4d6daaa83 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 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { 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 aa851d1bd30f..586cab07c508 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 @@ -43,6 +43,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -55,10 +56,12 @@ import java.util.Optional; * A handler for dealing with transitions involving multiple other handlers. For example: an * activity in split-screen going into PiP. */ -public class DefaultMixedHandler implements Transitions.TransitionHandler { +public class DefaultMixedHandler implements Transitions.TransitionHandler, + RecentsTransitionHandler.RecentsMixedHandler { private final Transitions mPlayer; private PipTransitionController mPipHandler; + private RecentsTransitionHandler mRecentsHandler; private StageCoordinator mSplitHandler; private static class MixedTransition { @@ -122,7 +125,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional) { + Optional<PipTouchHandler> pipTouchHandlerOptional, + Optional<RecentsTransitionHandler> recentsHandlerOptional) { mPlayer = player; if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent() && splitScreenControllerOptional.isPresent()) { @@ -134,6 +138,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { if (mSplitHandler != null) { mSplitHandler.setMixedHandler(this); } + mRecentsHandler = recentsHandlerOptional.orElse(null); + if (mRecentsHandler != null) { + mRecentsHandler.addMixer(this); + } }, this); } } @@ -200,6 +208,29 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return null; } + @Override + public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) { + if (mRecentsHandler != null && mSplitHandler.isSplitActive()) { + return this; + } + return null; + } + + @Override + public void setRecentsTransition(IBinder transition) { + if (mSplitHandler.isSplitActive()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + + "Split-Screen is active, so treat it as Mixed."); + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); + mixed.mLeftoversHandler = mRecentsHandler; + mActiveTransitions.add(mixed); + } else { + throw new IllegalStateException("Accepted a recents transition but don't know how to" + + " handle it"); + } + } + private TransitionInfo subCopy(@NonNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges) { final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); @@ -561,6 +592,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mPipHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { + mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } } } 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 e643170273de..e632b56d5e54 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.animation.ValueAnimator; 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 @@ class ScreenRotationAnimation { .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/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 6b45149e35a2..317b9a322fbc 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } decoration.relayout(taskInfo); - setupCaptionColor(taskInfo, decoration); } @Override @@ -252,7 +251,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } - 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 3c0ef965f4f5..6478fe723027 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.app.ActivityManager; 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.MotionEvent; 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.DisplayController; 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 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin 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 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandler = handler; mChoreographer = choreographer; mSyncQueue = syncQueue; - mDesktopActive = DesktopModeStatus.isActive(mContext); } @Override @@ -152,9 +152,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin 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,23 +174,27 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return; } if (oldRootView != mResult.mRootView) { - setupRootView(); + 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 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 (!mTaskInfo.isFocused) { + closeHandleMenu(); } if (!isDragResizeable) { @@ -219,34 +225,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin 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 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin 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 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin 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 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin 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 ddd3b440e1a6..31e93ac6068f 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 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> }; RunningTaskInfo mTaskInfo; + int mLayoutResId; final SurfaceControl mTaskSurface; Display mDisplay; @@ -162,6 +163,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> if (params.mRunningTaskInfo != null) { mTaskInfo = params.mRunningTaskInfo; } + final int oldLayoutResId = mLayoutResId; + mLayoutResId = params.mLayoutResId; if (!mTaskInfo.isVisible) { releaseViews(); @@ -178,7 +181,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 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 000000000000..95b5051cb81d --- /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 000000000000..47a12a0cb71c --- /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 000000000000..514ea52cb8ae --- /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/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index 43f8f7b074bf..63de74fa3b05 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.clearInvocations; 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 @@ public class DesktopModeControllerTest extends ShellTestCase { 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 95e78a8b7bcc..5cad50da7e42 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 androidx.test.filters.SmallTest 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 @@ class DesktopTasksControllerTest : ShellTestCase() { @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 @@ class DesktopTasksControllerTest : ShellTestCase() { context, shellInit, shellController, + displayController, shellTaskOrganizer, + syncQueue, + rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, TestShellExecutor() diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h index 41ced8cebf83..139cdde5c0d2 100644 --- a/libs/hwui/MemoryPolicy.h +++ b/libs/hwui/MemoryPolicy.h @@ -54,6 +54,7 @@ struct MemoryPolicy { // 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 28d78b4474f7..914266de2753 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -579,17 +579,9 @@ jobject GraphicsJNI::getColorSpace(JNIEnv* env, SkColorSpace* decodeColorSpace, 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 @@ int register_android_graphics_Graphics(JNIEnv* env) 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/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_device_other.xml new file mode 100644 index 000000000000..aafe46681336 --- /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 97d201d625c8..190e0a8020a6 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 161186155340..78120a3db999 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 000000000000..f1fda1772874 --- /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 15f6987bb551..dc12d12b49ad 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 90655203fe93..0baf7ef7917f 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 d7a28d949997..0f6b21f6767c 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 d1d2c70134c6..d470d4c7b3f6 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 ac5294ad373a..79f04b92aecc 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 99b776cb939f..71ae578ec310 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.CompanionDeviceResources.TITLES 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.CompanionDeviceManager; 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 @@ public class CompanionDeviceActivity extends FragmentActivity implements 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 @@ public class CompanionDeviceActivity extends FragmentActivity implements 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 328c67ebb216..d8348d1c9218 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 @@ class DeviceListAdapter extends RecyclerView.Adapter<DeviceListAdapter.ViewHolde 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 fceca91ce617..8c14f8037e64 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.Context; 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 @@ class Utils { } } + 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/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index e8e39741c3bd..5d72424c8f8a 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.common.StartBalIntentSenderForResultContrac 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 @@ class CredentialSelectorActivity : ComponentActivity() { 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 bcf692fceacc..7b98049b51c0 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 @@ fun PrimaryCreateOptionRow( 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 000000000000..8b95b5e46aa1 --- /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 263a632ef5ee..7a8679038579 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 @@ internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean { !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/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle index 2071489b31ad..4fd2b5d5fbe1 100644 --- a/packages/SettingsLib/Spa/build.gradle +++ b/packages/SettingsLib/Spa/build.gradle @@ -24,7 +24,7 @@ buildscript { } } 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 c3d5431743d1..ed85e33ca8a6 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 4563b7d75b22..9962c935f655 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle +++ b/packages/SettingsLib/Spa/spa/build.gradle @@ -79,7 +79,7 @@ dependencies { 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/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index f741f655bf7c..7b4c86207a2a 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 class CachedBluetoothDeviceManager { 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 000000000000..d8475b3f22af --- /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 000000000000..c9512cd01aa3 --- /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 ebfec0aee1b7..4354e0c6e952 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 @@ package com.android.settingslib.bluetooth; 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 @@ public class HearingAidDeviceManager { 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 @@ public class HearingAidDeviceManager { 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 @@ public class HearingAidDeviceManager { // 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 @@ public class HearingAidDeviceManager { // 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 a3c2e70c7da4..43e3a32c97b6 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 @@ public class LocalBluetoothProfileManager { 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 071ab27f60b9..a9d15f3b4afe 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 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { @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 f06623d0bd4a..4b3820eb0444 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 @@ public class CachedBluetoothDeviceManagerTest { */ @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 000000000000..8b5ea30e17fe --- /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 470d8e07f1f8..a83913693458 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 @@ package com.android.settingslib.bluetooth; 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.BluetoothDevice; 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 @@ public class HearingAidDeviceManagerTest { 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 @@ public class HearingAidDeviceManagerTest { @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 @@ public class HearingAidDeviceManagerTest { @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 @@ public class HearingAidDeviceManagerTest { 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 @@ public class HearingAidDeviceManagerTest { } @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/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 8b38debef942..941697b2392f 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -376,7 +376,7 @@ android_app { 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 @@ soong_config_module_type { } systemui_optimized_java_defaults { - name: "SystemUI_app_defaults", + name: "SystemUI_optimized_defaults", soong_config_variables: { SYSTEMUI_OPTIMIZE_JAVA: { optimize: { @@ -452,12 +452,10 @@ systemui_optimized_java_defaults { 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 @@ android_app { name: "SystemUI", defaults: [ "platform_app_defaults", - "SystemUI_app_defaults", + "SystemUI_optimized_defaults", ], static_libs: [ "SystemUI-core", @@ -483,6 +481,9 @@ android_app { 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 650d5fabeb85..9b3f1a8f0734 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/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 4f7d09963fc6..4d6c2022c3b8 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -321,7 +321,8 @@ asked for it --> <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/values/config.xml b/packages/SystemUI/res/values/config.xml index 082f3855affc..160218929773 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 ff86c595b19b..412e70f0df06 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/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f2f0c597b86c..085f202861bb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -480,6 +480,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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; } diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 873a695ecd93..64a9cc995248 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 android.view.accessibility.AccessibilityEvent; 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.FalsingManager; 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 @@ public class SwipeHelper implements Gefingerpoken { 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 @@ public class SwipeHelper implements Gefingerpoken { 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 @@ public class SwipeHelper implements Gefingerpoken { 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; - } + protected Animator getViewTranslationAnimator(View view, float target, + AnimatorUpdateListener listener) { + + cancelSnapbackAnimation(view); - private float getPerpendicularVelocity(VelocityTracker vt) { - return mSwipeDirection == X ? vt.getYVelocity() : - vt.getXVelocity(); + if (view instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) view).getTranslateViewAnimator(target, listener); + } + + return createTranslationAnimation(view, target, listener); } - protected Animator getViewTranslationAnimator(View v, float target, + protected Animator createTranslationAnimation(View view, float newPos, AnimatorUpdateListener listener) { - ObjectAnimator anim = createTranslationAnimation(v, target); + 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 @@ public class SwipeHelper implements Gefingerpoken { mTouchedView = mCallback.getChildAtPosition(ev); if (mTouchedView != null) { + cancelSnapbackAnimation(mTouchedView); onDownUpdate(mTouchedView, ev); mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mTouchedView); mVelocityTracker.addMovement(ev); @@ -526,47 +538,59 @@ public class SwipeHelper implements Gefingerpoken { } /** - * 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; - } + PhysicsAnimator<? extends View> anim = + createSnapBackAnimation(animView, targetLeft, velocity); + anim.addUpdateListener((target, values) -> { + onTranslationUpdate(target, getTranslation(target), canBeDismissed); + }); + anim.addEndListener((t, p, wasFling, cancelled, finalValue, finalVelocity, allEnded) -> { + mSnappingChild = false; - @Override - public void onAnimationEnd(Animator animator) { - mSnappingChild = false; - if (!wasCancelled) { - updateSwipeProgressFromOffset(animView, canBeDismissed); - resetSwipeState(); - } - onSnapChildWithAnimationFinished(); + if (!cancelled) { + updateSwipeProgressFromOffset(animView, canBeDismissed); + resetSwipeState(); } + onChildSnappedBack(animView, targetLeft); }); - prepareSnapBackAnimation(animView, anim); 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 @@ public class SwipeHelper implements Gefingerpoken { } /** - * 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 46e945ba6880..3f22f18f6fa7 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 @@ constructor( } } - 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 84094626193d..b0071340cf1a 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 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at 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 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } 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 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at dwellRadius = sensorRadius * 1.5f } - fun setAlphaInDuration(duration: Long) { - alphaInDuration = duration - } - /** * Animate dwell ripple inwards back to radius 0 */ @@ -253,7 +248,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at override fun onAnimationEnd(animation: Animator?) { drawDwell = false - resetRippleAlpha() } }) start() @@ -277,22 +271,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } } - 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 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at 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/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt deleted file mode 100644 index 079c0b3f6966..000000000000 --- 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 28ca41d166a8..000000000000 --- 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 fca4cf9bf082..e3dbcb523258 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 @@ private const val MAJOR = 10F */ @SysUISingleton class UdfpsShell @Inject constructor( - commandRegistry: CommandRegistry, - private val udfpsOverlay: UdfpsOverlay + commandRegistry: CommandRegistry ) : Command { /** @@ -69,10 +68,6 @@ class UdfpsShell @Inject constructor( 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 @@ class UdfpsShell @Inject constructor( ) } - 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 653c12ef955e..25b1e3a8eed2 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 android.widget.TextView; 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 @@ public class BroadcastDialog extends SystemUIDialog { 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 @@ public class BroadcastDialog extends SystemUIDialog { 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 @@ public class BroadcastDialog extends SystemUIDialog { 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 @@ public class BroadcastDialog extends SystemUIDialog { } @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 @@ public class BroadcastDialog extends SystemUIDialog { } } + 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 1b699e8ea5dd..17bf1a716944 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 @@ public class BroadcastDialogController { 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 class BroadcastDialogController { 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 0ed7d2711c62..e9daa462c022 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 @@ class ClipboardToast extends Toast.Callback { } 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/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index b86d419f540f..9bf6b2a5b42b 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.SliceBroadcastRelayHandler 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 @@ abstract class SystemUICoreStartableModule { @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 fc3263fd3d11..f0aefb5bc0df 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 @@ public class DozeMachine { } 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/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index b7f6a70ecad8..779098656ce3 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.internal.annotations.VisibleForTesting; 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 @@ public class DreamOverlayStateController implements 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 @@ public class DreamOverlayStateController implements 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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index a9dd260be67f..4dd3a4efca87 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -226,7 +226,7 @@ object Flags { /** 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 @@ -251,20 +251,12 @@ object Flags { // 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 @@ -415,7 +407,7 @@ object Flags { @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") @@ -426,6 +418,11 @@ object Flags { 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 @@ -517,7 +514,7 @@ object Flags { @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 diff --git a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt index b49d60dbdde1..dc0de2cef349 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt @@ -39,15 +39,15 @@ constructor( ) { 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) { - storeSleepState(isDreaming) + override fun onDozingChanged(isDozing: Boolean) { + storeSleepState(isDozing) } } @@ -67,9 +67,13 @@ constructor( fun maybeRestartSleep() { bgExecutor.executeDelayed( { - if (settings.getBool(RESTART_NAP_KEY, false)) { + if (settings.getBool(RESTART_SLEEP_KEY, false)) { Log.d("RestartDozeListener", "Restarting sleep state") - powerManager.wakeUp(systemClock.uptimeMillis()) + powerManager.wakeUp( + systemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "RestartDozeListener" + ) powerManager.goToSleep(systemClock.uptimeMillis()) } }, @@ -78,6 +82,6 @@ constructor( } private fun storeSleepState(sleeping: Boolean) { - bgExecutor.execute { settings.putBool(RESTART_NAP_KEY, sleeping) } + 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 3e52ff2c2da0..9ab2e9922531 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.animation.ValueAnimator 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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 @@ class KeyguardUnlockAnimationController @Inject constructor( 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/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index b10aa90e6d7f..e65c8a16f9e9 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 @@ constructor( /** 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 b23247c30256..df93d235245c 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 @@ constructor( ) .map { if (willRunDismissFromKeyguard) { - ScrimAlpha( - notificationsAlpha = 1f, - ) + ScrimAlpha() } else if (leaveShadeOpen) { ScrimAlpha( behindAlpha = 1f, 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 9c7b48d2514d..f08b97695202 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 @@ constructor( 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 15d999ad2fe8..cb1f12cf412f 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.Intent; 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.Process; 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { // 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 @@ public class MediaControlPanel { 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 7a1302c5baa2..cd51d92062bc 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 @@ constructor( /** 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 @@ constructor( ) } } + 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 b71a91934314..9928c4f79a96 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateDeviceStatusIcon(deviceStatusIcon); mStatusIcon.setVisibility(View.VISIBLE); } - updateTwoLineLayoutContentAlpha( + updateSingleLineLayoutContentAlpha( updateClickActionBasedOnSelectionBehavior(device) ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); } else { @@ -368,6 +364,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { 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 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { @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 f76f049abf97..f92a5abdbf23 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 @@ public abstract class MediaOutputBaseAdapter extends 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 @@ public abstract class MediaOutputBaseAdapter extends } 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 2aedd3634626..f3f17d1c7144 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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, 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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, 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 760a42cc512c..132bf99c3f62 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 @@ class MediaOutputDialogReceiver @Inject constructor( 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 253c3c713485..be5d60799f79 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 @@ import android.widget.SeekBar; */ 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 @@ public class MediaOutputSeekbar extends SeekBar { } static int scaleVolumeToProgress(int volume) { - return volume * SCALE_SIZE; + return volume == 0 ? 0 : INITIAL_PROGRESS + volume * SCALE_SIZE; } int getVolume() { @@ -46,7 +47,7 @@ public class MediaOutputSeekbar extends SeekBar { } 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/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt index 9bccb7df4ed0..89f66b7daaf8 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 @@ package com.android.systemui.mediaprojection.appselector.view 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 @@ constructor( 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/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 8a5bac8858b4..44c718f26a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -159,6 +159,8 @@ import com.android.systemui.util.ViewController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.Locale; import java.util.Map; @@ -167,8 +169,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; - /** * Contains logic for a navigation bar view. */ @@ -245,12 +245,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private boolean mTransientShown; private boolean mTransientShownFromGestureOnSystemBar; - /** - * This is to indicate whether the navigation bar button is forced visible. This is true - * when the setup wizard is on display. When that happens, the window frame should be provided - * as insets size directly. - */ - private boolean mIsButtonForceVisible; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; private LightBarController mLightBarController; private final LightBarController mMainLightBarController; @@ -679,8 +673,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.setTouchHandler(mTouchHandler); setNavBarMode(mNavBarMode); mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates); - mEdgeBackGestureHandler.setButtonForceVisibleChangeCallback((forceVisible) -> { - mIsButtonForceVisible = forceVisible; + mEdgeBackGestureHandler.setButtonForcedVisibleChangeCallback((forceVisible) -> { repositionNavigationBar(mCurrentRotation); }); mNavigationBarTransitions.addListener(this::onBarTransition); @@ -1721,7 +1714,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] { new InsetsFrameProvider.InsetsSizeOverride( TYPE_INPUT_METHOD, null)}); - if (insetsHeight != -1 && !mIsButtonForceVisible) { + if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) { navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight)); } 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 e7bb6dcab30f..0d5a3fd0854d 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_ACTIVE_AFTER_INACTIVE_ANIMATION = 130L 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 @@ class BackPanelController internal constructor( 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 @@ class BackPanelController internal constructor( mView.cancelAnimations() mainHandler.removeCallbacks(onEndSetCommittedStateListener.runnable) mainHandler.removeCallbacks(onEndSetGoneStateListener.runnable) - mainHandler.removeCallbacks(playAnimationThenSetGoneOnAlphaEnd) + mainHandler.removeCallbacks(onAlphaEndSetGoneStateListener.runnable) } /** @@ -509,7 +516,7 @@ class BackPanelController internal constructor( 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 @@ class BackPanelController internal constructor( * 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 @@ class BackPanelController internal constructor( 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 @@ class BackPanelController internal constructor( 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 @@ class BackPanelController internal constructor( 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 cfcc6713eca9..498d5c0dedb4 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 @@ package com.android.systemui.navigationbar.gestural; 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; @@ -177,7 +177,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final OverviewProxyService mOverviewProxyService; private final SysUiState mSysUiState; private Runnable mStateChangeCallback; - private Consumer<Boolean> mButtonForceVisibleCallback; + private Consumer<Boolean> mButtonForcedVisibleCallback; private final PluginManager mPluginManager; private final ProtoTracer mProtoTracer; @@ -244,8 +244,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private boolean mIsBackGestureAllowed; private boolean mGestureBlockingActivityRunning; private boolean mIsNewBackAffordanceEnabled; - private boolean mIsTrackpadGestureBackEnabled; - private boolean mIsButtonForceVisible; + private boolean mIsTrackpadGestureFeaturesEnabled; + private boolean mIsButtonForcedVisible; private InputMonitor mInputMonitor; private InputChannelCompat.InputEventReceiver mInputEventReceiver; @@ -412,8 +412,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack mStateChangeCallback = callback; } - public void setButtonForceVisibleChangeCallback(Consumer<Boolean> callback) { - mButtonForceVisibleCallback = callback; + public void setButtonForcedVisibleChangeCallback(Consumer<Boolean> callback) { + mButtonForcedVisibleCallback = callback; } public int getEdgeWidthLeft() { @@ -428,13 +428,14 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Resources res = mNavigationModeController.getCurrentUserContext().getResources(); mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); - final boolean previousForceVisible = mIsButtonForceVisible; - mIsButtonForceVisible = + final boolean previousForcedVisible = mIsButtonForcedVisible; + mIsButtonForcedVisible = mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); - if (previousForceVisible != mIsButtonForceVisible && mButtonForceVisibleCallback != null) { - mButtonForceVisibleCallback.accept(mIsButtonForceVisible); + if (previousForcedVisible != mIsButtonForcedVisible + && mButtonForcedVisibleCallback != null) { + mButtonForcedVisibleCallback.accept(mIsButtonForcedVisible); } - mIsBackGestureAllowed = !mIsButtonForceVisible; + mIsBackGestureAllowed = !mIsButtonForcedVisible; final DisplayMetrics dm = res.getDisplayMetrics(); final float defaultGestureHeight = res.getDimension( @@ -590,7 +591,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // 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( @@ -635,6 +636,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack return mIsEnabled && mIsBackGestureAllowed; } + public boolean isButtonForcedVisible() { + return mIsButtonForcedVisible; + } + /** * Update the PiP bounds, used for exclusion calculation. */ @@ -883,7 +888,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } 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 3dc6d2f8464d..35b6c15d92f7 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 @@ package com.android.systemui.navigationbar.gestural 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 1345c9bdfeb3..9e2b6d3cd898 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 @@ import android.view.MotionEvent; 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 000000000000..5d03218406d0 --- /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 58ac5b30972f..93ed8591e738 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 android.util.Log 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 @@ class NoteTaskController @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,7 +140,7 @@ constructor( 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) { @@ -182,30 +189,71 @@ constructor( 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 8ecf08192e29..616f9b526156 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 @@ constructor( 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 fb3c0cb54f84..04ed08b6fc20 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 @@ internal class NoteTaskInitializer @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 @@ constructor( } 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 000000000000..441b9f5d0181 --- /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 5c59532e0c2e..0cfb0a521820 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 @@ class CreateNoteTaskShortcutActivity @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/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index df1c8dfdde96..08fe2709b810 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 @@ package com.android.systemui.qs.tiles; 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.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BluetoothController; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -60,8 +63,14 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { 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 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { statusBarStateController, activityStarter, qsLogger); mController = bluetoothController; mController.observe(getLifecycle(), mCallback); + mExecutor = new HandlerExecutor(mainHandler); } @Override @@ -117,6 +127,15 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } @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 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { 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 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { 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 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { 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 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { 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 aa2ea0b6cf3e..75d01723667d 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 android.widget.Switch; 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 @@ public class InternetTile extends QSTileImpl<SignalState> { @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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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 @@ public class InternetTile extends QSTileImpl<SignalState> { } 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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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 @@ public class InternetTile extends QSTileImpl<SignalState> { // 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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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 @@ public class InternetTile extends QSTileImpl<SignalState> { + "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 @@ public class InternetTile extends QSTileImpl<SignalState> { 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 @@ public class InternetTile extends QSTileImpl<SignalState> { + "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 @@ public class InternetTile extends QSTileImpl<SignalState> { // 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/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt index 48aa60f69cdb..253f07de7b3c 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 @@ constructor( // 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 236213cb023f..798c4908e467 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 @@ package com.android.systemui.screenshot 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 @@ constructor( 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 3360511617ba..827a4c12006b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -979,6 +979,7 @@ public final class NotificationPanelViewController implements Dumpable { onTrackingStopped(false); instantCollapse(); } else { + mView.animate().cancel(); mView.animate() .alpha(0f) .setStartDelay(0) @@ -3149,7 +3150,9 @@ public final class NotificationPanelViewController implements Dumpable { */ 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) @@ -3168,9 +3171,14 @@ public final class NotificationPanelViewController implements Dumpable { @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. */ @@ -3369,6 +3377,7 @@ public final class NotificationPanelViewController implements Dumpable { } 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); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index 5adb58bca886..63179dac7b8c 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 class CrossFadeHelper { */ 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 779be2b102a6..fda227795915 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 android.view.View.VISIBLE; 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.keyguard.domain.interactor.AlternateBouncerInteracto 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 @@ public class KeyguardIndicationController { 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 @@ public class KeyguardIndicationController { FaceHelpMessageDeferral faceHelpMessageDeferral, KeyguardLogger keyguardLogger, AlternateBouncerInteractor alternateBouncerInteractor, - AlarmManager alarmManager + AlarmManager alarmManager, + UserTracker userTracker ) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; @@ -275,6 +277,7 @@ public class KeyguardIndicationController { mKeyguardLogger = keyguardLogger; mScreenLifecycle.addObserver(mScreenObserver); mAlternateBouncerInteractor = alternateBouncerInteractor; + mUserTracker = userTracker; mFaceAcquiredMessageDeferral = faceHelpMessageDeferral; mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>(); @@ -475,6 +478,10 @@ public class KeyguardIndicationController { } } + 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 @@ public class KeyguardIndicationController { 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 @@ public class KeyguardIndicationController { 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/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index a529da54fc4e..a38f52727c26 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.FloatProperty; 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView }; 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return object.getTranslation(); } }; + private OnClickListener mOnClickListener; private OnDragSuccessListener mOnDragSuccessListener; private boolean mHeadsupDisappearRunning; @@ -2177,6 +2177,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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 c6f56d482d43..b476b683463f 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 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc @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 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc 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 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc } } + @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/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index f866d65e2d2d..d0c6215a55d8 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 @@ class MobileConnectionRepositoryImpl( * 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/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 2ee52325ca4a..654ba04eba7a 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.ArrayList; 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 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC 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 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Nullable private String generateTimeRemainingString() { - synchronized (mFetchCallbacks) { + synchronized (mEstimateLock) { if (mEstimate == null) { return null; } @@ -340,7 +345,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC 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 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } @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 0c5b8515071d..3429e25abfc7 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 @@ public interface BluetoothController extends CallbackController<Callback>, Dumpa 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 acdf0d2bc32b..c804fe76d882 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.ArrayList; 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 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private final H mHandler; private int mState; + private final BluetoothAdapter mAdapter; /** */ @Inject @@ -88,7 +90,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa 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 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mCurrentUser = userTracker.getUserId(); mDumpManager.registerDumpable(TAG, this); + mAdapter = bluetoothAdapter; } @Override @@ -412,6 +416,30 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa 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/wallet/controller/IWalletCardsUpdatedListener.aidl b/packages/SystemUI/src/com/android/systemui/wallet/controller/IWalletCardsUpdatedListener.aidl new file mode 100644 index 000000000000..aa7ef57ea30c --- /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 000000000000..eebbdfd06f7c --- /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 2ef3511a0cce..ce2d15f97cd8 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 0369d5b32883..ec6c42114693 100644 --- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java +++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java @@ -59,7 +59,7 @@ public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestC 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 @@ public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestC 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 @@ public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestC // 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/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt new file mode 100644 index 000000000000..6ddba0b4719c --- /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/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt index 5c0924011259..c2a129be66a4 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 @@ class UdfpsShellTest : SysuiTestCase() { 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 @@ class UdfpsShellTest : SysuiTestCase() { 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 9d16185e75c5..6fa19162b150 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 @@ package com.android.systemui.bluetooth; 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 android.widget.TextView; 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 @@ public class BroadcastDialogTest extends SysuiTestCase { 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 @@ public class BroadcastDialogTest extends SysuiTestCase { @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 @@ public class BroadcastDialogTest extends SysuiTestCase { 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/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index a636b7f43648..b87647ef9839 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 @@ public class DozeMachineTest extends SysuiTestCase { } @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 0f25764ab2c1..b88dbe6be856 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 androidx.test.filters.SmallTest; 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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { @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 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { 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/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt index 2db4596c80a7..e287f19b2455 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt @@ -21,12 +21,15 @@ 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 @@ -58,36 +61,37 @@ class RestartDozeListenerTest : SysuiTestCase() { } @Test - fun testStoreDreamState_onDreamingStarted() { - listener.onDreamingChanged(true) + fun testStoreDreamState_onDozingStarted() { + listener.onDozingChanged(true) executor.runAllReady() - assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isTrue() + assertThat(settings.getBool(RestartDozeListener.RESTART_SLEEP_KEY)).isTrue() } @Test - fun testStoreDreamState_onDreamingStopped() { - listener.onDreamingChanged(false) + fun testStoreDozeState_onDozingStopped() { + listener.onDozingChanged(false) executor.runAllReady() - assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isFalse() + 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() executor.advanceClockToLast() executor.runAllReady() - verify(powerManager).wakeUp(clock.uptimeMillis()) + 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() executor.advanceClockToLast() executor.runAllReady() - verify(powerManager, never()).wakeUp(anyLong()) + 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/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt index 5ec6283f3de0..bdc33f45c717 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.plugins.ActivityStarter 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.Mock 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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { 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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { } @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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @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 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { 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 746f66881a88..98794fd4de0a 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 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { 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 55b57f170774..543875dc31cf 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { .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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 3d5dba3b9efc..e2cf87a6dd62 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt index 464acb68fb07..01ffdcd580c0 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 @@ package com.android.systemui.mediaprojection.appselector.view 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 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() { } 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/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 0ee52ea7838a..e64094675ff5 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_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.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 @@ -47,6 +59,7 @@ 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 @@ -63,15 +76,17 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @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) @@ -82,6 +97,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { ) ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE) + whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle)) + .thenReturn(listOf(NOTES_PACKAGE_NAME)) } private fun createNoteTaskController( @@ -98,6 +115,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { isEnabled = isEnabled, devicePolicyManager = devicePolicyManager, userTracker = userTracker, + roleManager = roleManager, + shortcutManager = shortcutManager, ) // region onBubbleExpandChanged @@ -132,7 +151,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() { + fun onBubbleExpandChanged_expandingAndKeyguardLocked_shouldDoNothing() { val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true) createNoteTaskController() @@ -146,7 +165,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() { + fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_shouldDoNothing() { val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true) createNoteTaskController() @@ -268,8 +287,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { verifyZeroInteractions(context) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), - isNull()) + 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) @@ -333,11 +352,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() { 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 @@ -348,11 +367,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() { 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 @@ -403,8 +422,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), - isNull()) + 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) @@ -427,8 +446,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), - isNull()) + 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) @@ -438,7 +457,78 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } // 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 46e02788b2df..cd67e8d0a4c2 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 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { 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 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { 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 75fd0000e0e1..2e77de270c65 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 android.testing.TestableLooper.RunWithLooper 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.QSHost 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 @@ import org.mockito.MockitoAnnotations @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 @@ class BluetoothTileTest : SysuiTestCase() { 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 @@ class BluetoothTileTest : SysuiTestCase() { 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 @@ class BluetoothTileTest : SysuiTestCase() { 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 @@ class BluetoothTileTest : SysuiTestCase() { 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 @@ class BluetoothTileTest : SysuiTestCase() { 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 @@ class BluetoothTileTest : SysuiTestCase() { 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 @@ class BluetoothTileTest : SysuiTestCase() { } 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 1f18d91aa133..08b5d2bdc31c 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 @@ package com.android.systemui.screenshot 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 junit.framework.Assert.assertTrue 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 @@ class ScreenshotDetectionControllerTest { 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 3440f91c1237..31f7771bb939 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 com.android.systemui.util.FakeSharedPreferences; 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 @@ import kotlin.Unit; @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 @@ public class WorkProfileMessageControllerTest extends SysuiTestCase { 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 @@ public class WorkProfileMessageControllerTest extends SysuiTestCase { @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 @@ public class WorkProfileMessageControllerTest extends SysuiTestCase { 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 @@ public class WorkProfileMessageControllerTest extends SysuiTestCase { 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 99cf8d0ebe93..7087c0135998 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 com.google.common.truth.Truth.assertThat; 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { .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 9a2e415f952f..d36cc7e0ddbe 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.times; 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 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @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/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 251acedcc66e..569f90b64609 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.ScreenLifecycle; 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private AuthController mAuthController; @Mock private AlarmManager mAlarmManager; + @Mock + private UserTracker mUserTracker; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @Captor @@ -209,6 +212,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { .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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mKeyguardBypassController, mAccessibilityManager, mFaceHelpMessageDeferral, mock(KeyguardLogger.class), mAlternateBouncerInteractor, - mAlarmManager + mAlarmManager, + mUserTracker ); mController.init(); mController.setIndicationArea(mIndicationArea); @@ -813,7 +819,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // 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 class KeyguardIndicationControllerTest extends SysuiTestCase { 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 class KeyguardIndicationControllerTest extends SysuiTestCase { 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 class KeyguardIndicationControllerTest extends SysuiTestCase { 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 class KeyguardIndicationControllerTest extends SysuiTestCase { 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 class KeyguardIndicationControllerTest extends SysuiTestCase { 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 class KeyguardIndicationControllerTest extends SysuiTestCase { 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // 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 class KeyguardIndicationControllerTest extends SysuiTestCase { 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 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { 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/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 78da78269ac4..824eb4aa25c2 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 class NotificationSwipeHelperTest extends SysuiTestCase { 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 @@ public class NotificationSwipeHelperTest extends SysuiTestCase { 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/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 542b688b162d..934e1c64c6da 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.ServiceState.STATE_OUT_OF_SERVICE 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.model.toNetworkNameMo 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 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { 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 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { 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 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { 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 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { 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 bbf04ed28fd7..9da9ff72d380 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 @@ import org.mockito.MockitoAnnotations * * 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 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { 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 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { 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 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { 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 d07b96f6609e..cf815c27a0bf 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 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod 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 @@ object MobileTelephonyHelpers { 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 833cabbaecf4..7d64eaff0845 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.ArgumentMatchers.anyBoolean; 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.SysuiTestCase; 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 org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -60,10 +64,11 @@ public class BluetoothControllerImplTest extends SysuiTestCase { 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 @@ public class BluetoothControllerImplTest extends SysuiTestCase { 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 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mock(BluetoothLogger.class), mTestableLooper.getLooper(), mTestableLooper.getLooper(), - mMockBluetoothManager); + mMockBluetoothManager, + mMockAdapter); } @Test @@ -98,7 +105,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { 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 @@ public class BluetoothControllerImplTest extends SysuiTestCase { @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 @@ public class BluetoothControllerImplTest extends SysuiTestCase { @Test public void testOnBluetoothStateChange_updatesConnectionState() { - when(mMockAdapter.getConnectionState()).thenReturn( + when(mMockLocalAdapter.getConnectionState()).thenReturn( BluetoothAdapter.STATE_CONNECTING, BluetoothAdapter.STATE_DISCONNECTED); @@ -240,6 +248,33 @@ public class BluetoothControllerImplTest extends SysuiTestCase { 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/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 8476d0d45603..bf54d4297ad8 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.updates.screen.ScreenStatusProvider.ScreenLis 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 @@ import org.mockito.MockitoAnnotations @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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { activityTypeProvider, unfoldKeyguardVisibilityProvider, rotationChangeProvider, + context, context.mainExecutor, handler ) @@ -112,7 +119,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { override fun onUnfoldedScreenAvailable() { unfoldedScreenAvailabilityUpdates.add(Unit) } - }) + } + ) foldStateProvider.start() verify(rotationChangeProvider).addCallback(capture(rotationListener)) @@ -134,6 +142,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { // By default, we're on launcher. setupForegroundActivityType(isHomeActivity = true) + setIsLargeScreen(true) } @Test @@ -181,7 +190,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java index 6cbd175c1084..4025ade5f715 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 com.android.systemui.statusbar.policy.BluetoothController.Callback; 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 class FakeBluetoothController extends BaseLeakChecker<Callback> implement 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/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt index 2044f05664d0..380c1fcbf732 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 @@ class ScreenSizeFoldProvider(private val context: Context) : FoldProvider { } } -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/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index d653fc7beff2..a633a5e41882 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 @@ constructor( 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 @@ constructor( "lastHingeAngle: $lastHingeAngle, " + "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition" ) - Trace.setCounter( "hinge_angle", angle.toLong()) + Trace.setCounter("hinge_angle", angle.toLong()) } val currentDirection = @@ -136,6 +139,7 @@ constructor( 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 @@ constructor( // 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 @@ constructor( } 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 @@ constructor( } } + 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 d142f25c6a62..8acc5089f8bd 100644 --- a/packages/WallpaperBackup/Android.bp +++ b/packages/WallpaperBackup/Android.bp @@ -42,7 +42,7 @@ android_test { 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 @@ android_test { "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 e549b61ac491..6aca2fdc0f7f 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -19,11 +19,18 @@ package com.android.wallpaperbackup; 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 @@ public class WallpaperBackupAgent extends BackupAgent { private boolean mQuotaExceeded; private WallpaperManager mWallpaperManager; + private WallpaperEventLogger mEventLogger; + + private boolean mSystemHasLiveComponent; + private boolean mLockHasLiveComponent; @Override public void onCreate() { @@ -117,6 +128,9 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { if (systemWallpaperImageFd == null) { Slog.w(TAG, "System wallpaper doesn't exist"); + logSystemImageErrorIfNoLiveComponent(ERROR_NO_WALLPAPER); return; } @@ -210,8 +250,17 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { } 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 @@ public class WallpaperBackupAgent extends BackupAgent { // 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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 000000000000..64944b3ff54f --- /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 44ab1b6d65ba..eb1e98b90808 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 000000000000..c6fbe2bda908 --- /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 20dd516503b8..89459f6e6772 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 android.os.ParcelFileDescriptor.MODE_READ_ONLY; 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 @@ public class WallpaperBackupAgentTest { 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 @@ public class WallpaperBackupAgentTest { 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 @@ public class WallpaperBackupAgentTest { 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 @@ public class WallpaperBackupAgentTest { 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 000000000000..3816a3ccc1eb --- /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/b/Target00.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/utils/TestWallpaperService.java index 8fb4e91f790c..cb8504132e45 100644 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.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,9 +13,17 @@ * 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; +package com.android.wallpaperbackup.utils; -public class Target00 extends BaseReceiver { +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/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java index fe7b11ffb4c8..5a783f47ccdc 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 @@ public class MagnificationThumbnail { 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 @@ public class MagnificationThumbnail { 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/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 7d1de40c7150..ca743cbb1867 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_CLICKED; 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; @@ -84,6 +90,32 @@ public final class PresentationStatsEventLogger { @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 = @@ -105,6 +137,20 @@ public final class PresentationStatsEventLogger { 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; @@ -146,7 +192,7 @@ public final class PresentationStatsEventLogger { } public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { mEventInternal.ifPresent(event -> { int availableCount = getDatasetCountForAutofillId(datasetList, currentViewId); event.mAvailableCount = availableCount; @@ -155,7 +201,7 @@ public final class PresentationStatsEventLogger { } public void maybeSetCountShown(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { mEventInternal.ifPresent(event -> { int countShown = getDatasetCountForAutofillId(datasetList, currentViewId); event.mCountShown = countShown; @@ -166,7 +212,7 @@ public final class PresentationStatsEventLogger { } private static int getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { int availableCount = 0; if (datasetList != null) { for (int i = 0; i < datasetList.size(); i++) { @@ -293,6 +339,43 @@ public final class PresentationStatsEventLogger { }); } + /** + * 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 " @@ -322,7 +405,12 @@ public final class PresentationStatsEventLogger { + " mSelectedDatasetId=" + event.mSelectedDatasetId + " mDialogDismissed=" + event.mDialogDismissed + " mNegativeCtaButtonClicked=" + event.mNegativeCtaButtonClicked - + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked); + + " 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. @@ -351,11 +439,15 @@ public final class PresentationStatsEventLogger { event.mSelectedDatasetId, event.mDialogDismissed, event.mNegativeCtaButtonClicked, - event.mPositiveCtaButtonClicked); + 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; @@ -376,6 +468,10 @@ public final class PresentationStatsEventLogger { 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/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java index 742c94af3211..d6f1348a6f3d 100644 --- a/services/core/java/com/android/server/MmsServiceBroker.java +++ b/services/core/java/com/android/server/MmsServiceBroker.java @@ -343,10 +343,8 @@ public class MmsServiceBroker extends SystemService { // 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 225afea3d1b7..a60f06a60d93 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 @@ public class AccountManagerService 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 @@ public class AccountManagerService 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 c129938ea7b1..461103ee4355 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -7322,7 +7322,7 @@ public final class ActiveServices { 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); @@ -7349,7 +7349,7 @@ public final class ActiveServices { 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 */, @@ -7366,13 +7366,14 @@ public final class ActiveServices { /** * 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; @@ -7440,8 +7441,8 @@ public final class ActiveServices { } 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; } @@ -7526,7 +7527,7 @@ public final class ActiveServices { final @ReasonCode int allowWhileInUse2 = shouldAllowFgsWhileInUsePermissionLocked( clientPackageName, - clientPid, clientUid, null /* serviceRecord */, + clientPid, clientUid, null /* targetProcess */, BackgroundStartPrivileges.NONE, false); final @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked( @@ -7946,11 +7947,18 @@ public final class ActiveServices { 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 4fa28a11c0a4..82c4796a21ef 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 @@ final class ActivityManagerConstants extends ContentObserver { 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 9e5acf796025..415c859f734d 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; 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_DISABLE_TEST_API_CHECKS; 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_HIGH; 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 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * 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 */ @@ -18156,6 +18308,11 @@ public class ActivityManagerService extends IActivityManager.Stub 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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index e8b65b86aed9..2617fb74cbb3 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 @@ public final class CachedAppOptimizer { } 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); diff --git a/services/core/java/com/android/server/am/ComponentAliasResolver.java b/services/core/java/com/android/server/am/ComponentAliasResolver.java index 01735a754c83..f9eaf0229b85 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.PackageManagerInternal; 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.content.PackageMonitor; 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.Objects; 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 @@ public class ComponentAliasResolver { } }; - 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 @@ public class ComponentAliasResolver { 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 @@ public class ComponentAliasResolver { 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/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java index e38e8c1428ee..e39ac2b08479 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 @@ public final class UidRecord { } @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 4e687f4929cf..16bf355f0634 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; 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.KeyEvent; 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 @@ public class AudioService extends IAudioService.Stub 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; @@ -1219,17 +1222,6 @@ public class AudioService extends IAudioService.Stub 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); @@ -1314,6 +1306,18 @@ public class AudioService extends IAudioService.Stub * 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 @@ -1349,8 +1353,19 @@ public class AudioService extends IAudioService.Stub // 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 @@ -1388,6 +1403,13 @@ public class AudioService extends IAudioService.Stub 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() { @@ -3665,7 +3687,7 @@ public class AudioService extends IAudioService.Stub 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); @@ -9237,6 +9259,10 @@ public class AudioService extends IAudioService.Stub onLowerVolumeToRs1(); break; + case MSG_CONFIGURATION_CHANGED: + onConfigurationChanged(); + break; + default: if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) { // msg could be for the SoundDoseHelper @@ -9419,7 +9445,12 @@ public class AudioService extends IAudioService.Stub } 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 @@ -10161,10 +10192,30 @@ public class AudioService extends IAudioService.Stub } //========================================================================================== + + // 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; } //========================================================================================== @@ -10375,11 +10426,11 @@ public class AudioService extends IAudioService.Stub * 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(); @@ -10406,7 +10457,7 @@ public class AudioService extends IAudioService.Stub 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 bb49a182d866..7f7c13898219 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 @@ public class SoundDoseHelper { 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/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java index 4f2bfd1c1e52..aab815c93e1a 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 @@ final class ConversionUtils { 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/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java index ce68edbb9fa1..70d7bde2e53a 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 @@ public final class CpuInfoReader { 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 @@ public final class CpuInfoReader { 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 @@ public final class CpuInfoReader { 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 @@ public final class CpuInfoReader { 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 @@ public final class CpuInfoReader { 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 @@ public final class CpuInfoReader { cpuCoresFile.getAbsolutePath()); continue; } - StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqPair, + StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqKHz, relatedCpuCores); mStaticPolicyInfoById.append(policyId, staticPolicyInfo); if (DEBUG) { @@ -461,18 +446,13 @@ public final class CpuInfoReader { } } - 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 @@ public final class CpuInfoReader { 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 @@ public final class CpuInfoReader { 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 @@ public final class CpuInfoReader { } } - 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 ea157c89f675..ea0a4ab56320 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 @@ public final class DisplayManagerService extends SystemService { } } - // 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 @@ public final class DisplayManagerService extends SystemService { // 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 { @@ -2586,9 +2589,7 @@ public final class DisplayManagerService extends SystemService { void setDisplayModeDirectorLoggingEnabled(boolean enabled) { synchronized (mSyncRoot) { - if (mDisplayModeDirector != null) { - mDisplayModeDirector.setLoggingEnabled(enabled); - } + mDisplayModeDirector.setLoggingEnabled(enabled); } } @@ -2849,8 +2850,11 @@ public final class DisplayManagerService extends SystemService { private void dumpInternal(PrintWriter pw) { pw.println("DISPLAY MANAGER (dumpsys display)"); + BrightnessTracker brightnessTrackerLocal; synchronized (mSyncRoot) { + brightnessTrackerLocal = mBrightnessTracker; + pw.println(" mSafeMode=" + mSafeMode); pw.println(" mPendingTraversal=" + mPendingTraversal); pw.println(" mViewports=" + mViewports); @@ -2923,10 +2927,6 @@ public final class DisplayManagerService extends SystemService { for (int i = 0; i < displayPowerControllerCount; i++) { mDisplayPowerControllers.valueAt(i).dump(pw); } - if (mBrightnessTracker != null) { - pw.println(); - mBrightnessTracker.dump(pw); - } pw.println(); mPersistentDataStore.dump(pw); @@ -2939,6 +2939,10 @@ public final class DisplayManagerService extends SystemService { mDisplayWindowPolicyControllers.valueAt(i).second.dump(" ", pw); } } + if (brightnessTrackerLocal != null) { + pw.println(); + brightnessTrackerLocal.dump(pw); + } pw.println(); mDisplayModeDirector.dump(pw); mBrightnessSynchronizer.dump(pw); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index e12cd8c9a43b..656882f3f615 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 fbc354eb4c11..3e01222bbae6 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal 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 8d0689ff8fe5..79984c9b5355 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 class LocalDisplayAdapter extends DisplayAdapter { 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/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java index a081dff9e62d..5ef89ad4269a 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 @@ public class GnssConfiguration { 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 @@ public class GnssConfiguration { /* * 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 @@ public class GnssConfiguration { } } - 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 @@ public class GnssConfiguration { 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 1aee345f96d4..f107d0bf9932 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 @@ class LockSettingsShellCommand extends ShellCommand { 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 @@ class LockSettingsShellCommand extends ShellCommand { 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 fa53a607ec84..0e5e55c2d7b0 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 @@ package com.android.server.locksettings.recoverablekeystore.storage; 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { } 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 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { @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/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 2e86df89f63e..f95f7bc0d165 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 @@ package com.android.server.pm; 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 @@ public class AppDataHelper { } } - 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 @@ public class AppDataHelper { Slog.wtf(TAG, "Package was null!", new Throwable()); return; } - if (useArtService()) { + if (DexOptHelper.useArtService()) { destroyAppProfilesWithArtService(pkg); } else { try { @@ -637,7 +636,7 @@ public class AppDataHelper { } private void destroyAppProfilesLeafLIF(AndroidPackage pkg) { - if (useArtService()) { + if (DexOptHelper.useArtService()) { destroyAppProfilesWithArtService(pkg); } else { try { @@ -651,6 +650,15 @@ public class AppDataHelper { } 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 a9d4115b4b79..064be7c5ddc7 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 @@ import java.util.function.Predicate; 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 @@ public final class DexOptHelper { 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 @@ public final class DexOptHelper { } /** + * 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 cd1ae74e413c..3ac7aa750bf9 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -4276,10 +4276,14 @@ final class InstallPackageHelper { 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 0d417e457509..68c8abf0c2d3 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 @@ public class Installer extends SystemService { * 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 @@ public class Installer extends SystemService { // 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 7f7a23419dda..83d2f6ae0e40 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 @@ final class PackageHandler extends Handler { // 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 class PackageHandler extends Handler { } 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 03e0d360f9e3..36aeca142f5c 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.writeBooleanAttribute; 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.TypedXmlPullParser; 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } 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 6bc876037cfb..a6faff85ab06 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 class PackageManagerService implements PackageSender, TestUtilityService 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 929bc1e0b3c4..0b6ccc41d956 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 @@ class PackageVerificationState { private final SparseBooleanArray mRequiredVerifierUids; private final SparseBooleanArray mUnrespondedRequiredVerifierUids; + private final SparseBooleanArray mExtendedTimeoutUids; + private boolean mSufficientVerificationComplete; private boolean mSufficientVerificationPassed; @@ -41,8 +43,6 @@ class PackageVerificationState { private boolean mRequiredVerificationPassed; - private boolean mExtendedTimeout; - private boolean mIntegrityVerificationComplete; /** @@ -54,9 +54,9 @@ class PackageVerificationState { mSufficientVerifierUids = new SparseBooleanArray(); mRequiredVerifierUids = new SparseBooleanArray(); mUnrespondedRequiredVerifierUids = new SparseBooleanArray(); + mExtendedTimeoutUids = new SparseBooleanArray(); mRequiredVerificationComplete = false; mRequiredVerificationPassed = true; - mExtendedTimeout = false; } VerifyingSession getVerifyingSession() { @@ -88,14 +88,27 @@ class PackageVerificationState { 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 @@ class PackageVerificationState { 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 @@ class PackageVerificationState { if (mSufficientVerifierUids.size() == 0) { mSufficientVerificationComplete = true; } - - return true; } - - return false; } /** @@ -181,10 +196,12 @@ class PackageVerificationState { } /** 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 @@ class PackageVerificationState { * * @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/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 18eebe45a759..b4b8cb2a370d 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 @@ public final class SuspendPackageHelper { 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 721ad889f7fe..194f237ecbf2 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 @@ public abstract class UserManagerInternal { * * <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 741464072fd8..a36e9f961211 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { } @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 @@ public class UserManagerService extends IUserManager.Stub { } @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 3710af6771b4..cf82536fa327 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 @@ public final class UserVisibilityMediator implements Dumpable { } /** - * 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 @@ public final class UserVisibilityMediator implements Dumpable { } 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 @@ public final class UserVisibilityMediator implements Dumpable { /** 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 30f2132ce1f1..f0610180040c 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 @@ package com.android.server.pm; 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.os.UserHandle; 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 @@ final class VerificationUtils { 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 7f0c3f9f4f06..6e738daf9315 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 @@ public class DexManager { } } - // 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 c0d71ac26853..579d4e3562b4 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/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING index 094e70fe8f80..9f1cb1a451c6 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/powerstats/BatteryTrigger.java b/services/core/java/com/android/server/powerstats/BatteryTrigger.java index b35cb52d5025..15c181198fae 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 @@ public final class BatteryTrigger extends PowerStatsLogTrigger { 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 55060a677c8c..f53b52c18e14 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } 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 62932f9f81fb..38ba7c3b52f3 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.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENAB 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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; @@ -8441,6 +8442,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + @NonNull Rect getScreenResolvedBounds() { + final Configuration resolvedConfig = getResolvedOverrideConfiguration(); + final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + return mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; + } + void recomputeConfiguration() { // We check if the current activity is transparent. In that case we need to // recomputeConfiguration of the first opaque activity beneath, to allow a @@ -8648,7 +8655,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedBounds.set(containingBounds); final float letterboxAspectRatioOverride = - mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(); + mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig); final float desiredAspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO ? letterboxAspectRatioOverride : computeAspectRatio(parentBounds); @@ -9701,9 +9708,36 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 90a4820e8724..105b2bb09cf8 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -191,6 +191,9 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord * Prepare to handle on transition start. Clear all temporary fields. */ void preTransitionStart() { + if (shouldDisableSnapshots()) { + return; + } resetTmpFields(); } @@ -283,6 +286,9 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord * Called when the visibility of an app changes outside the regular app transition flow. */ void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) { + if (shouldDisableSnapshots()) { + return; + } if (!visible) { resetTmpFields(); addBelowTopActivityIfExist(appWindowToken.getTask(), mPendingRemoveActivity, diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 5e066faf0e90..f8fb76acf81e 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -50,8 +50,6 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; import android.view.RemoteAnimationAdapter; -import android.view.WindowManager; -import android.window.RemoteTransition; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -562,9 +560,8 @@ public class ActivityStartController { final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea() .getRootTask(WINDOWING_MODE_UNDEFINED, activityType); if (rootTask == null) return false; - final RemoteTransition remote = options.getRemoteTransition(); final ActivityRecord r = rootTask.topRunningActivity(); - if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null + if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || !r.mActivityComponent.equals(intent.getComponent()) // Recents keeps invisible while device is locked. || r.mDisplayContent.isKeyguardLocked()) { @@ -573,47 +570,13 @@ public class ActivityStartController { mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r); final ActivityMetricsLogger.LaunchingState launchingState = mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent); - final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT, - 0 /* flags */, r.mTransitionController, mService.mWindowManager.mSyncEngine); - if (r.mTransitionController.isCollecting()) { - // Special case: we are entering recents while an existing transition is running. In - // this case, we know it's safe to "defer" the activity launch, so lets do so now so - // that it can get its own transition and thus update launcher correctly. - mService.mWindowManager.mSyncEngine.queueSyncSet( - () -> { - if (r.isAttached()) { - r.mTransitionController.moveToCollecting(transition); - } - }, - () -> { - if (r.isAttached() && transition.isCollecting()) { - startExistingRecentsIfPossibleInner(options, r, rootTask, - launchingState, remote, transition); - } - }); - } else { - r.mTransitionController.moveToCollecting(transition); - startExistingRecentsIfPossibleInner(options, r, rootTask, launchingState, remote, - transition); - } - return true; - } - - private void startExistingRecentsIfPossibleInner(ActivityOptions options, ActivityRecord r, - Task rootTask, ActivityMetricsLogger.LaunchingState launchingState, - RemoteTransition remoteTransition, Transition transition) { final Task task = r.getTask(); mService.deferWindowLayout(); try { final TransitionController controller = r.mTransitionController; if (controller.getTransitionPlayer() != null) { - controller.requestStartTransition(transition, task, remoteTransition, - null /* displayChange */); controller.collect(task); controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask)); - } else { - // The transition player might be died when executing the queued transition. - transition.abort(); } task.moveToFront("startExistingRecents"); task.mInResumeTopActivity = true; @@ -624,6 +587,7 @@ public class ActivityStartController { task.mInResumeTopActivity = false; mService.continueWindowLayout(); } + return true; } void registerRemoteAnimationForNextActivityStart(String packageName, diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index ce29564d0b02..12be1d3186a1 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1582,19 +1582,19 @@ class ActivityStarter { } } if (isTransientLaunch) { - if (forceTransientTransition && newTransition != null) { - newTransition.collect(mLastStartActivityRecord); - newTransition.collect(mPriorAboveTask); + if (forceTransientTransition) { + transitionController.collect(mLastStartActivityRecord); + transitionController.collect(mPriorAboveTask); } // `started` isn't guaranteed to be the actual relevant activity, so we must wait // until after we launched to identify the relevant activity. transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask); - if (forceTransientTransition && newTransition != null) { + if (forceTransientTransition) { final DisplayContent dc = mLastStartActivityRecord.getDisplayContent(); // update wallpaper target to TransientHide dc.mWallpaperController.adjustWallpaperWindows(); // execute transition because there is no change - newTransition.setReady(dc, true /* ready */); + transitionController.setReady(dc, true /* ready */); } } if (!userLeaving) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 992743ab8593..555cd38806e6 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1240,25 +1240,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) { final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions); - // A quick path (skip general intent/task resolving) to start recents animation if the - // recents (or home) activity is available in background. - if (opts != null && opts.getOriginalOptions().getTransientLaunch() - && isCallerRecents(Binder.getCallingUid())) { - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents"); - if (mActivityStartController.startExistingRecentsIfPossible( - intent, opts.getOriginalOptions())) { - return ActivityManager.START_TASK_TO_FRONT; - } - // Else follow the standard launch procedure. - } - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - Binder.restoreCallingIdentity(origId); - } - } assertPackageMatchesCallingUid(callingPackage); enforceNotIsolatedCaller("startActivityAsUser"); @@ -5718,6 +5699,23 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, BackgroundStartPrivileges backgroundStartPrivileges) { assertPackageMatchesCallingUid(callingPackage); + // A quick path (skip general intent/task resolving) to start recents animation if the + // recents (or home) activity is available in background. + if (options != null && options.getOriginalOptions() != null + && options.getOriginalOptions().getTransientLaunch() && isCallerRecents(uid)) { + try { + synchronized (mGlobalLock) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents"); + if (mActivityStartController.startExistingRecentsIfPossible( + intent, options.getOriginalOptions())) { + return ActivityManager.START_TASK_TO_FRONT; + } + // Else follow the standard launch procedure. + } + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + } return getActivityStartController().startActivityInPackage(uid, realCallingPid, realCallingUid, callingPackage, callingFeatureId, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, options, userId, inTask, diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 9e258cbc2ec6..d358eb5d38db 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 @@ final class ContentRecorder implements WindowContainerListener { // 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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { // 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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { */ 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 class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { // 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 @@ final class ContentRecorder implements WindowContainerListener { // 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 @@ final class ContentRecorder implements WindowContainerListener { @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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { 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 @@ final class ContentRecorder implements WindowContainerListener { 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 1efc202a49ae..d60addc42831 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 @@ final class ContentRecordingController { } /** - * 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 a4d475fd928f..ef01cc8cd61a 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 389c908bfb6b..c719a1d30a16 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.TYPE_INTERNAL; -import static android.view.InsetsFrameProvider.SOURCE_FRAME; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -179,6 +178,7 @@ public class DisplayPolicy { private final boolean mCarDockEnablesAccelerometer; private final boolean mDeskDockEnablesAccelerometer; + private final boolean mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer; private final AccessibilityManager mAccessibilityManager; private final ImmersiveModeConfirmation mImmersiveModeConfirmation; private final ScreenshotHelper mScreenshotHelper; @@ -388,6 +388,8 @@ public class DisplayPolicy { final Resources r = mContext.getResources(); mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer); mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer); + mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer = + r.getBoolean(R.bool.config_deskRespectsNoSensorAndLockedWithoutAccelerometer); mCanSystemBarsBeShownByUser = !r.getBoolean( R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean( R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction); @@ -699,6 +701,10 @@ public class DisplayPolicy { return mDeskDockEnablesAccelerometer; } + boolean isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer() { + return mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer; + } + public void setPersistentVrModeEnabled(boolean persistentVrModeEnabled) { mPersistentVrModeEnabled = persistentVrModeEnabled; } @@ -1069,7 +1075,7 @@ public class DisplayPolicy { // runtime as ensured in WMS. Make use of the index in the provider directly // to access the latest provided size at runtime. final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider = - getFrameProvider(win, provider, i); + getFrameProvider(win, i); final InsetsFrameProvider.InsetsSizeOverride[] overrides = provider.getInsetsSizeOverrides(); final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> @@ -1095,19 +1101,15 @@ public class DisplayPolicy { } } - @Nullable private TriConsumer<DisplayFrames, WindowContainer, Rect> getFrameProvider(WindowState win, - InsetsFrameProvider provider, int index) { - if (provider.getInsetsSize() == null && provider.getSource() == SOURCE_FRAME) { - return null; - } + int index) { return (displayFrames, windowContainer, inOutFrame) -> { final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation); final InsetsFrameProvider ifp = lp.providedInsets[index]; InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted, windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame, ifp.getSource(), ifp.getInsetsSize(), lp.privateFlags, - ifp.getMinimalInsetsSizeInDisplayCutoutSafe()); + ifp.getMinimalInsetsSizeInDisplayCutoutSafe(), win.mGivenContentInsets); }; } @@ -1120,7 +1122,8 @@ public class DisplayPolicy { InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted, windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame, ifp.getSource(), ifp.getInsetsSizeOverrides()[overrideIndex].getInsetsSize(), - lp.privateFlags, null /* displayCutoutSafeInsetsSize */); + lp.privateFlags, null /* displayCutoutSafeInsetsSize */, + null /* givenContentInsets */); }; } @@ -2447,6 +2450,8 @@ public class DisplayPolicy { pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer); pw.print(" mDeskDockEnablesAccelerometer="); pw.println(mDeskDockEnablesAccelerometer); + pw.print(" mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer="); + pw.println(mDeskDockRespectsNoSensorAndLockedWithoutAccelerometer); pw.print(prefix); pw.print("mDockMode="); pw.print(Intent.dockStateToString(mDockMode)); pw.print(" mLidState="); pw.println(WindowManagerFuncs.lidStateToString(mLidState)); pw.print(prefix); pw.print("mAwake="); pw.print(mAwake); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 06d108b20b93..72263ffc7de2 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -87,6 +87,12 @@ import java.util.Set; */ public class DisplayRotation { private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayRotation" : TAG_WM; + // Delay to avoid race between fold update and orientation update. + private static final int ORIENTATION_UPDATE_DELAY_MS = 800; + + // 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 @@ -1194,6 +1200,10 @@ public class DisplayRotation { mDisplayPolicy.isCarDockEnablesAccelerometer(); final boolean deskDockEnablesAccelerometer = mDisplayPolicy.isDeskDockEnablesAccelerometer(); + final boolean deskDockRespectsNoSensorAndLockedWithoutAccelerometer = + mDisplayPolicy.isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer() + && (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED + || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); @Surface.Rotation final int preferredRotation; @@ -1213,7 +1223,8 @@ public class DisplayRotation { } else if ((dockMode == Intent.EXTRA_DOCK_STATE_DESK || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK) - && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0)) { + && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0) + && !deskDockRespectsNoSensorAndLockedWithoutAccelerometer) { // Ignore sensor when in desk dock unless explicitly enabled. // This case can override the behavior of NOSENSOR, and can also // enable 180 degree rotation while docked. @@ -1662,6 +1673,7 @@ public class DisplayRotation { private boolean mInHalfFoldTransition = false; private final boolean mIsDisplayAlwaysSeparatingHinge; private final Set<Integer> mTabletopRotations; + private final Runnable mActivityBoundsUpdateCallback; FoldController() { mTabletopRotations = new ArraySet<>(); @@ -1696,6 +1708,26 @@ public class DisplayRotation { } 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) { @@ -1757,24 +1789,19 @@ public class DisplayRotation { mDeviceState = newState; // Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will // return true, so rotation is unlocked. - mService.updateRotation(false /* alwaysSendConfiguration */, - false /* forceRelayout */); } else { mInHalfFoldTransition = true; mDeviceState = newState; - // Tell the device to update its orientation. - mService.updateRotation(false /* alwaysSendConfiguration */, - false /* forceRelayout */); } + UiThread.getHandler().postDelayed( + () -> { + mService.updateRotation(false /* alwaysSendConfiguration */, + false /* forceRelayout */); + }, ORIENTATION_UPDATE_DELAY_MS); // 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 8f40e79d7c0f..0b960ec2a583 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -419,8 +419,12 @@ final class InputMonitor { 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 = @@ -429,6 +433,14 @@ final class InputMonitor { 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 37cf5bc95a23..ec04894b1d42 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 @@ final class LetterboxConfiguration { // 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 @@ final class LetterboxConfiguration { 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 @@ final class LetterboxConfiguration { 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 @@ final class LetterboxConfiguration { } /** + * 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 @@ final class LetterboxConfiguration { 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 ef464d299e2f..f6f7b49d6d44 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.isFixedOrientationLandscape; 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 @@ final class LetterboxUiController { 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 @@ final class LetterboxUiController { 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 @@ final class LetterboxUiController { : 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() { @@ -877,7 +888,8 @@ final class LetterboxUiController { return; } - boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge(); + boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge() + && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); int letterboxPositionForHorizontalReachability = mLetterboxConfiguration .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode); if (mLetterbox.getInnerFrame().left > x) { @@ -957,6 +969,8 @@ final class LetterboxUiController { * </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 @@ final class LetterboxUiController { && 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 @@ final class LetterboxUiController { * </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 @@ final class LetterboxUiController { && 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 @@ final class LetterboxUiController { 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,6 +1514,22 @@ final class LetterboxUiController { 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 aspect ratio to activities // which are not already providing one (e.g. permission dialogs) and presumably also @@ -1516,7 +1549,7 @@ final class LetterboxUiController { 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 fda2125be3cf..2f5634362e68 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 78ee6f95fdba..7b10c6372b0e 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java index cd1263e5d168..badcfa9d6964 100644 --- a/services/core/java/com/android/server/wm/SnapshotController.java +++ b/services/core/java/com/android/server/wm/SnapshotController.java @@ -213,8 +213,12 @@ class SnapshotController { void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) { if (!visible && hasTransitionStateConsumer(TASK_CLOSE)) { + final Task task = appWindowToken.getTask(); + if (task == null || task.isVisibleRequested()) { + return; + } // close task transition - addTransitionRecord(TASK_CLOSE, false /*open*/, appWindowToken.getTask()); + addTransitionRecord(TASK_CLOSE, false /*open*/, task); mActivitySnapshotController.preTransitionStart(); notifyTransition(TASK_CLOSE); mActivitySnapshotController.postTransitionStart(); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 93c8c3666706..184293e11002 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 ba49dd0032a4..2cfd2af89c80 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1227,7 +1227,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // transferred. If transition is transient, IME won't be moved during the transition and // the tasks are still live, so we take the snapshot at the end of the transition instead. if (mTransientLaunches == null) { - mController.mSnapshotController.onTransitionReady(mType, mParticipants); + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); + if (ar == null || ar.getTask() == null + || ar.getTask().isVisibleRequested()) continue; + mController.mSnapshotController.mTaskSnapshotController.recordSnapshot( + ar.getTask(), false /* allowSnapshotHome */); + } } // This is non-null only if display has changes. It handles the visible windows that don't @@ -2732,9 +2738,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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 61685fc9635c..a55c7c1ce10b 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 @@ public class WindowManagerService extends IWindowManager.Stub } 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(); diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 8c2dd2d2f5e2..437af4b4ac76 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 @@ public class WindowManagerShellCommand extends ShellCommand { runSetBooleanFlag(pw, mLetterboxConfiguration ::setIsVerticalReachabilityEnabled); break; + case "--isAutomaticReachabilityInBookModeEnabled": + runSetBooleanFlag(pw, mLetterboxConfiguration + ::setIsAutomaticReachabilityInBookModeEnabled); + break; case "--defaultPositionForHorizontalReachability": runSetLetterboxDefaultPositionForHorizontalReachability(pw); break; @@ -1183,6 +1187,7 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled(); mLetterboxConfiguration.resetIsVerticalReachabilityEnabled(); + mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode(); mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability(); mLetterboxConfiguration.resetDefaultPositionForVerticalReachability(); mLetterboxConfiguration.resetIsEducationEnabled(); @@ -1218,6 +1223,8 @@ public class WindowManagerShellCommand extends ShellCommand { + 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 8b0ef6cb94b4..c74af8291142 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * @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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); mService.continueWindowLayout(); } + return effects; } private int applyChanges(@NonNull WindowContainer<?> container, diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index a15d66300eca..90b92f43d80f 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -42,8 +42,9 @@ import android.credentials.IClearCredentialStateCallback; 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; @@ -307,6 +308,29 @@ public final class CredentialManagerService 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) { @@ -443,21 +467,122 @@ public final class CredentialManagerService } @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; diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index 4e058a8e05e2..8082cdbf6ec8 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.GetCredentialResponse; 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 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, 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 000000000000..9165901ad81e --- /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 95b0ff024bfc..d17e98439978 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -119,6 +119,40 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential 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<>(); + 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; + } + private static BeginGetCredentialRequest constructQueryPhaseRequest( android.credentials.GetCredentialRequest filteredRequest, CallingAppInfo callingAppInfo, diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index a5196c519740..85c78445e66b 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 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption 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 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption .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 e9c50b54f5dd..269c4abf537e 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_ENTERPR 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.AttestationUtils; 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { onLockSettingsReady(); loadAdminDataAsync(); mOwners.systemReady(); - if (isWorkProfileTelephonyFlagEnabled()) { + if (isWorkProfileTelephonyEnabled()) { applyManagedSubscriptionsPolicyIfRequired(); } break; @@ -3534,26 +3536,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mLockSettingsInternal.refreshStrongAuthTimeout(parentId); - if (isWorkProfileTelephonyFlagEnabled()) { + if (isWorkProfileTelephonyEnabled()) { clearManagedSubscriptionsPolicy(); clearLauncherShortcutOverrides(); updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false); @@ -10991,8 +10988,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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); @@ -22705,11 +22704,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 @@ -22822,7 +22834,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() { - if (isWorkProfileTelephonyFlagEnabled()) { + if (isWorkProfileTelephonyEnabled()) { synchronized (getLockObject()) { ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(); if (admin != null && admin.mManagedSubscriptionsPolicy != null) { @@ -22836,7 +22848,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) { - if (!isWorkProfileTelephonyFlagEnabled()) { + if (!isWorkProfileTelephonyEnabled()) { throw new UnsupportedOperationException("This api is not enabled"); } CallerIdentity caller = getCallerIdentity(); @@ -22844,9 +22856,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "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"); } @@ -22868,7 +22881,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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 { @@ -22879,10 +22891,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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, @@ -22908,6 +22918,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + 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 fd31b2211b7a..d559b67218ca 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.FileInputStream; 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 @@ public class PackageManagerTests extends AndroidTestCase { 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 8715afda5cee..a93e8ad93756 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 @@ public class PackageVerificationStateTest extends AndroidTestCase { 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 @@ public class PackageVerificationStateTest extends AndroidTestCase { 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 @@ public class PackageVerificationStateTest extends AndroidTestCase { 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 @@ public class PackageVerificationStateTest extends AndroidTestCase { 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 @@ public class PackageVerificationStateTest extends AndroidTestCase { 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 80b164d85baa..000000000000 --- 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 582d8c8b8378..000000000000 --- 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 749fce669df1..000000000000 --- 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 index d4f015c68e40..d4f015c68e40 100644 --- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/corrupted_cpufreq/policy1/cpuinfo_cur_freq +++ b/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 e69de29bb2d1..000000000000 --- 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 e69de29bb2d1..000000000000 --- 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 dadd9737778d..000000000000 --- 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 a93d6f7b2c09..000000000000 --- 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 573541ac9702..dadd9737778d 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 573541ac9702..a93d6f7b2c09 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 index 2bc4ce96ec98..2bc4ce96ec98 100644 --- 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 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 index c754f1a461ab..c754f1a461ab 100644 --- 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 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 e69de29bb2d1..000000000000 --- 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 e69de29bb2d1..000000000000 --- 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 749fce669df1..000000000000 --- 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 a93d6f7b2c09..000000000000 --- 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 573541ac9702..749fce669df1 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 573541ac9702..a93d6f7b2c09 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 index c754f1a461ab..c754f1a461ab 100644 --- 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 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 index c754f1a461ab..c754f1a461ab 100644 --- 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 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 e69de29bb2d1..000000000000 --- 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 e69de29bb2d1..000000000000 --- 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 dadd9737778d..000000000000 --- 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 a93d6f7b2c09..000000000000 --- 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 573541ac9702..dadd9737778d 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 573541ac9702..a93d6f7b2c09 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 index 2bc4ce96ec98..2bc4ce96ec98 100644 --- 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 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 index c754f1a461ab..c754f1a461ab 100644 --- 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 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 e69de29bb2d1..000000000000 --- 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 e69de29bb2d1..000000000000 --- 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 749fce669df1..000000000000 --- 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 a93d6f7b2c09..000000000000 --- 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 573541ac9702..749fce669df1 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 573541ac9702..a93d6f7b2c09 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 index c754f1a461ab..c754f1a461ab 100644 --- 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 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 index c754f1a461ab..c754f1a461ab 100644 --- 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 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 e69de29bb2d1..000000000000 --- 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 e69de29bb2d1..000000000000 --- 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 f9f53251fa7e..a5adf3f9bf80 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 @@ public final class AlarmManagerServiceTest { 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 @@ public final class AlarmManagerServiceTest { 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 a129f39106cd..246b0f04171e 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 @@ public class AlarmTest { 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 63e8e56d09b2..390119c968cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -52,6 +52,7 @@ 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; @@ -355,6 +356,33 @@ public final class BroadcastQueueModernImplTest { } /** + * 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. */ @@ -364,8 +392,10 @@ public final class BroadcastQueueModernImplTest { 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); @@ -373,6 +403,8 @@ public final class BroadcastQueueModernImplTest { 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()); } @@ -519,11 +551,13 @@ public final class BroadcastQueueModernImplTest { } @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); } @@ -1079,7 +1113,8 @@ public final class BroadcastQueueModernImplTest { 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(), anyString(), anyString()), 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/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 51dcc0323a96..0ab984bd9381 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 com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 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 @@ public final class DisplayPowerController2Test { @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 @@ public final class DisplayPowerController2Test { 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 @@ public final class DisplayPowerController2Test { 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 @@ public final class DisplayPowerController2Test { return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, wakelockController, - screenOffBrightnessSensorController, hbmMetadata); + screenOffBrightnessSensorController, hbmMetadata, brightnessMappingStrategy, + injector); } /** @@ -833,6 +888,8 @@ public final class DisplayPowerController2Test { 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 @@ public final class DisplayPowerController2Test { 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 @@ public final class DisplayPowerController2Test { 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 0a1bf1c9ed99..c021ef65a291 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 com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 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 @@ public final class DisplayPowerControllerTest { @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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, screenOffBrightnessSensorController, - hbmMetadata); + hbmMetadata, brightnessMappingStrategy, injector); } /** @@ -835,13 +889,17 @@ public final class DisplayPowerControllerTest { 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 @@ public final class DisplayPowerControllerTest { 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 d5aa7fec996f..9a7ee4d7887b 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.content.IntentFilter; 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.DexManager; 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 @@ public final class BackgroundDexOptServiceUnitTest { @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 70b5ac063316..386fd3ecde0f 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 @@ abstract class UserVisibilityMediatorTestCase extends ExpectableTestCase { } 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 84a61c7a21e5..a9b68eb8f447 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 @@ public class AlarmManagerEconomicPolicyTest { 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 @@ public class AlarmManagerEconomicPolicyTest { 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 @@ public class AlarmManagerEconomicPolicyTest { 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 @@ public class AlarmManagerEconomicPolicyTest { 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 @@ public class AlarmManagerEconomicPolicyTest { 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 @@ public class AlarmManagerEconomicPolicyTest { 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 @@ public class AlarmManagerEconomicPolicyTest { 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 cad608f8ff59..d66e74a812a7 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 @@ public class CompleteEconomicPolicyTest { 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 @@ public class CompleteEconomicPolicyTest { 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 ebf760cdf857..22c731042e42 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 @@ public class JobSchedulerEconomicPolicyTest { 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 @@ public class JobSchedulerEconomicPolicyTest { 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 @@ public class JobSchedulerEconomicPolicyTest { 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 @@ public class JobSchedulerEconomicPolicyTest { 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 @@ public class JobSchedulerEconomicPolicyTest { 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 @@ public class JobSchedulerEconomicPolicyTest { 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 @@ public class JobSchedulerEconomicPolicyTest { 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 24b003c2c011..caa2e367f009 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 @@ package com.android.server.accounts; 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 @@ public class AccountManagerServiceTest extends AndroidTestCase { } @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 73f30d9f9e79..b98a6a891d55 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 @@ package com.android.server.accounts; import android.accounts.Account; -import java.util.ArrayList; -import java.util.List; - /** * Constants shared between test AccountAuthenticators and AccountManagerServiceTest. */ @@ -31,6 +28,8 @@ public final class AccountManagerServiceTestFixtures { "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 8106364477d9..924443e9d5cf 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.Context; import android.content.Intent; import android.os.Bundle; -import com.android.frameworks.servicestests.R; - import java.util.concurrent.atomic.AtomicInteger; /** @@ -270,11 +268,13 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator 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 @@ public class TestAccountType1Authenticator extends AbstractAccountAuthenticator 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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index aaabb286589c..4f74ef8b5f70 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 94d30bb4440b..6d2ce7fbe68d 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.assertThrows; 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.HdrConversionMode; 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 @@ public class DisplayManagerServiceTest { } @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 @@ public class DisplayManagerServiceTest { 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 @@ public class DisplayManagerServiceTest { 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/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java index 6f89ff099338..2a9c18c55035 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 @@ public class RecoverableKeyStoreDbHelperTest { 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/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp index 9ca287686758..986fb71afa2d 100644 --- a/services/tests/voiceinteractiontests/Android.bp +++ b/services/tests/voiceinteractiontests/Android.bp @@ -29,6 +29,7 @@ android_test { 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 000000000000..8694094ce6ac --- /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/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 6dd4f2a703c6..226ecf421928 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -17,6 +17,8 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -790,7 +792,7 @@ public class DisplayRotationTests { // ... until half-fold mTarget.foldStateChanged(DeviceStateController.DeviceState.HALF_FOLDED); assertTrue(waitForUiHandler()); - verify(sMockWm).updateRotation(false, false); + verify(sMockWm).updateRotation(anyBoolean(), anyBoolean()); assertTrue(waitForUiHandler()); assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation( SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); @@ -798,7 +800,7 @@ public class DisplayRotationTests { // ... then transition back to flat mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN); assertTrue(waitForUiHandler()); - verify(sMockWm, atLeast(1)).updateRotation(false, false); + verify(sMockWm, atLeast(1)).updateRotation(anyBoolean(), anyBoolean()); assertTrue(waitForUiHandler()); assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation( SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); @@ -866,6 +868,23 @@ public class DisplayRotationTests { } @Test + public void testIgnoresDeskDockRotation_whenNoSensorAndLockedRespected() throws Exception { + mBuilder.setDeskDockRotation(Surface.ROTATION_270).build(); + when(mMockDisplayPolicy.isDeskDockRespectsNoSensorAndLockedWithoutAccelerometer()) + .thenReturn(true); + configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false); + + when(mMockDisplayPolicy.getDockMode()).thenReturn(Intent.EXTRA_DOCK_STATE_DESK); + + freezeRotation(Surface.ROTATION_90); + + assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_LOCKED, Surface.ROTATION_90)); + assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_NOSENSOR, Surface.ROTATION_90)); + } + + @Test public void testReturnsUserRotation_FixedToUserRotation_IgnoreIncompatibleAppRequest() throws Exception { mBuilder.build(); 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 a17e124e8216..dfc453f0f1b6 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 @@ public class RootWindowContainerTests extends WindowTestsBase { // 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 adc3db73a172..e4a591ea4fe2 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; @@ -352,6 +353,33 @@ public class SizeCompatTests extends WindowTestsBase { } @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); @@ -418,7 +446,7 @@ public class SizeCompatTests extends WindowTestsBase { .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()); @@ -3085,12 +3113,44 @@ public class SizeCompatTests extends WindowTestsBase { 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 */); @@ -3106,8 +3166,8 @@ public class SizeCompatTests extends WindowTestsBase { 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()); } @@ -3127,8 +3187,8 @@ public class SizeCompatTests extends WindowTestsBase { 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()); } @@ -3148,7 +3208,8 @@ public class SizeCompatTests extends WindowTestsBase { 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()); } @@ -3670,7 +3731,6 @@ public class SizeCompatTests extends WindowTestsBase { @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 */); @@ -3692,16 +3752,15 @@ public class SizeCompatTests extends WindowTestsBase { 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); @@ -3719,7 +3778,28 @@ public class SizeCompatTests extends WindowTestsBase { 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/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index 1a1af3b929d1..2975e1e050f5 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 @@ public class UsbPortManager implements IBinder.DeathRecipient { } } - // 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 70af33796ad1..04c1c0451e63 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.PackageManager; 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.permission.SafeCloseable; 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.Parcel; 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 com.android.server.utils.EventLogger; 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 @@ public class SoundTriggerService extends SystemService { } } + // 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 @@ public class SoundTriggerService extends SystemService { 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 class SoundTriggerService extends SystemService { 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 @@ public class SoundTriggerService extends SystemService { @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 7d5750e49907..2f8d17d77e52 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.PhraseRecognitionEvent; 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.RemoteException; 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.text.SimpleDateFormat; 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 @@ import java.util.Objects; 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 class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt 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 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt 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 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt 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 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt 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 c35d90f4a495..f7b66a26ff68 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_EVENT 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 @@ public final class HotwordMetricsLogger { 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 1a76295c251f..e1da2ca2a086 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.app.IVoiceInteractor; 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 class VoiceInteractionManagerService extends SystemService { 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 0325ba648af6..f9b76f4907cf 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 static android.telephony.Annotation.DataState; 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 @@ public final class TelephonyUtils { * 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 @@ public final class TelephonyUtils { "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/tests/componentalias/Android.bp b/tests/componentalias/Android.bp index e5eb3c7b6394..7af76e1144f8 100644 --- a/tests/componentalias/Android.bp +++ b/tests/componentalias/Android.bp @@ -16,6 +16,9 @@ package { 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 @@ java_defaults { ], 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 7bb83a336833..000000000000 --- 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 70e817ebf3e7..000000000000 --- 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 c96f1736c684..000000000000 --- 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 24c0432bcf4c..000000000000 --- 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 21616f5edf00..000000000000 --- 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 c11b0cd55ef4..000000000000 --- 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 afdfe79ea4a4..000000000000 --- 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 99322ee46106..000000000000 --- 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 7d5e0b9c6d8a..000000000000 --- 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 ee20379d971a..000000000000 --- 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 d41696f27880..000000000000 --- 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 0899886fe951..000000000000 --- 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 f0ff088815af..000000000000 --- 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 165d728c92a6..000000000000 --- 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 1d05e72a2f3f..000000000000 --- 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/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java deleted file mode 100644 index 06f7a13f73d7..000000000000 --- 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 df7579d8304d..000000000000 --- 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 5ae55215f696..000000000000 --- 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 f9b9886b0bb2..000000000000 --- 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 535d9b80f100..000000000000 --- 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 64b91f5695f5..000000000000 --- 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 bd589991d7dc..000000000000 --- 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 0ddf8188768b..000000000000 --- 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 0dbc0501b6f9..000000000000 --- 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/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java deleted file mode 100644 index 099425867f02..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/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.s; - -public class Target04 extends BaseService { -} diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp index 7375c160a59f..43f21221ae5a 100644 --- a/tools/lint/fix/Android.bp +++ b/tools/lint/fix/Android.bp @@ -23,9 +23,8 @@ package { 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 367d0bcb1aa7..a5ac2be1c18a 100644 --- a/tools/lint/fix/README.md +++ b/tools/lint/fix/README.md @@ -5,9 +5,12 @@ Inspiration: go/refactor-the-platform-with-lint\ ## 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 @@ directory. This script runs the lint, unpacks those files, and copies them back ## 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 1c83f7b38400..000000000000 --- 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 3308df6fc5af..cd4d778d1dec 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 @@ class SoongLintFix: 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. - - By default, it will apply lint fixes using the intermediate `suggested-fixes` - directory that soong creates during its invocation of lint. + It breaks up these operations into a few "private" methods + that are intentionally exposed so experimental code can tweak behavior. - 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 @@ class SoongLintFix: ``` """ 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}" - ] - - - if self._args.dry_run: - print(self._get_commands_str()) - else: - self._execute() + self._fix() + 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" + env = os.environ.copy() + if 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 - if not self._args.dry_run: - self._commands += [f"export ANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"] + self._kwargs = { + "env": env, + "executable": "/bin/bash", + "shell": True, + } - if self._args.check: - self._commands += [f"export ANDROID_LINT_CHECK={self._args.check}"] + os.chdir(ANDROID_BUILD_TOP) + + + 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}.") - 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}", - ] + 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 - subprocess.call(self._get_commands_str(), executable='/bin/bash', shell=True) + 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.") + + + 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 @@ def _setup_parser(): 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 b4f6a1c12d8f..935badecf8d5 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 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { 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 f7560a712f70..75b00737a168 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 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { ) } + 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. |