diff options
191 files changed, 5386 insertions, 2827 deletions
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml index 63e5983401d7..5befa1f1d4d4 100644 --- a/apct-tests/perftests/multiuser/AndroidManifest.xml +++ b/apct-tests/perftests/multiuser/AndroidManifest.xml @@ -35,4 +35,8 @@ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.perftests.multiuser"/> + <queries> + <package android:name="perftests.multiuser.apps.dummyapp" /> + </queries> + </manifest> 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 d9fb318c9335..358f009e8797 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -177,7 +177,7 @@ public class JobSchedulerService extends com.android.server.SystemService @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L; - @VisibleForTesting + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static Clock sSystemClock = Clock.systemUTC(); private abstract static class MySimpleClock extends Clock { @@ -454,6 +454,10 @@ public class JobSchedulerService extends com.android.server.SystemService runtimeUpdated = true; } break; + case Constants.KEY_PERSIST_IN_SPLIT_FILES: + mConstants.updatePersistingConstantsLocked(); + mJobs.setUseSplitFiles(mConstants.PERSIST_IN_SPLIT_FILES); + break; default: if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY) && !concurrencyUpdated) { @@ -537,6 +541,8 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = "runtime_min_high_priority_guarantee_ms"; + private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files"; + private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; @@ -563,6 +569,7 @@ public class JobSchedulerService extends com.android.server.SystemService public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS; @VisibleForTesting static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS; + static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = false; private static final boolean DEFAULT_USE_TARE_POLICY = false; /** @@ -678,6 +685,12 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS; /** + * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be + * saved in a single file. + */ + public boolean PERSIST_IN_SPLIT_FILES = DEFAULT_PERSIST_IN_SPLIT_FILES; + + /** * If true, use TARE policy for job limiting. If false, use quotas. */ public boolean USE_TARE_POLICY = DEFAULT_USE_TARE_POLICY; @@ -735,6 +748,11 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC); } + private void updatePersistingConstantsLocked() { + PERSIST_IN_SPLIT_FILES = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_PERSIST_IN_SPLIT_FILES, DEFAULT_PERSIST_IN_SPLIT_FILES); + } + private void updatePrefetchConstantsLocked() { PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = DeviceConfig.getLong( DeviceConfig.NAMESPACE_JOB_SCHEDULER, @@ -835,6 +853,8 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS) .println(); + pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println(); + pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY).println(); pw.decreaseIndent(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index 68cb049af758..2f94705f01c2 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -40,6 +40,7 @@ import android.util.AtomicFile; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SystemConfigFileCommitEventLogger; import android.util.Xml; @@ -89,6 +90,8 @@ public final class JobStore { /** Threshold to adjust how often we want to write to the db. */ private static final long JOB_PERSIST_DELAY = 2000L; + private static final String JOB_FILE_SPLIT_PREFIX = "jobs_"; + private static final int ALL_UIDS = -1; final Object mLock; final Object mWriteScheduleLock; // used solely for invariants around write scheduling @@ -105,13 +108,20 @@ public final class JobStore { @GuardedBy("mWriteScheduleLock") private boolean mWriteInProgress; + @GuardedBy("mWriteScheduleLock") + private boolean mSplitFileMigrationNeeded; + private static final Object sSingletonLock = new Object(); private final SystemConfigFileCommitEventLogger mEventLogger; private final AtomicFile mJobsFile; + private final File mJobFileDirectory; + private final SparseBooleanArray mPendingJobWriteUids = new SparseBooleanArray(); /** Handler backed by IoThread for writing to disk. */ private final Handler mIoHandler = IoThread.getHandler(); private static JobStore sSingleton; + private boolean mUseSplitFiles = JobSchedulerService.Constants.DEFAULT_PERSIST_IN_SPLIT_FILES; + private JobStorePersistStats mPersistInfo = new JobStorePersistStats(); /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */ @@ -144,10 +154,10 @@ public final class JobStore { mContext = context; File systemDir = new File(dataDir, "system"); - File jobDir = new File(systemDir, "job"); - jobDir.mkdirs(); + mJobFileDirectory = new File(systemDir, "job"); + mJobFileDirectory.mkdirs(); mEventLogger = new SystemConfigFileCommitEventLogger("jobs"); - mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), mEventLogger); + mJobsFile = createJobFile(new File(mJobFileDirectory, "jobs.xml")); mJobSet = new JobSet(); @@ -162,12 +172,21 @@ public final class JobStore { // an incorrect historical timestamp. That's fine; at worst we'll reboot with // a *correct* timestamp, see a bunch of overdue jobs, and run them; then // settle into normal operation. - mXmlTimestamp = mJobsFile.getLastModifiedTime(); + mXmlTimestamp = mJobsFile.exists() + ? mJobsFile.getLastModifiedTime() : mJobFileDirectory.lastModified(); mRtcGood = (sSystemClock.millis() > mXmlTimestamp); readJobMapFromDisk(mJobSet, mRtcGood); } + private AtomicFile createJobFile(String baseName) { + return createJobFile(new File(mJobFileDirectory, baseName + ".xml")); + } + + private AtomicFile createJobFile(File file) { + return new AtomicFile(file, mEventLogger); + } + public boolean jobTimesInflatedValid() { return mRtcGood; } @@ -211,6 +230,7 @@ public final class JobStore { public void add(JobStatus jobStatus) { mJobSet.add(jobStatus); if (jobStatus.isPersisted()) { + mPendingJobWriteUids.put(jobStatus.getUid(), true); maybeWriteStatusToDiskAsync(); } if (DEBUG) { @@ -224,6 +244,9 @@ public final class JobStore { @VisibleForTesting public void addForTesting(JobStatus jobStatus) { mJobSet.add(jobStatus); + if (jobStatus.isPersisted()) { + mPendingJobWriteUids.put(jobStatus.getUid(), true); + } } boolean containsJob(JobStatus jobStatus) { @@ -257,12 +280,24 @@ public final class JobStore { return false; } if (removeFromPersisted && jobStatus.isPersisted()) { + mPendingJobWriteUids.put(jobStatus.getUid(), true); maybeWriteStatusToDiskAsync(); } return removed; } /** + * Like {@link #remove(JobStatus, boolean)}, but doesn't schedule a disk write. + */ + @VisibleForTesting + public void removeForTesting(JobStatus jobStatus) { + mJobSet.remove(jobStatus); + if (jobStatus.isPersisted()) { + mPendingJobWriteUids.put(jobStatus.getUid(), true); + } + } + + /** * Remove the jobs of users not specified in the keepUserIds. * @param keepUserIds Array of User IDs whose jobs should be kept and not removed. */ @@ -273,6 +308,7 @@ public final class JobStore { @VisibleForTesting public void clear() { mJobSet.clear(); + mPendingJobWriteUids.put(ALL_UIDS, true); maybeWriteStatusToDiskAsync(); } @@ -282,6 +318,36 @@ public final class JobStore { @VisibleForTesting public void clearForTesting() { mJobSet.clear(); + mPendingJobWriteUids.put(ALL_UIDS, true); + } + + void setUseSplitFiles(boolean useSplitFiles) { + synchronized (mLock) { + if (mUseSplitFiles != useSplitFiles) { + mUseSplitFiles = useSplitFiles; + migrateJobFilesAsync(); + } + } + } + + /** + * The same as above but does not schedule writing. This makes perf benchmarks more stable. + */ + @VisibleForTesting + public void setUseSplitFilesForTesting(boolean useSplitFiles) { + final boolean changed; + synchronized (mLock) { + changed = mUseSplitFiles != useSplitFiles; + if (changed) { + mUseSplitFiles = useSplitFiles; + mPendingJobWriteUids.put(ALL_UIDS, true); + } + } + if (changed) { + synchronized (mWriteScheduleLock) { + mSplitFileMigrationNeeded = true; + } + } } /** @@ -352,6 +418,16 @@ public final class JobStore { private static final String XML_TAG_ONEOFF = "one-off"; private static final String XML_TAG_EXTRAS = "extras"; + private void migrateJobFilesAsync() { + synchronized (mLock) { + mPendingJobWriteUids.put(ALL_UIDS, true); + } + synchronized (mWriteScheduleLock) { + mSplitFileMigrationNeeded = true; + maybeWriteStatusToDiskAsync(); + } + } + /** * Every time the state changes we write all the jobs in one swath, instead of trying to * track incremental changes. @@ -449,10 +525,38 @@ public final class JobStore { * NOTE: This Runnable locks on mLock */ private final Runnable mWriteRunnable = new Runnable() { + private final SparseArray<AtomicFile> mJobFiles = new SparseArray<>(); + private final CopyConsumer mPersistedJobCopier = new CopyConsumer(); + + class CopyConsumer implements Consumer<JobStatus> { + private final SparseArray<List<JobStatus>> mJobStoreCopy = new SparseArray<>(); + private boolean mCopyAllJobs; + + private void prepare() { + mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS); + } + + @Override + public void accept(JobStatus jobStatus) { + final int uid = mUseSplitFiles ? jobStatus.getUid() : ALL_UIDS; + if (jobStatus.isPersisted() && (mCopyAllJobs || mPendingJobWriteUids.get(uid))) { + List<JobStatus> uidJobList = mJobStoreCopy.get(uid); + if (uidJobList == null) { + uidJobList = new ArrayList<>(); + mJobStoreCopy.put(uid, uidJobList); + } + uidJobList.add(new JobStatus(jobStatus)); + } + } + + private void reset() { + mJobStoreCopy.clear(); + } + } + @Override public void run() { final long startElapsed = sElapsedRealtimeClock.millis(); - final List<JobStatus> storeCopy = new ArrayList<JobStatus>(); // Intentionally allow new scheduling of a write operation *before* we clone // the job set. If we reset it to false after cloning, there's a window in // which no new write will be scheduled but mLock is not held, i.e. a new @@ -469,31 +573,73 @@ public final class JobStore { } mWriteInProgress = true; } + final boolean useSplitFiles; synchronized (mLock) { // Clone the jobs so we can release the lock before writing. - mJobSet.forEachJob(null, (job) -> { - if (job.isPersisted()) { - storeCopy.add(new JobStatus(job)); + useSplitFiles = mUseSplitFiles; + mPersistedJobCopier.prepare(); + mJobSet.forEachJob(null, mPersistedJobCopier); + mPendingJobWriteUids.clear(); + } + mPersistInfo.countAllJobsSaved = 0; + mPersistInfo.countSystemServerJobsSaved = 0; + mPersistInfo.countSystemSyncManagerJobsSaved = 0; + for (int i = mPersistedJobCopier.mJobStoreCopy.size() - 1; i >= 0; --i) { + AtomicFile file; + if (useSplitFiles) { + final int uid = mPersistedJobCopier.mJobStoreCopy.keyAt(i); + file = mJobFiles.get(uid); + if (file == null) { + file = createJobFile(JOB_FILE_SPLIT_PREFIX + uid); + mJobFiles.put(uid, file); } - }); + } else { + file = mJobsFile; + } + if (DEBUG) { + Slog.d(TAG, "Writing for " + mPersistedJobCopier.mJobStoreCopy.keyAt(i) + + " to " + file.getBaseFile().getName() + ": " + + mPersistedJobCopier.mJobStoreCopy.valueAt(i).size() + " jobs"); + } + writeJobsMapImpl(file, mPersistedJobCopier.mJobStoreCopy.valueAt(i)); } - writeJobsMapImpl(storeCopy); if (DEBUG) { Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis() - startElapsed) + "ms"); } + mPersistedJobCopier.reset(); + if (!useSplitFiles) { + mJobFiles.clear(); + } + // Update the last modified time of the directory to aid in RTC time verification + // (see the JobStore constructor). + mJobFileDirectory.setLastModified(sSystemClock.millis()); synchronized (mWriteScheduleLock) { + if (mSplitFileMigrationNeeded) { + final File[] files = mJobFileDirectory.listFiles(); + for (File file : files) { + if (useSplitFiles) { + if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) { + // Delete the now unused file so there's no confusion in the future. + file.delete(); + } + } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) { + // Delete the now unused file so there's no confusion in the future. + file.delete(); + } + } + } mWriteInProgress = false; mWriteScheduleLock.notifyAll(); } } - private void writeJobsMapImpl(List<JobStatus> jobList) { + private void writeJobsMapImpl(@NonNull AtomicFile file, @NonNull List<JobStatus> jobList) { int numJobs = 0; int numSystemJobs = 0; int numSyncJobs = 0; mEventLogger.setStartTime(SystemClock.uptimeMillis()); - try (FileOutputStream fos = mJobsFile.startWrite()) { + try (FileOutputStream fos = file.startWrite()) { TypedXmlSerializer out = Xml.resolveSerializer(fos); out.startDocument(null, true); out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); @@ -523,7 +669,7 @@ public final class JobStore { out.endTag(null, "job-info"); out.endDocument(); - mJobsFile.finishWrite(fos); + file.finishWrite(fos); } catch (IOException e) { if (DEBUG) { Slog.v(TAG, "Error writing out job data.", e); @@ -533,9 +679,9 @@ public final class JobStore { Slog.d(TAG, "Error persisting bundle.", e); } } finally { - mPersistInfo.countAllJobsSaved = numJobs; - mPersistInfo.countSystemServerJobsSaved = numSystemJobs; - mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs; + mPersistInfo.countAllJobsSaved += numJobs; + mPersistInfo.countSystemServerJobsSaved += numSystemJobs; + mPersistInfo.countSystemSyncManagerJobsSaved += numSyncJobs; } } @@ -720,49 +866,82 @@ public final class JobStore { @Override public void run() { + if (!mJobFileDirectory.isDirectory()) { + Slog.wtf(TAG, "jobs directory isn't a directory O.O"); + mJobFileDirectory.mkdirs(); + return; + } + int numJobs = 0; int numSystemJobs = 0; int numSyncJobs = 0; List<JobStatus> jobs; - try (FileInputStream fis = mJobsFile.openRead()) { - synchronized (mLock) { - jobs = readJobMapImpl(fis, rtcGood); - if (jobs != null) { - long now = sElapsedRealtimeClock.millis(); - for (int i=0; i<jobs.size(); i++) { - JobStatus js = jobs.get(i); - js.prepareLocked(); - js.enqueueTime = now; - this.jobSet.add(js); - - numJobs++; - if (js.getUid() == Process.SYSTEM_UID) { - numSystemJobs++; - if (isSyncJob(js)) { - numSyncJobs++; + final File[] files; + try { + files = mJobFileDirectory.listFiles(); + } catch (SecurityException e) { + Slog.wtf(TAG, "Not allowed to read job file directory", e); + return; + } + if (files == null) { + Slog.wtfStack(TAG, "Couldn't get job file list"); + return; + } + boolean needFileMigration = false; + long now = sElapsedRealtimeClock.millis(); + for (File file : files) { + final AtomicFile aFile = createJobFile(file); + try (FileInputStream fis = aFile.openRead()) { + synchronized (mLock) { + jobs = readJobMapImpl(fis, rtcGood); + if (jobs != null) { + for (int i = 0; i < jobs.size(); i++) { + JobStatus js = jobs.get(i); + js.prepareLocked(); + js.enqueueTime = now; + this.jobSet.add(js); + + numJobs++; + if (js.getUid() == Process.SYSTEM_UID) { + numSystemJobs++; + if (isSyncJob(js)) { + numSyncJobs++; + } } } } } + } catch (FileNotFoundException e) { + // mJobFileDirectory.listFiles() gave us this file...why can't we find it??? + Slog.e(TAG, "Could not find jobs file: " + file.getName()); + } catch (XmlPullParserException | IOException e) { + Slog.wtf(TAG, "Error in " + file.getName(), e); + } catch (Exception e) { + // Crashing at this point would result in a boot loop, so live with a general + // Exception for system stability's sake. + Slog.wtf(TAG, "Unexpected exception", e); } - } catch (FileNotFoundException e) { - if (DEBUG) { - Slog.d(TAG, "Could not find jobs file, probably there was nothing to load."); - } - } catch (XmlPullParserException | IOException e) { - Slog.wtf(TAG, "Error jobstore xml.", e); - } catch (Exception e) { - // Crashing at this point would result in a boot loop, so live with a general - // Exception for system stability's sake. - Slog.wtf(TAG, "Unexpected exception", e); - } finally { - if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once. - mPersistInfo.countAllJobsLoaded = numJobs; - mPersistInfo.countSystemServerJobsLoaded = numSystemJobs; - mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs; + if (mUseSplitFiles) { + if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) { + // We're supposed to be using the split file architecture, but we still have + // the old job file around. Fully migrate and remove the old file. + needFileMigration = true; + } + } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) { + // We're supposed to be using the legacy single file architecture, but we still + // have some job split files around. Fully migrate and remove the split files. + needFileMigration = true; } } + if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once. + mPersistInfo.countAllJobsLoaded = numJobs; + mPersistInfo.countSystemServerJobsLoaded = numSystemJobs; + mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs; + } Slog.i(TAG, "Read " + numJobs + " jobs"); + if (needFileMigration) { + migrateJobFilesAsync(); + } } private List<JobStatus> readJobMapImpl(InputStream fis, boolean rtcIsGood) diff --git a/core/api/current.txt b/core/api/current.txt index bba189f229d9..ee7cdefa77a1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32542,7 +32542,7 @@ package android.os { method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle); method public android.os.Bundle getUserRestrictions(); method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); - method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.List<android.os.UserHandle> getVisibleUsers(); + method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers(); method public boolean hasUserRestriction(String); method public boolean isDemoUser(); method public static boolean isHeadlessSystemUserMode(); @@ -35801,6 +35801,7 @@ package android.provider { field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS"; field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS"; + field public static final String ACTION_MEMTAG_SETTINGS = "android.settings.MEMTAG_SETTINGS"; field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS"; field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; field public static final String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS"; @@ -44095,8 +44096,8 @@ package android.telephony { field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED = 3; // 0x3 field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED = 7; // 0x7 field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR = 8; // 0x8 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13; // 0xd field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED = 10; // 0xa - field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; // 0xd field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB = 14; // 0xe field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5 diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 7103806fa751..8ec313ecdd09 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -30,7 +30,7 @@ per-file ProfilerInfo* = file:/services/core/java/com/android/server/am/OWNERS per-file Service* = file:/services/core/java/com/android/server/am/OWNERS per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS -per-file UiAutomation* = file:/services/accessibility/OWNERS +per-file *UiAutomation* = file:/services/accessibility/OWNERS per-file GameManager* = file:/GAME_MANAGER_OWNERS per-file GameMode* = file:/GAME_MANAGER_OWNERS per-file GameState* = file:/GAME_MANAGER_OWNERS diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 30a6c311bd1e..ee14708c38a8 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -176,7 +176,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeStrongBinder(mClient.asBinder()); final boolean writeActivityToken = mActivityToken != null; dest.writeBoolean(writeActivityToken); if (writeActivityToken) { @@ -192,7 +191,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** Read from Parcel. */ private ClientTransaction(Parcel in) { - mClient = (IApplicationThread) in.readStrongBinder(); final boolean readActivityToken = in.readBoolean(); if (readActivityToken) { mActivityToken = in.readStrongBinder(); diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 63306612fdaf..1929a4d562d4 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -536,8 +536,8 @@ public final class BinderProxy implements IBinder { mWarnOnBlocking = false; warnOnBlocking = false; - if (Build.IS_USERDEBUG) { - // Log this as a WTF on userdebug builds. + if (Build.IS_USERDEBUG || Build.IS_ENG) { + // Log this as a WTF on userdebug and eng builds. Log.wtf(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY", new Throwable()); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 8eaa5ad7fe94..a887f2a6ef29 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -130,7 +130,7 @@ interface IUserManager { boolean isUserRunning(int userId); boolean isUserForeground(int userId); boolean isUserVisible(int userId); - List<UserHandle> getVisibleUsers(); + int[] getVisibleUsers(); 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/UserManager.java b/core/java/android/os/UserManager.java index 3d20d6373ae2..a38d9daee74a 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2904,12 +2904,19 @@ public class UserManager { */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.INTERACT_ACROSS_USERS}) - public @NonNull List<UserHandle> getVisibleUsers() { + public @NonNull Set<UserHandle> getVisibleUsers() { + ArraySet<UserHandle> result = new ArraySet<>(); try { - return mService.getVisibleUsers(); + int[] visibleUserIds = mService.getVisibleUsers(); + if (visibleUserIds != null) { + for (int userId : visibleUserIds) { + result.add(UserHandle.of(userId)); + } + } } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } + return result; } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fab6f7b97790..5865437d855c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -675,6 +675,22 @@ public final class Settings { "android.settings.WIFI_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of MTE. + * <p> + * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain + * classes of security problems at a small runtime performance cost overhead. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MEMTAG_SETTINGS = + "android.settings.MEMTAG_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of a static IP * address for Wi-Fi. * <p> diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java index 47b16a351806..d2a4ae282061 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -55,6 +55,20 @@ public abstract class ControlsProviderService extends Service { "android.service.controls.ControlsProviderService"; /** + * Manifest metadata to show a custom embedded activity as part of device controls. + * + * The value of this metadata must be the {@link ComponentName} as a string of an activity in + * the same package that will be launched as part of a TaskView. + * + * The activity must be exported, enabled and protected by + * {@link Manifest.permission.BIND_CONTROLS}. + * + * @hide + */ + public static final String META_DATA_PANEL_ACTIVITY = + "android.service.controls.META_DATA_PANEL_ACTIVITY"; + + /** * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 37fc9f288c3e..104570d03b42 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -575,6 +575,7 @@ public abstract class WallpaperService extends Service { */ public void reportEngineShown(boolean waitForEngineShown) { if (mIWallpaperEngine.mShownReported) return; + Trace.beginSection("WPMS.reportEngineShown-" + waitForEngineShown); Log.d(TAG, "reportEngineShown: shouldWait=" + waitForEngineShown); if (!waitForEngineShown) { Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN); @@ -587,6 +588,7 @@ public abstract class WallpaperService extends Service { mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5)); } } + Trace.endSection(); } /** @@ -1259,7 +1261,9 @@ public abstract class WallpaperService extends Service { didSurface = true; if (DEBUG) Log.v(TAG, "onSurfaceCreated(" + mSurfaceHolder + "): " + this); + Trace.beginSection("WPMS.Engine.onSurfaceCreated"); onSurfaceCreated(mSurfaceHolder); + Trace.endSection(); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { @@ -1285,8 +1289,10 @@ public abstract class WallpaperService extends Service { + ", " + mCurWidth + ", " + mCurHeight + "): " + this); didSurface = true; + Trace.beginSection("WPMS.Engine.onSurfaceChanged"); onSurfaceChanged(mSurfaceHolder, mFormat, mCurWidth, mCurHeight); + Trace.endSection(); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { @@ -1303,11 +1309,15 @@ public abstract class WallpaperService extends Service { if (DEBUG) { Log.v(TAG, "dispatching insets=" + windowInsets); } + Trace.beginSection("WPMS.Engine.onApplyWindowInsets"); onApplyWindowInsets(windowInsets); + Trace.endSection(); } if (redrawNeeded) { + Trace.beginSection("WPMS.Engine.onSurfaceRedrawNeeded"); onSurfaceRedrawNeeded(mSurfaceHolder); + Trace.endSection(); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { @@ -1332,11 +1342,15 @@ public abstract class WallpaperService extends Service { // the state to get them to notice. if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: " + this); + Trace.beginSection("WPMS.Engine.onVisibilityChanged-true"); onVisibilityChanged(true); + Trace.endSection(); } if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: " + this); + Trace.beginSection("WPMS.Engine.onVisibilityChanged-false"); onVisibilityChanged(false); + Trace.endSection(); } } finally { mIsCreating = false; @@ -1421,12 +1435,16 @@ public abstract class WallpaperService extends Service { mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); + Trace.beginSection("WPMS.Engine.onCreate"); onCreate(mSurfaceHolder); + Trace.endSection(); mInitializing = false; mReportedVisible = false; + Trace.beginSection("WPMS.Engine.updateSurface"); updateSurface(false, false, false); + Trace.endSection(); } /** @@ -2236,14 +2254,15 @@ public abstract class WallpaperService extends Service { public void reportShown() { if (!mShownReported) { mShownReported = true; + Trace.beginSection("WPMS.mConnection.engineShown"); try { mConnection.engineShown(this); Log.d(TAG, "Wallpaper has updated the surface:" + mWallpaperManager.getWallpaperInfo()); } catch (RemoteException e) { Log.w(TAG, "Wallpaper host disappeared", e); - return; } + Trace.endSection(); } } @@ -2285,6 +2304,27 @@ public abstract class WallpaperService extends Service { return mEngine == null ? null : SurfaceControl.mirrorSurface(mEngine.mSurfaceControl); } + private void doAttachEngine() { + Trace.beginSection("WPMS.onCreateEngine"); + Engine engine = onCreateEngine(); + Trace.endSection(); + mEngine = engine; + Trace.beginSection("WPMS.mConnection.attachEngine-" + mDisplayId); + try { + mConnection.attachEngine(this, mDisplayId); + } catch (RemoteException e) { + engine.detach(); + Log.w(TAG, "Wallpaper host disappeared", e); + return; + } finally { + Trace.endSection(); + } + mActiveEngines.add(engine); + Trace.beginSection("WPMS.engine.attach"); + engine.attach(this); + Trace.endSection(); + } + private void doDetachEngine() { mActiveEngines.remove(mEngine); mEngine.detach(); @@ -2310,21 +2350,15 @@ public abstract class WallpaperService extends Service { } switch (message.what) { case DO_ATTACH: { - Engine engine = onCreateEngine(); - mEngine = engine; - try { - mConnection.attachEngine(this, mDisplayId); - } catch (RemoteException e) { - engine.detach(); - Log.w(TAG, "Wallpaper host disappeared", e); - return; - } - mActiveEngines.add(engine); - engine.attach(this); + Trace.beginSection("WPMS.DO_ATTACH"); + doAttachEngine(); + Trace.endSection(); return; } case DO_DETACH: { + Trace.beginSection("WPMS.DO_DETACH"); doDetachEngine(); + Trace.endSection(); return; } case DO_SET_DESIRED_SIZE: { @@ -2405,7 +2439,9 @@ public abstract class WallpaperService extends Service { } } break; case MSG_REPORT_SHOWN: { + Trace.beginSection("WPMS.MSG_REPORT_SHOWN"); reportShown(); + Trace.endSection(); } break; default : Log.w(TAG, "Unknown message type " + message.what); @@ -2429,8 +2465,10 @@ public abstract class WallpaperService extends Service { public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId, @SetWallpaperFlags int which) { + Trace.beginSection("WPMS.ServiceWrapper.attach"); mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId); + Trace.endSection(); } @Override @@ -2441,16 +2479,20 @@ public abstract class WallpaperService extends Service { @Override public void onCreate() { + Trace.beginSection("WPMS.onCreate"); super.onCreate(); + Trace.endSection(); } @Override public void onDestroy() { + Trace.beginSection("WPMS.onDestroy"); super.onDestroy(); for (int i=0; i<mActiveEngines.size(); i++) { mActiveEngines.get(i).detach(); } mActiveEngines.clear(); + Trace.endSection(); } /** diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index 9b91cf2e9db6..a25e0351df83 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -89,8 +89,6 @@ public final class BackNavigationInfo implements Parcelable { @Nullable private final IOnBackInvokedCallback mOnBackInvokedCallback; private final boolean mPrepareRemoteAnimation; - @Nullable - private WindowContainerToken mDepartingWindowContainerToken; /** * Create a new {@link BackNavigationInfo} instance. @@ -100,20 +98,15 @@ public final class BackNavigationInfo implements Parcelable { * back preview. * @param onBackInvokedCallback The back callback registered by the current top level window. * @param departingWindowContainerToken The {@link WindowContainerToken} of departing window. - * @param isPrepareRemoteAnimation Return whether the core is preparing a back gesture - * animation, if true, the caller of startBackNavigation should - * be expected to receive an animation start callback. */ private BackNavigationInfo(@BackTargetType int type, @Nullable RemoteCallback onBackNavigationDone, @Nullable IOnBackInvokedCallback onBackInvokedCallback, - boolean isPrepareRemoteAnimation, - @Nullable WindowContainerToken departingWindowContainerToken) { + boolean isPrepareRemoteAnimation) { mType = type; mOnBackNavigationDone = onBackNavigationDone; mOnBackInvokedCallback = onBackInvokedCallback; mPrepareRemoteAnimation = isPrepareRemoteAnimation; - mDepartingWindowContainerToken = departingWindowContainerToken; } private BackNavigationInfo(@NonNull Parcel in) { @@ -121,7 +114,6 @@ public final class BackNavigationInfo implements Parcelable { mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR); mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder()); mPrepareRemoteAnimation = in.readBoolean(); - mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR); } @Override @@ -130,7 +122,6 @@ public final class BackNavigationInfo implements Parcelable { dest.writeTypedObject(mOnBackNavigationDone, flags); dest.writeStrongInterface(mOnBackInvokedCallback); dest.writeBoolean(mPrepareRemoteAnimation); - dest.writeTypedObject(mDepartingWindowContainerToken, flags); } /** @@ -164,18 +155,6 @@ public final class BackNavigationInfo implements Parcelable { } /** - * Returns the {@link WindowContainerToken} of the highest container in the hierarchy being - * removed. - * <p> - * For example, if an Activity is the last one of its Task, the Task's token will be given. - * Otherwise, it will be the Activity's token. - */ - @Nullable - public WindowContainerToken getDepartingWindowContainerToken() { - return mDepartingWindowContainerToken; - } - - /** * Callback to be called when the back preview is finished in order to notify the server that * it can clean up the resources created for the animation. * @@ -212,7 +191,6 @@ public final class BackNavigationInfo implements Parcelable { + "mType=" + typeToString(mType) + " (" + mType + ")" + ", mOnBackNavigationDone=" + mOnBackNavigationDone + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback - + ", mWindowContainerToken=" + mDepartingWindowContainerToken + '}'; } @@ -248,8 +226,6 @@ public final class BackNavigationInfo implements Parcelable { @Nullable private IOnBackInvokedCallback mOnBackInvokedCallback = null; private boolean mPrepareRemoteAnimation; - @Nullable - private WindowContainerToken mDepartingWindowContainerToken = null; /** * @see BackNavigationInfo#getType() @@ -285,20 +261,12 @@ public final class BackNavigationInfo implements Parcelable { } /** - * @see BackNavigationInfo#getDepartingWindowContainerToken() - */ - public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) { - mDepartingWindowContainerToken = windowContainerToken; - } - - /** * Builds and returns an instance of {@link BackNavigationInfo} */ public BackNavigationInfo build() { return new BackNavigationInfo(mType, mOnBackNavigationDone, mOnBackInvokedCallback, - mPrepareRemoteAnimation, - mDepartingWindowContainerToken); + mPrepareRemoteAnimation); } } } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 0956a71bd92d..c2da638aca8d 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -138,8 +138,11 @@ public final class TransitionInfo implements Parcelable { /** The container is a system window, excluding wallpaper and input-method. */ public static final int FLAG_IS_SYSTEM_WINDOW = 1 << 16; + /** The window was animated by back gesture. */ + public static final int FLAG_BACK_GESTURE_ANIMATED = 1 << 17; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 17; + public static final int FLAG_FIRST_CUSTOM = 1 << 18; /** The change belongs to a window that won't contain activities. */ public static final int FLAGS_IS_NON_APP_WINDOW = @@ -165,6 +168,7 @@ public final class TransitionInfo implements Parcelable { FLAG_IS_BEHIND_STARTING_WINDOW, FLAG_IS_OCCLUDED, FLAG_IS_SYSTEM_WINDOW, + FLAG_BACK_GESTURE_ANIMATED, FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -380,6 +384,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_IS_SYSTEM_WINDOW) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_SYSTEM_WINDOW"); } + if ((flags & FLAG_BACK_GESTURE_ANIMATED) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FLAG_BACK_GESTURE_ANIMATED"); + } if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index d9ca16ea8748..0798110134f8 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -19,6 +19,7 @@ #define LOG_NDEBUG 1 #include <android-base/macros.h> +#include <android-base/parsebool.h> #include <android-base/properties.h> #include <android/graphics/jni_runtime.h> #include <android_runtime/AndroidRuntime.h> @@ -52,6 +53,8 @@ using namespace android; using android::base::GetBoolProperty; using android::base::GetProperty; +using android::base::ParseBool; +using android::base::ParseBoolResult; extern int register_android_os_Binder(JNIEnv* env); extern int register_android_os_Process(JNIEnv* env); @@ -703,17 +706,24 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p // Read if we are using the profile configuration, do this at the start since the last ART args // take precedence. - property_get("dalvik.vm.profilebootclasspath", propBuf, ""); - std::string profile_boot_class_path_flag = propBuf; - // Empty means the property is unset and we should default to the phenotype property. - // The possible values are {"true", "false", ""} - if (profile_boot_class_path_flag.empty()) { - profile_boot_class_path_flag = server_configurable_flags::GetServerConfigurableFlag( - RUNTIME_NATIVE_BOOT_NAMESPACE, - PROFILE_BOOT_CLASS_PATH, - /*default_value=*/ ""); + std::string profile_boot_class_path_flag = + server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE, + PROFILE_BOOT_CLASS_PATH, + /*default_value=*/""); + bool profile_boot_class_path; + switch (ParseBool(profile_boot_class_path_flag)) { + case ParseBoolResult::kError: + // Default to the system property. + profile_boot_class_path = + GetBoolProperty("dalvik.vm.profilebootclasspath", /*default_value=*/false); + break; + case ParseBoolResult::kTrue: + profile_boot_class_path = true; + break; + case ParseBoolResult::kFalse: + profile_boot_class_path = false; + break; } - const bool profile_boot_class_path = (profile_boot_class_path_flag == "true"); if (profile_boot_class_path) { addOption("-Xcompiler-option"); addOption("--count-hotness-in-compiled-code"); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 06d7cddf3148..c3623a28c17a 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -210,7 +210,7 @@ public final class TunerSessionTest { mTunerSessions[0].setMuted(/* mute= */ false); - assertWithMessage("Session mute state after setting muted %s", false) + assertWithMessage("Session mute state after setting unmuted") .that(mTunerSessions[0].isMuted()).isFalse(); } @@ -220,7 +220,7 @@ public final class TunerSessionTest { mTunerSessions[0].setMuted(/* mute= */ true); - assertWithMessage("Session mute state after setting muted %s", true) + assertWithMessage("Session mute state after setting muted") .that(mTunerSessions[0].isMuted()).isTrue(); } diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java index 392e140f24e0..ad1b8727d8a7 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TestUtils.java @@ -22,6 +22,7 @@ import android.hardware.broadcastradio.V2_0.VendorKeyValue; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; +import android.util.ArrayMap; import java.util.ArrayList; import java.util.HashMap; @@ -32,14 +33,33 @@ final class TestUtils { throw new UnsupportedOperationException("TestUtils class is noninstantiable"); } + static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) { + return new RadioManager.ProgramInfo(selector, + selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null, + /* infoFlags= */ 0, signalQuality, + new RadioMetadata.Builder().build(), new ArrayMap<>()); + } + static RadioManager.ProgramInfo makeProgramInfo(int programType, ProgramSelector.Identifier identifier, int signalQuality) { // Note: If you set new fields, check if programInfoToHal() needs to be updated as well. - return new RadioManager.ProgramInfo(new ProgramSelector(programType, identifier, null, - null), null, null, null, 0, signalQuality, new RadioMetadata.Builder().build(), + return new RadioManager.ProgramInfo(makeProgramSelector(programType, identifier), null, + null, null, 0, signalQuality, new RadioMetadata.Builder().build(), new HashMap<String, String>()); } + static ProgramSelector makeFmSelector(long freq) { + return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, + new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, + freq)); + } + + static ProgramSelector makeProgramSelector(int programType, + ProgramSelector.Identifier identifier) { + return new ProgramSelector(programType, identifier, /* secondaryIds= */ null, + /* vendorIds= */ null); + } + static ProgramInfo programInfoToHal(RadioManager.ProgramInfo info) { // Note that because Convert does not by design provide functions for all conversions, this // function only copies fields that are set by makeProgramInfo(). @@ -57,10 +77,22 @@ final class TestUtils { android.hardware.broadcastradio.V2_0.ProgramSelector halSelector = new android.hardware.broadcastradio.V2_0.ProgramSelector(); halSelector.primaryId = halId; - halSelector.secondaryIds = new ArrayList<>(); + halSelector.secondaryIds = new ArrayList<ProgramIdentifier>(); return halSelector; } + static ProgramInfo makeHalProgramInfo( + android.hardware.broadcastradio.V2_0.ProgramSelector hwSel, int hwSignalQuality) { + ProgramInfo hwInfo = new ProgramInfo(); + hwInfo.selector = hwSel; + hwInfo.logicallyTunedTo = hwSel.primaryId; + hwInfo.physicallyTunedTo = hwSel.primaryId; + hwInfo.signalQuality = hwSignalQuality; + hwInfo.relatedContent = new ArrayList<>(); + hwInfo.metadata = new ArrayList<>(); + return hwInfo; + } + static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) { VendorKeyValue vendorKeyValue = new VendorKeyValue(); vendorKeyValue.key = vendorKey; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java new file mode 100644 index 000000000000..9b62dc75419f --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -0,0 +1,463 @@ +/* + * 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.broadcastradio.hal2; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.graphics.Bitmap; +import android.hardware.broadcastradio.V2_0.Constants; +import android.hardware.broadcastradio.V2_0.IBroadcastRadio; +import android.hardware.broadcastradio.V2_0.ITunerCallback; +import android.hardware.broadcastradio.V2_0.ITunerSession; +import android.hardware.broadcastradio.V2_0.IdentifierType; +import android.hardware.broadcastradio.V2_0.ProgramInfo; +import android.hardware.broadcastradio.V2_0.Result; +import android.hardware.radio.ProgramList; +import android.hardware.radio.ProgramSelector; +import android.hardware.radio.RadioManager; +import android.hardware.radio.RadioTuner; +import android.util.ArraySet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.verification.VerificationWithTimeout; + +import java.util.ArrayList; + +/** + * Tests for HIDL HAL TunerSession. + */ +@RunWith(MockitoJUnitRunner.class) +public final class TunerSessionHidlTest { + + private static final VerificationWithTimeout CALLBACK_TIMEOUT = + timeout(/* millis= */ 200); + private static final int SIGNAL_QUALITY = 1; + private static final long AM_FM_FREQUENCY_SPACING = 500; + private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100}; + private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR = + new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM, + /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100, + /* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false, + /* ea= */ false); + private static final RadioManager.BandConfig FM_BAND_CONFIG = + new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR); + private static final int UNSUPPORTED_CONFIG_FLAG = 0; + + private final Object mLock = new Object(); + private RadioModule mRadioModule; + private ITunerCallback mHalTunerCallback; + private ProgramInfo mHalCurrentInfo; + private TunerSession[] mTunerSessions; + + @Mock private IBroadcastRadio mBroadcastRadioMock; + @Mock ITunerSession mHalTunerSessionMock; + private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks; + + @Before + public void setup() throws Exception { + mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties( + /* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "", + /* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0, + /* numAudioSources= */ 0, /* isInitializationRequired= */ false, + /* isCaptureSupported= */ false, /* bands= */ null, /* isBgScanSupported= */ false, + new int[] {}, new int[] {}, + /* dabFrequencyTable= */ null, /* vendorInfo= */ null), mLock); + + doAnswer(invocation -> { + mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; + IBroadcastRadio.openSessionCallback cb = (IBroadcastRadio.openSessionCallback) + invocation.getArguments()[1]; + cb.onValues(Result.OK, mHalTunerSessionMock); + return null; + }).when(mBroadcastRadioMock).openSession(any(), any()); + + doAnswer(invocation -> { + android.hardware.broadcastradio.V2_0.ProgramSelector halSel = + (android.hardware.broadcastradio.V2_0.ProgramSelector) + invocation.getArguments()[0]; + mHalCurrentInfo = TestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY); + if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY) { + return Result.NOT_SUPPORTED; + } + mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); + return Result.OK; + }).when(mHalTunerSessionMock).tune(any()); + + doAnswer(invocation -> { + if ((boolean) invocation.getArguments()[0]) { + mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING; + } else { + mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING; + } + mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); + return Result.OK; + }).when(mHalTunerSessionMock).step(anyBoolean()); + + doAnswer(invocation -> { + if (mHalCurrentInfo == null) { + android.hardware.broadcastradio.V2_0.ProgramSelector placeHolderSelector = + TestUtils.makeHalFmSelector(/* freq= */ 97300); + + mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector); + return Result.OK; + } + mHalCurrentInfo.selector.primaryId.value = getSeekFrequency( + mHalCurrentInfo.selector.primaryId.value, + !(boolean) invocation.getArguments()[0]); + mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId; + mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo); + return Result.OK; + }).when(mHalTunerSessionMock).scan(anyBoolean(), anyBoolean()); + + when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0)); + } + + @Test + public void openSession_withMultipleSessions() throws Exception { + int numSessions = 3; + + openAidlClients(numSessions); + + for (int index = 0; index < numSessions; index++) { + assertWithMessage("Session of index %s close state", index) + .that(mTunerSessions[index].isClosed()).isFalse(); + } + } + + @Test + public void setConfiguration() throws Exception { + openAidlClients(/* numClients= */ 1); + + mTunerSessions[0].setConfiguration(FM_BAND_CONFIG); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG); + } + + @Test + public void getConfiguration() throws Exception { + openAidlClients(/* numClients= */ 1); + mTunerSessions[0].setConfiguration(FM_BAND_CONFIG); + + RadioManager.BandConfig config = mTunerSessions[0].getConfiguration(); + + assertWithMessage("Session configuration").that(config) + .isEqualTo(FM_BAND_CONFIG); + } + + @Test + public void setMuted_withUnmuted() throws Exception { + openAidlClients(/* numClients= */ 1); + + mTunerSessions[0].setMuted(/* mute= */ false); + + assertWithMessage("Session mute state after setting unmuted") + .that(mTunerSessions[0].isMuted()).isFalse(); + } + + @Test + public void setMuted_withMuted() throws Exception { + openAidlClients(/* numClients= */ 1); + + mTunerSessions[0].setMuted(/* mute= */ true); + + assertWithMessage("Session mute state after setting muted") + .that(mTunerSessions[0].isMuted()).isTrue(); + } + + @Test + public void close_withOneSession() throws Exception { + openAidlClients(/* numClients= */ 1); + + mTunerSessions[0].close(); + + assertWithMessage("Close state of broadcast radio service session") + .that(mTunerSessions[0].isClosed()).isTrue(); + } + + @Test + public void close_withOnlyOneSession_withMultipleSessions() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + int closeIdx = 0; + + mTunerSessions[closeIdx].close(); + + for (int index = 0; index < numSessions; index++) { + if (index == closeIdx) { + assertWithMessage( + "Close state of broadcast radio service session of index %s", index) + .that(mTunerSessions[index].isClosed()).isTrue(); + } else { + assertWithMessage( + "Close state of broadcast radio service session of index %s", index) + .that(mTunerSessions[index].isClosed()).isFalse(); + } + } + } + + @Test + public void close_withOneSession_withError() throws Exception { + openAidlClients(/* numClients= */ 1); + int errorCode = RadioTuner.ERROR_SERVER_DIED; + + mTunerSessions[0].close(errorCode); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onError(errorCode); + assertWithMessage("Close state of broadcast radio service session") + .that(mTunerSessions[0].isClosed()).isTrue(); + } + + @Test + public void closeSessions_withMultipleSessions_withError() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + + int errorCode = RadioTuner.ERROR_SERVER_DIED; + mRadioModule.closeSessions(errorCode); + + for (int index = 0; index < numSessions; index++) { + verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT).onError(errorCode); + assertWithMessage("Close state of broadcast radio service session of index %s", index) + .that(mTunerSessions[index].isClosed()).isTrue(); + } + } + + @Test + public void tune_withOneSession() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); + RadioManager.ProgramInfo tuneInfo = + TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY); + + mTunerSessions[0].tune(initialSel); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo); + } + + @Test + public void tune_withMultipleSessions() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); + RadioManager.ProgramInfo tuneInfo = + TestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY); + + mTunerSessions[0].tune(initialSel); + + for (int index = 0; index < numSessions; index++) { + verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT) + .onCurrentProgramInfoChanged(tuneInfo); + } + } + + @Test + public void tune_withUnsupportedSelector_throwsException() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector( + ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300)); + + UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class, + () -> mTunerSessions[0].tune(unsupportedSelector)); + + assertWithMessage("Exception for tuning on unsupported program selector") + .that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED"); + } + + @Test + public void step_withDirectionUp() throws Exception { + long initFreq = AM_FM_FREQUENCY_LIST[1]; + ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq); + RadioManager.ProgramInfo stepUpInfo = TestUtils.makeProgramInfo( + TestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING), SIGNAL_QUALITY); + openAidlClients(/* numClients= */ 1); + mHalCurrentInfo = TestUtils.makeHalProgramInfo( + Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY); + + mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) + .onCurrentProgramInfoChanged(stepUpInfo); + } + + @Test + public void step_withDirectionDown() throws Exception { + long initFreq = AM_FM_FREQUENCY_LIST[1]; + ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq); + RadioManager.ProgramInfo stepDownInfo = TestUtils.makeProgramInfo( + TestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING), + SIGNAL_QUALITY); + openAidlClients(/* numClients= */ 1); + mHalCurrentInfo = TestUtils.makeHalProgramInfo( + Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY); + + mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) + .onCurrentProgramInfoChanged(stepDownInfo); + } + + @Test + public void scan_withDirectionUp() throws Exception { + long initFreq = AM_FM_FREQUENCY_LIST[2]; + ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq); + RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo( + TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)), + SIGNAL_QUALITY); + openAidlClients(/* numClients= */ 1); + mHalCurrentInfo = TestUtils.makeHalProgramInfo( + Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY); + + mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) + .onCurrentProgramInfoChanged(scanUpInfo); + } + + @Test + public void scan_callsOnTuneFailedWhenTimeout() throws Exception { + int numSessions = 2; + openAidlClients(numSessions); + + mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false); + + for (int index = 0; index < numSessions; index++) { + verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT) + .onTuneFailed(eq(Result.TIMEOUT), any()); + } + } + + @Test + public void scan_withDirectionDown() throws Exception { + long initFreq = AM_FM_FREQUENCY_LIST[2]; + ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq); + RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo( + TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)), + SIGNAL_QUALITY); + openAidlClients(/* numClients= */ 1); + mHalCurrentInfo = TestUtils.makeHalProgramInfo( + Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY); + + mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false); + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) + .onCurrentProgramInfoChanged(scanUpInfo); + } + + @Test + public void cancel() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); + mTunerSessions[0].tune(initialSel); + + mTunerSessions[0].cancel(); + + verify(mHalTunerSessionMock).cancel(); + } + + @Test + public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception { + openAidlClients(/* numClients= */ 1); + int imageId = Constants.INVALID_IMAGE; + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> { + mTunerSessions[0].getImage(imageId); + }); + + assertWithMessage("Exception for getting image with invalid ID") + .that(thrown).hasMessageThat().contains("Image ID is missing"); + } + + @Test + public void getImage_withValidId() throws Exception { + openAidlClients(/* numClients= */ 1); + int imageId = 1; + + Bitmap imageTest = mTunerSessions[0].getImage(imageId); + + assertWithMessage("Null image").that(imageTest).isEqualTo(null); + } + + @Test + public void startBackgroundScan() throws Exception { + openAidlClients(/* numClients= */ 1); + + mTunerSessions[0].startBackgroundScan(); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onBackgroundScanComplete(); + } + + @Test + public void stopProgramListUpdates() throws Exception { + openAidlClients(/* numClients= */ 1); + ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); + mTunerSessions[0].startProgramListUpdates(aidlFilter); + + mTunerSessions[0].stopProgramListUpdates(); + + verify(mHalTunerSessionMock).stopProgramListUpdates(); + } + + private void openAidlClients(int numClients) throws Exception { + mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients]; + mTunerSessions = new TunerSession[numClients]; + for (int index = 0; index < numClients; index++) { + mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class); + mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]); + } + } + + private long getSeekFrequency(long currentFrequency, boolean seekDown) { + long seekFrequency; + if (seekDown) { + seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1]; + for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) { + if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) { + seekFrequency = AM_FM_FREQUENCY_LIST[i]; + break; + } + } + } else { + seekFrequency = AM_FM_FREQUENCY_LIST[0]; + for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) { + if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) { + seekFrequency = AM_FM_FREQUENCY_LIST[index]; + break; + } + } + } + return seekFrequency; + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index b292d7dfafe2..a0ed0266d24b 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -294,10 +294,9 @@ public class TransactionParcelTests { StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */); - IApplicationThread appThread = new StubAppThread(); Binder activityToken = new Binder(); - ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken); + ClientTransaction transaction = ClientTransaction.obtain(null, activityToken); transaction.addCallback(callback1); transaction.addCallback(callback2); transaction.setLifecycleStateRequest(lifecycleRequest); @@ -318,10 +317,9 @@ public class TransactionParcelTests { ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( config()); - IApplicationThread appThread = new StubAppThread(); Binder activityToken = new Binder(); - ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken); + ClientTransaction transaction = ClientTransaction.obtain(null, activityToken); transaction.addCallback(callback1); transaction.addCallback(callback2); @@ -339,10 +337,9 @@ public class TransactionParcelTests { // Write to parcel StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */); - IApplicationThread appThread = new StubAppThread(); Binder activityToken = new Binder(); - ClientTransaction transaction = ClientTransaction.obtain(appThread, activityToken); + ClientTransaction transaction = ClientTransaction.obtain(null, activityToken); transaction.setLifecycleStateRequest(lifecycleRequest); writeAndPrepareForReading(transaction); @@ -400,286 +397,4 @@ public class TransactionParcelTests { } }; } - - /** Stub implementation of IApplicationThread that can be presented as {@link Binder}. */ - class StubAppThread extends android.app.IApplicationThread.Stub { - - @Override - public void scheduleTransaction(ClientTransaction transaction) throws RemoteException { - } - - @Override - public void scheduleReceiver(Intent intent, ActivityInfo activityInfo, - CompatibilityInfo compatibilityInfo, int i, String s, Bundle bundle, boolean b, - int i1, int i2) throws RemoteException { - } - - @Override - public void scheduleCreateService(IBinder iBinder, ServiceInfo serviceInfo, - CompatibilityInfo compatibilityInfo, int i) throws RemoteException { - } - - @Override - public void scheduleStopService(IBinder iBinder) throws RemoteException { - } - - @Override - public void bindApplication(String s, ApplicationInfo applicationInfo, - String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage, - ProviderInfoList list, ComponentName componentName, ProfilerInfo profilerInfo, - Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher, - IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1, - boolean b2, boolean b3, Configuration configuration, - CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1, - AutofillOptions ao, ContentCaptureOptions co, long[] disableCompatChanges, - SharedMemory serializedSystemFontMap, - long startRequestedElapsedTime, long startRequestedUptime) - throws RemoteException { - } - - @Override - public void scheduleExit() throws RemoteException { - } - - @Override - public void scheduleServiceArgs(IBinder iBinder, ParceledListSlice parceledListSlice) - throws RemoteException { - } - - @Override - public void updateTimeZone() throws RemoteException { - } - - @Override - public void processInBackground() throws RemoteException { - } - - @Override - public void scheduleBindService(IBinder iBinder, Intent intent, boolean b, int i) - throws RemoteException { - } - - @Override - public void scheduleUnbindService(IBinder iBinder, Intent intent) throws RemoteException { - } - - @Override - public void dumpService(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder, - String[] strings) throws RemoteException { - } - - @Override - public void scheduleRegisteredReceiver(IIntentReceiver iIntentReceiver, Intent intent, - int i, String s, Bundle bundle, boolean b, boolean b1, int i1, int i2) - throws RemoteException { - } - - @Override - public void scheduleLowMemory() throws RemoteException { - } - - @Override - public void profilerControl(boolean b, ProfilerInfo profilerInfo, int i) - throws RemoteException { - } - - @Override - public void setSchedulingGroup(int i) throws RemoteException { - } - - @Override - public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo, - int i, int userId, int operatioType) - throws RemoteException { - } - - @Override - public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo, - int userId) throws RemoteException { - } - - @Override - public void scheduleOnNewActivityOptions(IBinder iBinder, Bundle bundle) - throws RemoteException { - } - - @Override - public void scheduleSuicide() throws RemoteException { - } - - @Override - public void dispatchPackageBroadcast(int i, String[] strings) throws RemoteException { - } - - @Override - public void scheduleCrash(String s, int i, Bundle extras) throws RemoteException { - } - - @Override - public void dumpActivity(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder, - String s, String[] strings) throws RemoteException { - } - - @Override - public void clearDnsCache() throws RemoteException { - } - - @Override - public void updateHttpProxy() throws RemoteException { - } - - @Override - public void setCoreSettings(Bundle bundle) throws RemoteException { - } - - @Override - public void updatePackageCompatibilityInfo(String s, CompatibilityInfo compatibilityInfo) - throws RemoteException { - } - - @Override - public void scheduleTrimMemory(int i) throws RemoteException { - } - - @Override - public void dumpMemInfo(ParcelFileDescriptor parcelFileDescriptor, - Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, boolean b3, - boolean b4, String[] strings) throws RemoteException { - } - - @Override - public void dumpMemInfoProto(ParcelFileDescriptor parcelFileDescriptor, - Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, - boolean b3, String[] strings) throws RemoteException { - } - - @Override - public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings) - throws RemoteException { - } - - @Override - public void dumpCacheInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings) - throws RemoteException { - } - - @Override - public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder, - String[] strings) throws RemoteException { - } - - @Override - public void dumpDbInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings) - throws RemoteException { - } - - @Override - public void unstableProviderDied(IBinder iBinder) throws RemoteException { - } - - @Override - public void requestAssistContextExtras(IBinder iBinder, IBinder iBinder1, int i, int i1, - int i2) throws RemoteException { - } - - @Override - public void scheduleTranslucentConversionComplete(IBinder iBinder, boolean b) - throws RemoteException { - } - - @Override - public void setProcessState(int i) throws RemoteException { - } - - @Override - public void scheduleInstallProvider(ProviderInfo providerInfo) throws RemoteException { - } - - @Override - public void updateTimePrefs(int i) throws RemoteException { - } - - @Override - public void scheduleEnterAnimationComplete(IBinder iBinder) throws RemoteException { - } - - @Override - public void notifyCleartextNetwork(byte[] bytes) throws RemoteException { - } - - @Override - public void startBinderTracking() throws RemoteException { - } - - @Override - public void stopBinderTrackingAndDump(ParcelFileDescriptor parcelFileDescriptor) - throws RemoteException { - } - - @Override - public void scheduleLocalVoiceInteractionStarted(IBinder iBinder, - IVoiceInteractor iVoiceInteractor) throws RemoteException { - } - - @Override - public void handleTrustStorageUpdate() throws RemoteException { - } - - @Override - public void attachAgent(String s) throws RemoteException { - } - - @Override - public void attachStartupAgents(String s) throws RemoteException { - } - - @Override - public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo) - throws RemoteException { - } - - @Override - public void setNetworkBlockSeq(long l) throws RemoteException { - } - - @Override - public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path, - ParcelFileDescriptor fd, RemoteCallback finishCallback) { - } - - @Override - public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) { - } - - @Override - public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) { - } - - @Override - public void requestDirectActions(IBinder activityToken, IVoiceInteractor interactor, - RemoteCallback cancellationCallback, RemoteCallback resultCallback) { - } - - @Override - public void performDirectAction(IBinder activityToken, String actionId, Bundle arguments, - RemoteCallback cancellationCallback, RemoteCallback resultCallback) { - } - - @Override - public void notifyContentProviderPublishStatus(ContentProviderHolder holder, String auth, - int userId, boolean published) { - } - - @Override - public void instrumentWithoutRestart(ComponentName instrumentationName, - Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, - IUiAutomationConnection instrumentationUiConnection, ApplicationInfo targetInfo) { - } - - @Override - public void updateUiTranslationState(IBinder activityToken, int state, - TranslationSpec sourceSpec, TranslationSpec targetSpec, List<AutofillId> viewIds, - UiTranslationSpec uiTranslationSpec) { - } - } } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index c1b4dcc1d3ff..4cc06e33ab62 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2179,12 +2179,6 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, - "-23020844": { - "message": "Back: Reset surfaces", - "level": "DEBUG", - "group": "WM_DEBUG_BACK_PREVIEW", - "at": "com\/android\/server\/wm\/BackNavigationController.java" - }, "-21399771": { "message": "activity %s already destroying, skipping request with reason:%s", "level": "VERBOSE", @@ -3823,12 +3817,6 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "1544805551": { - "message": "Skipping app transition animation. task=%s", - "level": "DEBUG", - "group": "WM_DEBUG_BACK_PREVIEW", - "at": "com\/android\/server\/wm\/Task.java" - }, "1557732761": { "message": "For Intent %s bringing to top: %s", "level": "DEBUG", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 64220c82fd9a..b6327e5d91f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -62,7 +62,6 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.transition.Transitions; import java.util.concurrent.atomic.AtomicBoolean; @@ -82,8 +81,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont SETTING_VALUE_OFF) == SETTING_VALUE_ON; /** Predictive back animation developer option */ private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); - // TODO (b/241808055) Find a appropriate time to remove during refactor - private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS; /** * Max duration to wait for a transition to finish before accepting another gesture start * request. @@ -121,8 +118,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final TouchTracker mTouchTracker = new TouchTracker(); private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>(); - private final Transitions mTransitions; - private BackTransitionHandler mBackTransitionHandler; @VisibleForTesting final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() { @@ -148,11 +143,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, - Context context, - Transitions transitions) { + Context context) { this(shellInit, shellController, shellExecutor, backgroundHandler, - ActivityTaskManager.getService(), context, context.getContentResolver(), - transitions); + ActivityTaskManager.getService(), context, context.getContentResolver()); } @VisibleForTesting @@ -162,8 +155,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler bgHandler, @NonNull IActivityTaskManager activityTaskManager, - Context context, ContentResolver contentResolver, - Transitions transitions) { + Context context, ContentResolver contentResolver) { mShellController = shellController; mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; @@ -171,7 +163,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mContentResolver = contentResolver; mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); - mTransitions = transitions; } @VisibleForTesting @@ -182,10 +173,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void onInit() { setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); createAdapter(); - if (ENABLE_SHELL_TRANSITIONS) { - mBackTransitionHandler = new BackTransitionHandler(this); - mTransitions.addHandler(mBackTransitionHandler); - } mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, this::createExternalInterface, this); @@ -335,17 +322,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - // In legacy transition, it would use `Task.mBackGestureStarted` in core to handle the - // following transition when back callback is invoked. - // If the back callback is not invoked, we should reset the token and finish the whole back - // navigation without waiting the transition. - if (!ENABLE_SHELL_TRANSITIONS) { - finishBackNavigation(); - } else if (!mTriggerBack) { - // reset the token to prevent it consume next transition. - mBackTransitionHandler.setDepartingWindowContainerToken(null); - finishBackNavigation(); - } + finishBackNavigation(); } /** @@ -614,10 +591,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } mTransitionInProgress = true; - if (ENABLE_SHELL_TRANSITIONS) { - mBackTransitionHandler.setDepartingWindowContainerToken( - mBackNavigationInfo.getDepartingWindowContainerToken()); - } mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION); } @@ -626,19 +599,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransitionInProgress = false; } - /** - * This should be called from {@link BackTransitionHandler#startAnimation} when the following - * transition is triggered by the real back callback in {@link #onBackAnimationFinished}. - * Will consume the default transition and finish current back navigation. - */ - void finishTransition(Transitions.TransitionFinishCallback finishCallback) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishTransition()"); - mShellExecutor.execute(() -> { - finishBackNavigation(); - finishCallback.onTransitionFinished(null, null); - }); - } - private void createAdapter() { IBackAnimationRunner runner = new IBackAnimationRunner.Stub() { @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java deleted file mode 100644 index 6d72d9c1f637..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java +++ /dev/null @@ -1,78 +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.wm.shell.back; - -import android.os.IBinder; -import android.view.SurfaceControl; -import android.window.TransitionInfo; -import android.window.TransitionRequestInfo; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.wm.shell.transition.Transitions; - -class BackTransitionHandler implements Transitions.TransitionHandler { - private BackAnimationController mBackAnimationController; - private WindowContainerToken mDepartingWindowContainerToken; - - BackTransitionHandler(@NonNull BackAnimationController backAnimationController) { - mBackAnimationController = backAnimationController; - } - - @Override - public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (mDepartingWindowContainerToken != null) { - final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken); - if (change == null) { - return false; - } - - startTransaction.hide(change.getLeash()); - startTransaction.apply(); - mDepartingWindowContainerToken = null; - mBackAnimationController.finishTransition(finishCallback); - return true; - } - - return false; - } - - @Nullable - @Override - public WindowContainerTransaction handleRequest(@NonNull IBinder transition, - @NonNull TransitionRequestInfo request) { - return null; - } - - @Override - public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - } - - void setDepartingWindowContainerToken( - @Nullable WindowContainerToken departingWindowContainerToken) { - mDepartingWindowContainerToken = departingWindowContainerToken; - } -} - 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 1977dcb81e97..962be9da2111 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 @@ -260,13 +260,12 @@ public abstract class WMShellBaseModule { ShellInit shellInit, ShellController shellController, @ShellMainThread ShellExecutor shellExecutor, - @ShellBackgroundThread Handler backgroundHandler, - Transitions transitions + @ShellBackgroundThread Handler backgroundHandler ) { if (BackAnimationController.IS_ENABLED) { return Optional.of( new BackAnimationController(shellInit, shellController, shellExecutor, - backgroundHandler, context, transitions)); + backgroundHandler, context)); } return Optional.empty(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 928e71f8d3a6..63d4a6f5acd9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -41,6 +41,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL; import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; @@ -395,6 +396,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } + // The back gesture has animated this change before transition happen, so here we don't + // play the animation again. + if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) { + continue; + } // Don't animate anything that isn't independent. if (!TransitionInfo.isIndependent(change, info)) continue; diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt index ead451f07653..4a3284e1953b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt @@ -41,6 +41,7 @@ import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import org.junit.Assert.assertNotNull import java.util.Collections internal object SplitScreenUtils { @@ -50,7 +51,7 @@ internal object SplitScreenUtils { private const val DIVIDER_BAR = "docked_divider_handle" private const val OVERVIEW_SNAPSHOT = "snapshot" private const val GESTURE_STEP_MS = 16L - private const val LONG_PRESS_TIME_MS = 100L + private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") private val notificationScrollerSelector: BySelector @@ -275,13 +276,6 @@ internal object SplitScreenUtils { } } - fun longPress(instrumentation: Instrumentation, point: Point) { - val downTime = SystemClock.uptimeMillis() - touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point) - SystemClock.sleep(LONG_PRESS_TIME_MS) - touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point) - } - fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) { tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) val allApps = tapl.workspace.switchToAllApps() @@ -353,9 +347,11 @@ internal object SplitScreenUtils { Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS ) - longPress(instrumentation, textView.visibleCenter) + assertNotNull("Unable to find the TextView", textView) + textView.click(LONG_PRESS_TIME_MS) val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) + assertNotNull("Unable to find the copy button", copyBtn) copyBtn.click() // Paste text to destinationApp @@ -364,9 +360,11 @@ internal object SplitScreenUtils { Until.findObject(By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS ) - longPress(instrumentation, editText.visibleCenter) + assertNotNull("Unable to find the EditText", editText) + editText.click(LONG_PRESS_TIME_MS) val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) + assertNotNull("Unable to find the paste button", pasteBtn) pasteBtn.click() // Verify text diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 7896247c5f5a..7eccbf42e67d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -64,7 +64,6 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; -import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Rule; @@ -103,9 +102,6 @@ public class BackAnimationControllerTest extends ShellTestCase { private IRemoteAnimationRunner mBackAnimationRunner; @Mock - private Transitions mTransitions; - - @Mock private ShellController mShellController; private BackAnimationController mController; @@ -127,7 +123,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mController = new BackAnimationController(mShellInit, mShellController, mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver, mTransitions); + mContentResolver); mController.setEnableUAnimation(true); mShellInit.init(); mEventTime = 0; @@ -225,7 +221,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mController = new BackAnimationController(shellInit, mShellController, mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver, mTransitions); + mContentResolver); shellInit.init(); mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index b4b908dbff16..161ea255dfb4 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -603,7 +603,7 @@ public final class MediaRouter2 { */ public void transferTo(@NonNull MediaRoute2Info route) { if (isSystemRouter()) { - sManager.selectRoute(mClientPackageName, route); + sManager.transfer(mClientPackageName, route); return; } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index b6f07f43cef5..e403e246f318 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -448,14 +448,16 @@ public final class MediaRouter2Manager { } /** - * Selects media route for the specified package name. + * Transfers a {@link RoutingSessionInfo routing session} belonging to a specified package name + * to a {@link MediaRoute2Info media route}. + * + * <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing + * session based on the provided package name. */ - public void selectRoute(@NonNull String packageName, @NonNull MediaRoute2Info route) { + public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) { Objects.requireNonNull(packageName, "packageName must not be null"); Objects.requireNonNull(route, "route must not be null"); - Log.v(TAG, "Selecting route. packageName= " + packageName + ", route=" + route); - List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName); RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1); transfer(targetSession, route); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 810b408370c7..4193ffad5a0a 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -384,7 +384,7 @@ public class MediaRouter2ManagerTest { MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1); assertThat(routeToSelect).isNotNull(); - mManager.selectRoute(mPackageName, routeToSelect); + mManager.transfer(mPackageName, routeToSelect); assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); assertThat(mManager.getRemoteSessions()).hasSize(1); } @@ -410,7 +410,7 @@ public class MediaRouter2ManagerTest { assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1); - mManager.selectRoute(mPackageName, routeToSelect); + mManager.transfer(mPackageName, routeToSelect); assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); @@ -514,7 +514,7 @@ public class MediaRouter2ManagerTest { } }); awaitOnRouteChangedManager( - () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)), + () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)), ROUTE_ID1, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); @@ -525,7 +525,7 @@ public class MediaRouter2ManagerTest { RoutingSessionInfo sessionInfo = sessions.get(1); awaitOnRouteChangedManager( - () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)), + () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)), ROUTE_ID5_TO_TRANSFER_TO, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); @@ -583,9 +583,9 @@ public class MediaRouter2ManagerTest { assertThat(route1).isNotNull(); assertThat(route2).isNotNull(); - mManager.selectRoute(mPackageName, route1); + mManager.transfer(mPackageName, route1); assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); - mManager.selectRoute(mPackageName, route2); + mManager.transfer(mPackageName, route2); assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); // onTransferFailed/onSessionReleased should not be called. @@ -703,7 +703,7 @@ public class MediaRouter2ManagerTest { } }); - mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)); + mManager.transfer(mPackageName, routes.get(ROUTE_ID1)); assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName); @@ -858,7 +858,7 @@ public class MediaRouter2ManagerTest { }); mRouter2.setOnGetControllerHintsListener(listener); - mManager.selectRoute(mPackageName, route); + mManager.transfer(mPackageName, route); assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); @@ -903,7 +903,7 @@ public class MediaRouter2ManagerTest { } }); - mManager.selectRoute(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT)); + mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT)); assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); } diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 25fa34b418ab..cdd1a8964b2b 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -21,14 +21,14 @@ <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string> <string name="set_as_default">Set as default</string> <string name="use_once">Use once</string> - <string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string> + <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName">%1$s</xliff:g> <xliff:g id="type">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName">%4$s</xliff:g></string> <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber">%2$s</xliff:g> passkeys</string> <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords</string> <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber">%1$s</xliff:g> passkeys</string> - <string name="passkeys">passkeys</string> - <string name="passwords">passwords</string> + <string name="passkey">passkey</string> + <string name="password">password</string> <string name="sign_ins">sign-ins</string> - <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string> + <string name="other_password_manager">Other password manager</string> <!-- Spoken content description of an element which will close the sheet when clicked. --> <string name="close_sheet">"Close sheet"</string> <!-- Spoken content description of the back arrow button. --> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 2099a235a3e8..b71af2b654e4 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -28,6 +28,7 @@ import android.credentials.ui.Constants import android.credentials.ui.Entry import android.credentials.ui.CreateCredentialProviderData import android.credentials.ui.GetCredentialProviderData +import android.credentials.ui.DisabledProviderData import android.credentials.ui.ProviderData import android.credentials.ui.RequestInfo import android.credentials.ui.BaseDialogResult @@ -39,7 +40,7 @@ import android.os.ResultReceiver import com.android.credentialmanager.createflow.ActiveEntry import com.android.credentialmanager.createflow.CreatePasskeyUiState import com.android.credentialmanager.createflow.CreateScreenState -import com.android.credentialmanager.createflow.ProviderInfo +import com.android.credentialmanager.createflow.EnabledProviderInfo import com.android.credentialmanager.createflow.RequestDisplayInfo import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetScreenState @@ -51,7 +52,8 @@ class CredentialManagerRepo( intent: Intent, ) { private val requestInfo: RequestInfo - private val providerList: List<ProviderData> + private val providerEnabledList: List<ProviderData> + private val providerDisabledList: List<DisabledProviderData> // TODO: require non-null. val resultReceiver: ResultReceiver? @@ -61,16 +63,16 @@ class CredentialManagerRepo( RequestInfo::class.java ) ?: testCreateRequestInfo() - providerList = when (requestInfo.type) { + providerEnabledList = when (requestInfo.type) { RequestInfo.TYPE_CREATE -> intent.extras?.getParcelableArrayList( ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, CreateCredentialProviderData::class.java - ) ?: testCreateCredentialProviderList() + ) ?: testCreateCredentialEnabledProviderList() RequestInfo.TYPE_GET -> intent.extras?.getParcelableArrayList( ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, - GetCredentialProviderData::class.java + DisabledProviderData::class.java ) ?: testGetCredentialProviderList() else -> { // TODO: fail gracefully @@ -78,6 +80,12 @@ class CredentialManagerRepo( } } + providerDisabledList = + intent.extras?.getParcelableArrayList( + ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, + DisabledProviderData::class.java + ) ?: testDisabledProviderList() + resultReceiver = intent.getParcelableExtra( Constants.EXTRA_RESULT_RECEIVER, ResultReceiver::class.java @@ -103,25 +111,28 @@ class CredentialManagerRepo( } fun getCredentialInitialUiState(): GetCredentialUiState { - val providerList = GetFlowUtils.toProviderList( + val providerEnabledList = GetFlowUtils.toProviderList( // TODO: handle runtime cast error - providerList as List<GetCredentialProviderData>, context) + providerEnabledList as List<GetCredentialProviderData>, context) // TODO: covert from real requestInfo val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("tribank") return GetCredentialUiState( - providerList, + providerEnabledList, GetScreenState.PRIMARY_SELECTION, requestDisplayInfo, ) } fun createPasskeyInitialUiState(): CreatePasskeyUiState { - val providerList = CreateFlowUtils.toProviderList( + val providerEnabledList = CreateFlowUtils.toEnabledProviderList( + // Handle runtime cast error + providerEnabledList as List<CreateCredentialProviderData>, context) + val providerDisabledList = CreateFlowUtils.toDisabledProviderList( // Handle runtime cast error - providerList as List<CreateCredentialProviderData>, context) + providerDisabledList as List<DisabledProviderData>, context) var hasDefault = false - var defaultProvider: ProviderInfo = providerList.first() - providerList.forEach{providerInfo -> providerInfo.createOptions = + var defaultProvider: EnabledProviderInfo = providerEnabledList.first() + providerEnabledList.forEach{providerInfo -> providerInfo.createOptions = providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed() if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} } // TODO: covert from real requestInfo @@ -131,7 +142,8 @@ class CredentialManagerRepo( TYPE_PUBLIC_KEY_CREDENTIAL, "tribank") return CreatePasskeyUiState( - providers = providerList, + enabledProviders = providerEnabledList, + disabledProviders = providerDisabledList, if (hasDefault) {CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO}, requestDisplayInfo, @@ -157,10 +169,10 @@ class CredentialManagerRepo( } // TODO: below are prototype functionalities. To be removed for productionization. - private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> { + private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> { return listOf( CreateCredentialProviderData - .Builder("com.google/com.google.CredentialManagerService") + .Builder("io.enpass.app") .setSaveEntries( listOf<Entry>( newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com", @@ -172,7 +184,7 @@ class CredentialManagerRepo( .setIsDefaultProvider(true) .build(), CreateCredentialProviderData - .Builder("com.dashlane/com.dashlane.CredentialManagerService") + .Builder("com.dashlane") .setSaveEntries( listOf<Entry>( newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com", @@ -185,6 +197,13 @@ class CredentialManagerRepo( ) } + private fun testDisabledProviderList(): List<DisabledProviderData> { + return listOf( + DisabledProviderData("com.lastpass.lpandroid"), + DisabledProviderData("com.google.android.youtube") + ) + } + private fun testGetCredentialProviderList(): List<GetCredentialProviderData> { return listOf( GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService") diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 5c7956423469..53e817e1a03a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -17,9 +17,11 @@ package com.android.credentialmanager import android.content.Context +import android.content.pm.PackageManager import android.credentials.ui.Entry import android.credentials.ui.GetCredentialProviderData import android.credentials.ui.CreateCredentialProviderData +import android.credentials.ui.DisabledProviderData import com.android.credentialmanager.createflow.CreateOptionInfo import com.android.credentialmanager.getflow.ActionEntryInfo import com.android.credentialmanager.getflow.AuthenticationEntryInfo @@ -103,22 +105,42 @@ class GetFlowUtils { class CreateFlowUtils { companion object { - fun toProviderList( + fun toEnabledProviderList( providerDataList: List<CreateCredentialProviderData>, context: Context, - ): List<com.android.credentialmanager.createflow.ProviderInfo> { + ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> { + val packageManager = context.packageManager return providerDataList.map { - com.android.credentialmanager.createflow.ProviderInfo( - // TODO: replace to extract from the service data structure when available - icon = context.getDrawable(R.drawable.ic_passkey)!!, + val pkgInfo = packageManager + .getPackageInfo(it.providerFlattenedComponentName, + PackageManager.PackageInfoFlags.of(0)) + com.android.credentialmanager.createflow.EnabledProviderInfo( + icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!, name = it.providerFlattenedComponentName, - displayName = it.providerFlattenedComponentName, + displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(), createOptions = toCreationOptionInfoList(it.saveEntries, context), isDefault = it.isDefaultProvider, ) } } + fun toDisabledProviderList( + providerDataList: List<DisabledProviderData>, + context: Context, + ): List<com.android.credentialmanager.createflow.DisabledProviderInfo> { + val packageManager = context.packageManager + return providerDataList.map { + val pkgInfo = packageManager + .getPackageInfo(it.providerFlattenedComponentName, + PackageManager.PackageInfoFlags.of(0)) + com.android.credentialmanager.createflow.DisabledProviderInfo( + icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!, + name = it.providerFlattenedComponentName, + displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(), + ) + } + } + private fun toCreationOptionInfoList( creationEntries: List<Entry>, context: Context, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 21190e7dc8c4..0c3447f02b14 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -18,13 +18,25 @@ package com.android.credentialmanager.createflow import android.graphics.drawable.Drawable -data class ProviderInfo( +open class ProviderInfo( val icon: Drawable, val name: String, val displayName: String, +) + +class EnabledProviderInfo( + icon: Drawable, + name: String, + displayName: String, var createOptions: List<CreateOptionInfo>, val isDefault: Boolean, -) +) : ProviderInfo(icon, name, displayName) + +class DisabledProviderInfo( + icon: Drawable, + name: String, + displayName: String, +) : ProviderInfo(icon, name, displayName) open class EntryInfo ( val entryKey: String, @@ -55,7 +67,7 @@ data class RequestDisplayInfo( * user selects a different entry on the more option page. */ data class ActiveEntry ( - val activeProvider: ProviderInfo, + val activeProvider: EnabledProviderInfo, val activeEntryInfo: EntryInfo, ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index 437e7b213620..4769a26b8eb1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -21,6 +21,7 @@ import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment @@ -59,7 +60,7 @@ fun CreatePasskeyScreen( onCancel = viewModel::onCancel, ) CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard( - providerList = uiState.providers, + enabledProviderList = uiState.enabledProviders, onCancel = viewModel::onCancel, onProviderSelected = viewModel::onProviderSelected ) @@ -70,14 +71,16 @@ fun CreatePasskeyScreen( onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected, onConfirm = viewModel::onPrimaryCreateOptionInfoSelected, onCancel = viewModel::onCancel, - multiProvider = uiState.providers.size > 1, + multiProvider = uiState.enabledProviders.size > 1, onMoreOptionsSelected = viewModel::onMoreOptionsSelected ) CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard( requestDisplayInfo = uiState.requestDisplayInfo, - providerList = uiState.providers, + enabledProviderList = uiState.enabledProviders, + disabledProviderList = uiState.disabledProviders, onBackButtonSelected = viewModel::onBackButtonSelected, - onOptionSelected = viewModel::onMoreOptionsRowSelected + onOptionSelected = viewModel::onMoreOptionsRowSelected, + onDisabledPasswordManagerSelected = viewModel::onDisabledPasswordManagerSelected ) CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard( providerInfo = uiState.activeEntry?.activeProvider!!, @@ -153,7 +156,7 @@ fun ConfirmationCard( @OptIn(ExperimentalMaterial3Api::class) @Composable fun ProviderSelectionCard( - providerList: List<ProviderInfo>, + enabledProviderList: List<EnabledProviderInfo>, onProviderSelected: (String) -> Unit, onCancel: () -> Unit ) { @@ -182,7 +185,7 @@ fun ProviderSelectionCard( LazyColumn( verticalArrangement = Arrangement.spacedBy(2.dp) ) { - providerList.forEach { + enabledProviderList.forEach { item { ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected) } @@ -212,9 +215,11 @@ fun ProviderSelectionCard( @Composable fun MoreOptionsSelectionCard( requestDisplayInfo: RequestDisplayInfo, - providerList: List<ProviderInfo>, + enabledProviderList: List<EnabledProviderInfo>, + disabledProviderList: List<DisabledProviderInfo>, onBackButtonSelected: () -> Unit, - onOptionSelected: (ActiveEntry) -> Unit + onOptionSelected: (ActiveEntry) -> Unit, + onDisabledPasswordManagerSelected: () -> Unit, ) { Card() { Column() { @@ -250,18 +255,24 @@ fun MoreOptionsSelectionCard( LazyColumn( verticalArrangement = Arrangement.spacedBy(2.dp) ) { - providerList.forEach { providerInfo -> - providerInfo.createOptions.forEach { createOptionInfo -> + enabledProviderList.forEach { enabledProviderInfo -> + enabledProviderInfo.createOptions.forEach { createOptionInfo -> item { MoreOptionsInfoRow( - providerInfo = providerInfo, + providerInfo = enabledProviderInfo, createOptionInfo = createOptionInfo, onOptionSelected = { - onOptionSelected(ActiveEntry(providerInfo, createOptionInfo)) + onOptionSelected(ActiveEntry(enabledProviderInfo, createOptionInfo)) }) } } } + item { + MoreOptionsDisabledProvidersRow( + disabledProviders = disabledProviderList, + onDisabledPasswordManagerSelected = onDisabledPasswordManagerSelected, + ) + } } } Divider( @@ -276,7 +287,7 @@ fun MoreOptionsSelectionCard( @OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsRowIntroCard( - providerInfo: ProviderInfo, + providerInfo: EnabledProviderInfo, onDefaultOrNotSelected: () -> Unit, ) { Card() { @@ -347,7 +358,7 @@ fun CreationSelectionCard( Card() { Column() { Icon( - bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(), + bitmap = providerInfo.icon.toBitmap().asImageBitmap(), contentDescription = null, tint = Color.Unspecified, modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp) @@ -366,18 +377,14 @@ fun CreationSelectionCard( .align(alignment = Alignment.CenterHorizontally), textAlign = TextAlign.Center, ) - Text( - text = requestDisplayInfo.appDomainName, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - ) if (createOptionInfo.userProviderDisplayName != null) { Text( text = stringResource( R.string.choose_create_option_description, + requestDisplayInfo.appDomainName, when (requestDisplayInfo.type) { - TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys) - TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords) + TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkey) + TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.password) else -> stringResource(R.string.sign_ins) }, providerInfo.displayName, @@ -460,7 +467,7 @@ fun PrimaryCreateOptionRow( icon = { Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp), bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(), - contentDescription = stringResource(R.string.createOptionInfo_icon_description)) + contentDescription = null) }, shape = MaterialTheme.shapes.large, label = { @@ -483,7 +490,7 @@ fun PrimaryCreateOptionRow( @OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsInfoRow( - providerInfo: ProviderInfo, + providerInfo: EnabledProviderInfo, createOptionInfo: CreateOptionInfo, onOptionSelected: () -> Unit ) { @@ -491,9 +498,9 @@ fun MoreOptionsInfoRow( modifier = Modifier.fillMaxWidth(), onClick = onOptionSelected, icon = { - Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp), - bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(), - contentDescription = stringResource(R.string.createOptionInfo_icon_description)) + Image(modifier = Modifier.size(32.dp, 32.dp).padding(start = 10.dp), + bitmap = providerInfo.icon.toBitmap().asImageBitmap(), + contentDescription = null) }, shape = MaterialTheme.shapes.large, label = { @@ -546,4 +553,37 @@ fun MoreOptionsInfoRow( } } ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MoreOptionsDisabledProvidersRow( + disabledProviders: List<ProviderInfo>, + onDisabledPasswordManagerSelected: () -> Unit, +) { + SuggestionChip( + modifier = Modifier.fillMaxWidth(), + onClick = onDisabledPasswordManagerSelected, + icon = { + Icon( + Icons.Filled.Add, + contentDescription = null + ) + }, + shape = MaterialTheme.shapes.large, + label = { + Column() { + Text( + text = stringResource(R.string.other_password_manager), + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(top = 16.dp) + ) + Text( + text = disabledProviders.joinToString(separator = ", "){ it.displayName }, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt index 2e9758aece33..8b94201ad787 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt @@ -28,7 +28,8 @@ import com.android.credentialmanager.common.DialogResult import com.android.credentialmanager.common.ResultState data class CreatePasskeyUiState( - val providers: List<ProviderInfo>, + val enabledProviders: List<EnabledProviderInfo>, + val disabledProviders: List<DisabledProviderInfo>, val currentScreenState: CreateScreenState, val requestDisplayInfo: RequestDisplayInfo, val activeEntry: ActiveEntry? = null, @@ -50,15 +51,15 @@ class CreatePasskeyViewModel( } fun onConfirmIntro() { - if (uiState.providers.size > 1) { + if (uiState.enabledProviders.size > 1) { uiState = uiState.copy( currentScreenState = CreateScreenState.PROVIDER_SELECTION ) - } else if (uiState.providers.size == 1){ + } else if (uiState.enabledProviders.size == 1){ uiState = uiState.copy( currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION, - activeEntry = ActiveEntry(uiState.providers.first(), - uiState.providers.first().createOptions.first()) + activeEntry = ActiveEntry(uiState.enabledProviders.first(), + uiState.enabledProviders.first().createOptions.first()) ) } else { throw java.lang.IllegalStateException("Empty provider list.") @@ -73,8 +74,8 @@ class CreatePasskeyViewModel( ) } - fun getProviderInfoByName(providerName: String): ProviderInfo { - return uiState.providers.single { + fun getProviderInfoByName(providerName: String): EnabledProviderInfo { + return uiState.enabledProviders.single { it.name.equals(providerName) } } @@ -98,6 +99,10 @@ class CreatePasskeyViewModel( ) } + fun onDisabledPasswordManagerSelected() { + // TODO: Complete this function + } + fun onCancel() { CredentialManagerRepo.getInstance().onCancel() dialogResult.value = DialogResult(ResultState.CANCELED) diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index f1a24aff4319..50cab84e0d9d 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -35,8 +35,8 @@ </activity> <provider - android:name=".GalleryEntryProvider" - android:authorities="com.android.spa.gallery.provider" + android:name="com.android.settingslib.spa.framework.SpaSearchProvider" + android:authorities="com.android.spa.gallery.search.provider" android:enabled="true" android:exported="false"> </provider> @@ -51,7 +51,7 @@ </activity> <provider android:name="com.android.settingslib.spa.framework.debug.DebugProvider" - android:authorities="com.android.spa.gallery.debug" + android:authorities="com.android.spa.gallery.debug.provider" android:enabled="true" android:exported="false"> </provider> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt deleted file mode 100644 index 817c209fbcdc..000000000000 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt +++ /dev/null @@ -1,21 +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.settingslib.spa.gallery - -import com.android.settingslib.spa.framework.EntryProvider - -class GalleryEntryProvider : EntryProvider() diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 742e2712e41a..016b27f0c82c 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -80,7 +80,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { override val browseActivityClass = GalleryMainActivity::class.java - override val entryProviderAuthorities = "com.android.spa.gallery.provider" + override val searchProviderAuthorities = "com.android.spa.gallery.search.provider" override val logger = LocalLogger() } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt index 38f41bc44a89..35b9c0fbd16b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt @@ -33,7 +33,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.addUri import com.android.settingslib.spa.framework.common.getColumns -private const val TAG = "EntryProvider" +private const val TAG = "SpaSearchProvider" /** * The content provider to return entry related data, which can be used for search and hierarchy. @@ -47,7 +47,7 @@ private const val TAG = "EntryProvider" * $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status * $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status */ -open class EntryProvider : ContentProvider() { +class SpaSearchProvider : ContentProvider() { private val spaEnvironment get() = SpaEnvironmentFactory.instance private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt index b83104360260..a9cb041cc198 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt @@ -64,7 +64,7 @@ abstract class SpaEnvironment(context: Context) { open val browseActivityClass: Class<out Activity>? = null - open val entryProviderAuthorities: String? = null + open val searchProviderAuthorities: String? = null open val logger: SpaLogger = object : SpaLogger {} diff --git a/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml new file mode 100644 index 000000000000..46abff81d38f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_5g_plus_mobiledata_default.xml @@ -0,0 +1,33 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:viewportWidth="22" + android:viewportHeight="17" + android:width="22dp" + android:height="17dp"> + <group> + <group> + <path android:fillColor="#FF000000" + android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/> + </group> + <group> + <path android:fillColor="#FF000000" + android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/> + </group> + <path android:fillColor="#FF000000" + android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/> + </group> +</vector> diff --git a/packages/SettingsLib/res/values/carrierid_icon_overrides.xml b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml new file mode 100644 index 000000000000..d2ae52d8347a --- /dev/null +++ b/packages/SettingsLib/res/values/carrierid_icon_overrides.xml @@ -0,0 +1,32 @@ +<?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. + --> +<!-- + ~ This resource file exists to enumerate all network type icon overrides on a + ~ per-carrierId basis +--> +<resources> + <!-- + Network type (RAT) icon overrides can be configured here on a per-carrierId basis. + 1. Add a new TypedArray here, using the naming scheme below + 2. The entries are (NetworkType, drawable ID) pairs + 3. Add this array's ID to the MAPPING field of MobileIconCarrierIdOverrides.kt + --> + <array name="carrierId_2032_iconOverrides"> + <item>5G_PLUS</item> + <item>@drawable/ic_5g_plus_mobiledata_default</item> + </array> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 7913c16b6b98..65c94cec6009 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -36,8 +36,10 @@ import android.content.pm.PackageStats; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.content.pm.UserProperties; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -1577,8 +1579,8 @@ public class ApplicationsState { public long internalSize; public long externalSize; public String labelDescription; - public boolean mounted; + public boolean showInPersonalTab; /** * Setting this to {@code true} prevents the entry to be filtered by @@ -1635,6 +1637,33 @@ public class ApplicationsState { ThreadUtils.postOnBackgroundThread( () -> this.ensureLabelDescriptionLocked(context)); } + this.showInPersonalTab = shouldShowInPersonalTab(context, info.uid); + } + + /** + * Checks if the user that the app belongs to have the property + * {@link UserProperties#SHOW_IN_SETTINGS_WITH_PARENT} set. + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + boolean shouldShowInPersonalTab(Context context, int uid) { + UserManager userManager = UserManager.get(context); + int userId = UserHandle.getUserId(uid); + + // Regardless of apk version, if the app belongs to the current user then return true. + if (userId == ActivityManager.getCurrentUser()) { + return true; + } + + // For sdk version < 34, if the app doesn't belong to the current user, + // then as per earlier behaviour the app shouldn't be displayed in personal tab. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + return false; + } + + UserProperties userProperties = userManager.getUserProperties( + UserHandle.of(userId)); + return userProperties.getShowInSettings() + == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT; } public void ensureLabel(Context context) { @@ -1784,7 +1813,7 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { - return UserHandle.getUserId(entry.info.uid) == mCurrentUser; + return entry.showInPersonalTab; } }; @@ -1811,7 +1840,7 @@ public class ApplicationsState { @Override public boolean filterApp(AppEntry entry) { - return UserHandle.getUserId(entry.info.uid) != mCurrentUser; + return !entry.showInPersonalTab; } }; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index c829bc316246..3ba51d2a2602 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -293,7 +293,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { return false; } setConnectedRecord(); - mRouterManager.selectRoute(mPackageName, mRouteInfo); + mRouterManager.transfer(mPackageName, mRouteInfo); return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt new file mode 100644 index 000000000000..a0395b559291 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt @@ -0,0 +1,142 @@ +/* + * 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.settingslib.mobile + +import android.annotation.DrawableRes +import android.content.res.Resources +import android.content.res.TypedArray +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.settingslib.R +import com.android.settingslib.SignalIcon.MobileIconGroup + +/** + * This class defines a network type (3G, 4G, etc.) override mechanism on a per-carrierId basis. + * + * Traditionally, carrier-customized network type iconography was achieved using the `MCC/MNC` + * resource qualifiers, and swapping out the drawable resource by name. It would look like this: + * + * res/ + * drawable/ + * 3g_mobiledata_icon.xml + * drawable-MCC-MNC/ + * 3g_mobiledata_icon.xml + * + * This would mean that, provided a context created with this MCC/MNC configuration set, loading + * the network type icon through [MobileIconGroup] would provide a carrier-defined network type + * icon rather than the AOSP-defined default. + * + * The MCC/MNC mechanism no longer can fully define carrier-specific network type icons, because + * there is no longer a 1:1 mapping between MCC/MNC and carrier. With the advent of MVNOs, multiple + * carriers can have the same MCC/MNC value, but wish to differentiate based on their carrier ID. + * CarrierId is a newer concept than MCC/MNC, and provides more granularity when it comes to + * determining the carrier (e.g. MVNOs can share MCC/MNC values with the network owner), therefore + * it can fit all of the same use cases currently handled by `MCC/MNC`, without the need to apply a + * configuration context in order to get the proper UI for a given SIM icon. + * + * NOTE: CarrierId icon overrides will always take precedence over those defined using `MCC/MNC` + * resource qualifiers. + * + * [MAPPING] encodes the relationship between CarrierId and the corresponding override array + * that exists in the config.xml. An alternative approach could be to generate the resource name + * by string concatenation at run-time: + * + * val resName = "carrierId_$carrierId_iconOverrides" + * val override = resources.getResourceIdentifier(resName) + * + * However, that's going to be far less efficient until MAPPING grows to a sufficient size. For now, + * given a relatively small number of entries, we should just maintain the mapping here. + */ +interface MobileIconCarrierIdOverrides { + @DrawableRes + fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int + fun carrierIdEntryExists(carrierId: Int): Boolean +} + +class MobileIconCarrierIdOverridesImpl : MobileIconCarrierIdOverrides { + @DrawableRes + override fun getOverrideFor(carrierId: Int, networkType: String, resources: Resources): Int { + val resId = MAPPING[carrierId] ?: return 0 + val ta = resources.obtainTypedArray(resId) + val map = parseNetworkIconOverrideTypedArray(ta) + ta.recycle() + return map[networkType] ?: 0 + } + + override fun carrierIdEntryExists(carrierId: Int) = + overrideExists(carrierId, MAPPING) + + companion object { + private const val TAG = "MobileIconOverrides" + /** + * This map maintains the lookup from the canonical carrier ID (see below link) to the + * corresponding overlay resource. New overrides should add an entry below in order to + * change the network type icon resources based on carrier ID + * + * Refer to the link below for the canonical mapping maintained in AOSP: + * https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb + */ + private val MAPPING = mapOf( + // 2032 == Xfinity Mobile + 2032 to R.array.carrierId_2032_iconOverrides, + ) + + /** + * Parse `carrierId_XXXX_iconOverrides` for a particular network type. The resource file + * "carrierid_icon_overrides.xml" defines a TypedArray format for overriding specific + * network type icons (a.k.a. RAT icons) for a particular carrier ID. The format is defined + * as an array of (network type name, drawable) pairs: + * <array name="carrierId_XXXX_iconOverrides> + * <item>NET_TYPE_1</item> + * <item>@drawable/net_type_1_override</item> + * <item>NET_TYPE_2</item> + * <item>@drawable/net_type_2_override</item> + * </array> + * + * @param ta the [TypedArray] defined in carrierid_icon_overrides.xml + * @return the overridden drawable resource ID if it exists, or 0 if it does not + */ + @VisibleForTesting + @JvmStatic + fun parseNetworkIconOverrideTypedArray(ta: TypedArray): Map<String, Int> { + if (ta.length() % 2 != 0) { + Log.w(TAG, + "override must contain an even number of (key, value) entries. skipping") + + return mapOf() + } + + val result = mutableMapOf<String, Int>() + // The array is defined as Pair(String, resourceId), so walk by 2 + for (i in 0 until ta.length() step 2) { + val key = ta.getString(i) + val override = ta.getResourceId(i + 1, 0) + if (key == null || override == 0) { + Log.w(TAG, "Invalid override found. Skipping") + continue + } + result[key] = override + } + + return result + } + + @JvmStatic + private fun overrideExists(carrierId: Int, mapping: Map<Int, Int>): Boolean = + mapping.containsKey(carrierId) + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java index f1e1e7d920cc..c5598bfa9438 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java @@ -293,4 +293,15 @@ public class ApplicationsStateTest { assertThat(ApplicationsState.FILTER_MOVIES.filterApp(mEntry)).isFalse(); } + + @Test + public void testPersonalAndWorkFiltersDisplaysCorrectApps() { + mEntry.showInPersonalTab = true; + assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue(); + assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isFalse(); + + mEntry.showInPersonalTab = false; + assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse(); + assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isTrue(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index fc2bf0a9bd93..39875f7950e4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -17,6 +17,7 @@ package com.android.settingslib.applications; import static android.os.UserHandle.MU_ENABLED; +import static android.os.UserHandle.USER_SYSTEM; import static com.google.common.truth.Truth.assertThat; @@ -48,9 +49,11 @@ import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; +import android.content.pm.UserProperties; import android.content.res.Resources; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -58,6 +61,8 @@ import android.os.UserManager; import android.text.TextUtils; import android.util.IconDrawableFactory; +import androidx.test.core.app.ApplicationProvider; + import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.Callbacks; import com.android.settingslib.applications.ApplicationsState.Session; @@ -71,6 +76,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -79,6 +85,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowContextImpl; import org.robolectric.shadows.ShadowLooper; +import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.Arrays; @@ -95,6 +102,7 @@ public class ApplicationsStateRoboTest { private final static String LAUNCHABLE_PACKAGE_NAME = "com.android.launchable"; private static final int PROFILE_USERID = 10; + private static final int PROFILE_USERID2 = 11; private static final String PKG_1 = "PKG1"; private static final int OWNER_UID_1 = 1001; @@ -106,6 +114,10 @@ public class ApplicationsStateRoboTest { private static final String PKG_3 = "PKG3"; private static final int OWNER_UID_3 = 1003; + private static final int PROFILE_UID_3 = UserHandle.getUid(PROFILE_USERID2, OWNER_UID_3); + + private static final String CLONE_USER = "clone_user"; + private static final String RANDOM_USER = "random_user"; /** Class under test */ private ApplicationsState mApplicationsState; @@ -113,6 +125,8 @@ public class ApplicationsStateRoboTest { private Application mApplication; + @Spy + Context mContext = ApplicationProvider.getApplicationContext(); @Mock private Callbacks mCallbacks; @Captor @@ -738,4 +752,51 @@ public class ApplicationsStateRoboTest { when(configChanges.applyNewConfig(any(Resources.class))).thenReturn(false); mApplicationsState.setInterestingConfigChanges(configChanges); } + + @Test + public void shouldShowInPersonalTab_forCurrentUser_returnsTrue() { + ApplicationInfo appInfo = createApplicationInfo(PKG_1); + AppEntry primaryUserApp = createAppEntry(appInfo, 1); + + assertThat(primaryUserApp.shouldShowInPersonalTab(mContext, appInfo.uid)).isTrue(); + } + + @Test + public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() { + ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", + Build.VERSION_CODES.TIRAMISU); + // Create an app (and subsequent AppEntry) in a non-primary user profile. + ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1); + AppEntry nonPrimaryUserApp = createAppEntry(appInfo1, 1); + + assertThat(nonPrimaryUserApp.shouldShowInPersonalTab(mContext, appInfo1.uid)).isFalse(); + } + + @Test + public void shouldShowInPersonalTab_currentUserIsParent_returnsAsPerUserPropertyOfProfile1() { + // Mark system user as parent for both profile users. + ShadowUserManager shadowUserManager = Shadow + .extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); + shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID, + CLONE_USER, 0); + shadowUserManager.addProfile(USER_SYSTEM, PROFILE_USERID2, + RANDOM_USER, 0); + shadowUserManager.setupUserProperty(PROFILE_USERID, + /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_WITH_PARENT); + shadowUserManager.setupUserProperty(PROFILE_USERID2, + /*showInSettings*/ UserProperties.SHOW_IN_SETTINGS_NO); + + ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", + Build.VERSION_CODES.UPSIDE_DOWN_CAKE); + + // Treat PROFILE_USERID as a clone user profile and create an app PKG_1 in it. + ApplicationInfo appInfo1 = createApplicationInfo(PKG_1, PROFILE_UID_1); + // Treat PROFILE_USERID2 as a random non-primary profile and create an app PKG_3 in it. + ApplicationInfo appInfo2 = createApplicationInfo(PKG_3, PROFILE_UID_3); + AppEntry nonPrimaryUserApp1 = createAppEntry(appInfo1, 1); + AppEntry nonPrimaryUserApp2 = createAppEntry(appInfo2, 2); + + assertThat(nonPrimaryUserApp1.shouldShowInPersonalTab(mContext, appInfo1.uid)).isTrue(); + assertThat(nonPrimaryUserApp2.shouldShowInPersonalTab(mContext, appInfo2.uid)).isFalse(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index 5399f8a0eff0..c058a61a3e9e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -461,7 +461,7 @@ public class MediaDeviceTest { public void connect_shouldSelectRoute() { mInfoMediaDevice1.connect(); - verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1); + verify(mMediaRouter2Manager).transfer(TEST_PACKAGE_NAME, mRouteInfo1); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java new file mode 100644 index 000000000000..740261d3bac2 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/mobile/MobileIconCarrierIdOverridesTest.java @@ -0,0 +1,140 @@ +/* + * 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.settingslib.mobile; + +import static com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl.parseNetworkIconOverrideTypedArray; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.TypedArray; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.Map; + +@RunWith(RobolectricTestRunner.class) +public final class MobileIconCarrierIdOverridesTest { + private static final String OVERRIDE_ICON_1_NAME = "name_1"; + private static final int OVERRIDE_ICON_1_RES = 1; + + private static final String OVERRIDE_ICON_2_NAME = "name_2"; + private static final int OVERRIDE_ICON_2_RES = 2; + + NetworkOverrideTypedArrayMock mResourceMock; + + @Before + public void setUp() { + mResourceMock = new NetworkOverrideTypedArrayMock( + new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME }, + new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES } + ); + } + + @Test + public void testParse_singleOverride() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME }, + new int[] { OVERRIDE_ICON_1_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES); + } + + @Test + public void testParse_multipleOverrides() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME, OVERRIDE_ICON_2_NAME }, + new int[] { OVERRIDE_ICON_1_RES, OVERRIDE_ICON_2_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isEqualTo(OVERRIDE_ICON_2_RES); + assertThat(parsed.get(OVERRIDE_ICON_1_NAME)).isEqualTo(OVERRIDE_ICON_1_RES); + } + + @Test + public void testParse_nonexistentKey_isNull() { + mResourceMock.setOverrides( + new String[] { OVERRIDE_ICON_1_NAME }, + new int[] { OVERRIDE_ICON_1_RES } + ); + + Map<String, Integer> parsed = parseNetworkIconOverrideTypedArray(mResourceMock.getMock()); + + assertThat(parsed.get(OVERRIDE_ICON_2_NAME)).isNull(); + } + + static class NetworkOverrideTypedArrayMock { + private Object[] mInterleaved; + + private final TypedArray mMockTypedArray = mock(TypedArray.class); + + NetworkOverrideTypedArrayMock( + String[] networkTypes, + int[] iconOverrides) { + + mInterleaved = interleaveTypes(networkTypes, iconOverrides); + + doAnswer(invocation -> { + return mInterleaved[(int) invocation.getArgument(0)]; + }).when(mMockTypedArray).getString(/* index */ anyInt()); + + doAnswer(invocation -> { + return mInterleaved[(int) invocation.getArgument(0)]; + }).when(mMockTypedArray).getResourceId(/* index */ anyInt(), /* default */ anyInt()); + + when(mMockTypedArray.length()).thenAnswer(invocation -> { + return mInterleaved.length; + }); + } + + TypedArray getMock() { + return mMockTypedArray; + } + + void setOverrides(String[] types, int[] resIds) { + mInterleaved = interleaveTypes(types, resIds); + } + + private Object[] interleaveTypes(String[] strs, int[] ints) { + assertThat(strs.length).isEqualTo(ints.length); + + Object[] ret = new Object[strs.length * 2]; + + // Keep track of where we are in the interleaved array, but iterate the overrides + int interleavedIndex = 0; + for (int i = 0; i < strs.length; i++) { + ret[interleavedIndex] = strs[i]; + interleavedIndex += 1; + ret[interleavedIndex] = ints[i]; + interleavedIndex += 1; + } + return ret; + } + } +} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 11237dca7804..47771aa3c774 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -195,9 +195,6 @@ <permission android:name="com.android.systemui.permission.FLAGS" android:protectionLevel="signature" /> - <permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES" - android:protectionLevel="signature|privileged" /> - <!-- Adding Quick Settings tiles --> <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" /> @@ -996,12 +993,5 @@ android:excludeFromRecents="true" android:exported="false"> </activity> - - <provider - android:authorities="com.android.systemui.keyguard.quickaffordance" - android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider" - android:exported="true" - android:permission="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES" - /> </application> </manifest> diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml index a4e7a5f12db4..f1aa54412b3b 100644 --- a/packages/SystemUI/res-keyguard/values-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml @@ -27,4 +27,6 @@ <integer name="scaled_password_text_size">26</integer> <dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen> + <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 0a55cf779683..3861d983b309 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -124,6 +124,8 @@ <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen> <dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen> <dimen name="bouncer_user_switcher_y_trans">0dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen> <!-- 2 * the margin + size should equal the plus_margin --> <dimen name="user_switcher_icon_large_margin">16dp</dimen> diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml index a93691434bc0..d6c9e98d8b4d 100644 --- a/packages/SystemUI/res/layout/screen_record_options.xml +++ b/packages/SystemUI/res/layout/screen_record_options.xml @@ -50,6 +50,7 @@ android:importantForAccessibility="yes"/> </LinearLayout> <LinearLayout + android:id="@+id/show_taps" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml index d9df3373bef1..707bc9e535ff 100644 --- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -17,7 +17,6 @@ <resources> <dimen name="notification_panel_margin_horizontal">48dp</dimen> <dimen name="status_view_margin_horizontal">62dp</dimen> - <dimen name="bouncer_user_switcher_y_trans">20dp</dimen> <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two pages is margin * 2, and that makes tiles page not appear immediately after user swipes to diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml index 97ead01669a9..b98165fb08f0 100644 --- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml @@ -21,4 +21,6 @@ <!-- Space between status view and notification shelf --> <dimen name="keyguard_status_view_bottom_margin">70dp</dimen> <dimen name="keyguard_clock_top_margin">80dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen> + <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml index 17f82b50d7be..8b41a44b9ba3 100644 --- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml @@ -21,7 +21,6 @@ for different hardware and product builds. --> <resources> <dimen name="status_view_margin_horizontal">124dp</dimen> - <dimen name="bouncer_user_switcher_y_trans">200dp</dimen> <dimen name="large_screen_shade_header_left_padding">24dp</dimen> <dimen name="qqs_layout_padding_bottom">40dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 0e48760b7e27..6824d7f17e13 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -405,7 +405,7 @@ <string name="keyguard_face_failed">Can\u2019t recognize face</string> <!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] --> <string name="keyguard_suggest_fingerprint">Use fingerprint instead</string> - <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] --> + <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=65] --> <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string> <!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt deleted file mode 100644 index 8612b3a2c587..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt +++ /dev/null @@ -1,326 +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.shared.keyguard.data.content - -import android.annotation.SuppressLint -import android.content.ContentValues -import android.content.Context -import android.database.ContentObserver -import android.graphics.drawable.Drawable -import android.net.Uri -import android.os.UserHandle -import androidx.annotation.DrawableRes -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.withContext - -/** Collection of utility functions for using a content provider implementing the [Contract]. */ -object KeyguardQuickAffordanceProviderClient { - - /** - * Selects an affordance with the given ID for a slot on the lock screen with the given ID. - * - * Note that the maximum number of selected affordances on this slot is automatically enforced. - * Selecting a slot that is already full (e.g. already has a number of selected affordances at - * its maximum capacity) will automatically remove the oldest selected affordance before adding - * the one passed in this call. Additionally, selecting an affordance that's already one of the - * selected affordances on the slot will move the selected affordance to the newest location in - * the slot. - */ - suspend fun insertSelection( - context: Context, - slotId: String, - affordanceId: String, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ) { - withContext(dispatcher) { - context.contentResolver.insert( - Contract.SelectionTable.URI, - ContentValues().apply { - put(Contract.SelectionTable.Columns.SLOT_ID, slotId) - put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId) - } - ) - } - } - - /** Returns all available slots supported by the device. */ - suspend fun querySlots( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): List<Slot> { - return withContext(dispatcher) { - context.contentResolver - .query( - Contract.SlotTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID) - val capacityColumnIndex = - cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY) - if (idColumnIndex == -1 || capacityColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - Slot( - id = cursor.getString(idColumnIndex), - capacity = cursor.getInt(capacityColumnIndex), - ) - ) - } - } - } - } - ?: emptyList() - } - - /** - * Returns [Flow] for observing the collection of slots. - * - * @see [querySlots] - */ - fun observeSlots( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Flow<List<Slot>> { - return observeUri( - context, - Contract.SlotTable.URI, - ) - .map { querySlots(context, dispatcher) } - } - - /** - * Returns all available affordances supported by the device, regardless of current slot - * placement. - */ - suspend fun queryAffordances( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): List<Affordance> { - return withContext(dispatcher) { - context.contentResolver - .query( - Contract.AffordanceTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val idColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID) - val nameColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME) - val iconColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON) - if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - Affordance( - id = cursor.getString(idColumnIndex), - name = cursor.getString(nameColumnIndex), - iconResourceId = cursor.getInt(iconColumnIndex), - ) - ) - } - } - } - } - ?: emptyList() - } - - /** - * Returns [Flow] for observing the collection of affordances. - * - * @see [queryAffordances] - */ - fun observeAffordances( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Flow<List<Affordance>> { - return observeUri( - context, - Contract.AffordanceTable.URI, - ) - .map { queryAffordances(context, dispatcher) } - } - - /** Returns the current slot-affordance selections. */ - suspend fun querySelections( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): List<Selection> { - return withContext(dispatcher) { - context.contentResolver - .query( - Contract.SelectionTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val slotIdColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID) - val affordanceIdColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID) - if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - Selection( - slotId = cursor.getString(slotIdColumnIndex), - affordanceId = cursor.getString(affordanceIdColumnIndex), - ) - ) - } - } - } - } - ?: emptyList() - } - - /** - * Returns [Flow] for observing the collection of selections. - * - * @see [querySelections] - */ - fun observeSelections( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Flow<List<Selection>> { - return observeUri( - context, - Contract.SelectionTable.URI, - ) - .map { querySelections(context, dispatcher) } - } - - /** Unselects an affordance with the given ID from the slot with the given ID. */ - suspend fun deleteSelection( - context: Context, - slotId: String, - affordanceId: String, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ) { - withContext(dispatcher) { - context.contentResolver.delete( - Contract.SelectionTable.URI, - "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" + - " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?", - arrayOf( - slotId, - affordanceId, - ), - ) - } - } - - /** Unselects all affordances from the slot with the given ID. */ - suspend fun deleteAllSelections( - context: Context, - slotId: String, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ) { - withContext(dispatcher) { - context.contentResolver.delete( - Contract.SelectionTable.URI, - "${Contract.SelectionTable.Columns.SLOT_ID}", - arrayOf( - slotId, - ), - ) - } - } - - private fun observeUri( - context: Context, - uri: Uri, - ): Flow<Unit> { - return callbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } - - context.contentResolver.registerContentObserver( - uri, - /* notifyForDescendants= */ true, - observer, - UserHandle.USER_CURRENT, - ) - - awaitClose { context.contentResolver.unregisterContentObserver(observer) } - } - .onStart { emit(Unit) } - } - - @SuppressLint("UseCompatLoadingForDrawables") - suspend fun getAffordanceIcon( - context: Context, - @DrawableRes iconResourceId: Int, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Drawable { - return withContext(dispatcher) { - context.packageManager - .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) - .getDrawable(iconResourceId) - } - } - - data class Slot( - val id: String, - val capacity: Int, - ) - - data class Affordance( - val id: String, - val name: String, - val iconResourceId: Int, - ) - - data class Selection( - val slotId: String, - val affordanceId: String, - ) - - private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt deleted file mode 100644 index c2658a9e61b1..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt +++ /dev/null @@ -1,111 +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.shared.keyguard.data.content - -import android.content.ContentResolver -import android.net.Uri - -/** Contract definitions for querying content about keyguard quick affordances. */ -object KeyguardQuickAffordanceProviderContract { - - const val AUTHORITY = "com.android.systemui.keyguard.quickaffordance" - const val PERMISSION = "android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES" - - private val BASE_URI: Uri = - Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build() - - /** - * Table for slots. - * - * Slots are positions where affordances can be placed on the lock screen. Affordances that are - * placed on slots are said to be "selected". The system supports the idea of multiple - * affordances per slot, though the implementation may limit the number of affordances on each - * slot. - * - * Supported operations: - * - Query - to know which slots are available, query the [SlotTable.URI] [Uri]. The result set - * will contain rows with the [SlotTable.Columns] columns. - */ - object SlotTable { - const val TABLE_NAME = "slots" - val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - - object Columns { - /** String. Unique ID for this slot. */ - const val ID = "id" - /** Integer. The maximum number of affordances that can be placed in the slot. */ - const val CAPACITY = "capacity" - } - } - - /** - * Table for affordances. - * - * Affordances are actions/buttons that the user can execute. They are placed on slots on the - * lock screen. - * - * Supported operations: - * - Query - to know about all the affordances that are available on the device, regardless of - * which ones are currently selected, query the [AffordanceTable.URI] [Uri]. The result set will - * contain rows, each with the columns specified in [AffordanceTable.Columns]. - */ - object AffordanceTable { - const val TABLE_NAME = "affordances" - val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - - object Columns { - /** String. Unique ID for this affordance. */ - const val ID = "id" - /** String. User-visible name for this affordance. */ - const val NAME = "name" - /** - * Integer. Resource ID for the drawable to load for this affordance. This is a resource - * ID from the system UI package. - */ - const val ICON = "icon" - } - } - - /** - * Table for selections. - * - * Selections are pairs of slot and affordance IDs. - * - * Supported operations: - * - Insert - to insert an affordance and place it in a slot, insert values for the columns into - * the [SelectionTable.URI] [Uri]. The maximum capacity rule is enforced by the system. - * Selecting a new affordance for a slot that is already full will automatically remove the - * oldest affordance from the slot. - * - Query - to know which affordances are set on which slots, query the [SelectionTable.URI] - * [Uri]. The result set will contain rows, each of which with the columns from - * [SelectionTable.Columns]. - * - Delete - to unselect an affordance, removing it from a slot, delete from the - * [SelectionTable.URI] [Uri], passing in values for each column. - */ - object SelectionTable { - const val TABLE_NAME = "selections" - val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - - object Columns { - /** String. Unique ID for the slot. */ - const val SLOT_ID = "slot_id" - /** String. Unique ID for the selected affordance. */ - const val AFFORDANCE_ID = "affordance_id" - } - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java index 40c8774d4f34..a790d89ac1ae 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java @@ -61,26 +61,42 @@ public class PreviewPositionHelper { * Updates the matrix based on the provided parameters */ public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, - int canvasWidth, int canvasHeight, int screenWidthPx, int taskbarSize, boolean isTablet, + int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx, + int taskbarSize, boolean isTablet, int currentRotation, boolean isRtl) { boolean isRotated = false; boolean isOrientationDifferent; - float fullscreenTaskWidth = screenWidthPx; - if (mSplitBounds != null && !mSplitBounds.appsStackedVertically) { - // For landscape, scale the width - float taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT - ? mSplitBounds.leftTaskPercent - : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent)); - // Scale landscape width to that of actual screen - fullscreenTaskWidth = screenWidthPx * taskPercent; - } int thumbnailRotation = thumbnailData.rotation; int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation); RectF thumbnailClipHint = new RectF(); - float canvasScreenRatio = canvasWidth / fullscreenTaskWidth; - float scaledTaskbarSize = taskbarSize * canvasScreenRatio; - thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0; + + float scaledTaskbarSize = 0; + if (mSplitBounds != null) { + float fullscreenTaskWidth; + float fullscreenTaskHeight; + float canvasScreenRatio; + + float taskPercent; + if (!mSplitBounds.appsStackedVertically) { + // For landscape, scale the width + taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT + ? mSplitBounds.leftTaskPercent + : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent)); + // Scale landscape width to that of actual screen + fullscreenTaskWidth = screenWidthPx * taskPercent; + canvasScreenRatio = canvasWidth / fullscreenTaskWidth; + } else { + taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT + ? mSplitBounds.leftTaskPercent + : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent)); + // Scale landscape width to that of actual screen + fullscreenTaskHeight = screenHeightPx * taskPercent; + canvasScreenRatio = canvasHeight / fullscreenTaskHeight; + } + scaledTaskbarSize = taskbarSize * canvasScreenRatio; + thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0; + } float scale = thumbnailData.scale; final float thumbnailScale; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 73229c321079..faaba63938bf 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.inputmethod.InputMethodManager; @@ -152,7 +153,9 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> } public void startAppearAnimation() { - mMessageAreaController.setMessage(getInitialMessageResId()); + if (TextUtils.isEmpty(mMessageAreaController.getMessage())) { + mMessageAreaController.setMessage(getInitialMessageResId()); + } mView.startAppearAnimation(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index 2bd3ca59b740..db986e0a631a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -103,6 +103,11 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> mView.setNextMessageColor(colorState); } + /** Returns the message of the underlying TextView. */ + public CharSequence getMessage() { + return mView.getText(); + } + /** * Reload colors from resources. **/ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 2bb3a5f437f5..ffcf42f74a55 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -1097,11 +1097,19 @@ public class KeyguardSecurityContainer extends ConstraintLayout { new KeyguardSecurityViewTransition()); } int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans); + int viewFlipperBottomMargin = mResources.getDimensionPixelSize( + R.dimen.bouncer_user_switcher_view_mode_view_flipper_bottom_margin); + int userSwitcherBottomMargin = mResources.getDimensionPixelSize( + R.dimen.bouncer_user_switcher_view_mode_user_switcher_bottom_margin); if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans); - constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP); - constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM); + constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, mViewFlipper.getId(), + TOP, userSwitcherBottomMargin); + constraintSet.connect(mViewFlipper.getId(), TOP, mUserSwitcherViewGroup.getId(), + BOTTOM); + constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM, + viewFlipperBottomMargin); constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID); constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID); constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD); diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt index 5d52056d8b17..90ecb466b5d0 100644 --- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt +++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt @@ -169,7 +169,7 @@ open class DisplayCutoutBaseView : View, RegionInterceptableView { return } cutoutPath.reset() - display.getDisplayInfo(displayInfo) + context.display?.getDisplayInfo(displayInfo) displayInfo.displayCutout?.cutoutPath?.let { path -> cutoutPath.set(path) } invalidate() } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 11d579d481c1..45f9385a2620 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -170,6 +170,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private Display.Mode mDisplayMode; @VisibleForTesting protected DisplayInfo mDisplayInfo = new DisplayInfo(); + private DisplayCutout mDisplayCutout; @VisibleForTesting protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) { @@ -384,6 +385,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mRotation = mDisplayInfo.rotation; mDisplayMode = mDisplayInfo.getMode(); mDisplayUniqueId = mDisplayInfo.uniqueId; + mDisplayCutout = mDisplayInfo.displayCutout; mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(), mDisplayUniqueId); mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( @@ -1022,7 +1024,8 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mRoundedCornerResDelegate.dump(pw, args); } - private void updateConfiguration() { + @VisibleForTesting + void updateConfiguration() { Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), "must call on " + mHandler.getLooper().getThread() + ", but was " + Thread.currentThread()); @@ -1033,11 +1036,14 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mDotViewController.setNewRotation(newRotation); } final Display.Mode newMod = mDisplayInfo.getMode(); + final DisplayCutout newCutout = mDisplayInfo.displayCutout; if (!mPendingConfigChange - && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) { + && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod) + || !Objects.equals(newCutout, mDisplayCutout))) { mRotation = newRotation; mDisplayMode = newMod; + mDisplayCutout = newCutout; mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( getPhysicalPixelDisplaySizeRatio()); if (mScreenDecorHwcLayer != null) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt index 588ef5c4e68f..4dfcd6398a4d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt @@ -16,16 +16,120 @@ package com.android.systemui.controls +import android.Manifest +import android.content.ComponentName import android.content.Context +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE +import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE +import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo +import android.os.UserHandle +import android.service.controls.ControlsProviderService +import androidx.annotation.WorkerThread import com.android.settingslib.applications.DefaultAppInfo +import java.util.Objects class ControlsServiceInfo( - context: Context, + private val context: Context, val serviceInfo: ServiceInfo ) : DefaultAppInfo( context, context.packageManager, context.userId, serviceInfo.componentName -)
\ No newline at end of file +) { + private val _panelActivity: ComponentName? + + init { + val metadata = serviceInfo.metaData + ?.getString(ControlsProviderService.META_DATA_PANEL_ACTIVITY) ?: "" + val unflatenned = ComponentName.unflattenFromString(metadata) + if (unflatenned != null && unflatenned.packageName == componentName.packageName) { + _panelActivity = unflatenned + } else { + _panelActivity = null + } + } + + /** + * Component name of an activity that will be shown embedded in the device controls space + * instead of using the controls rendered by SystemUI. + * + * The activity must be in the same package, exported, enabled and protected by the + * [Manifest.permission.BIND_CONTROLS] permission. + */ + var panelActivity: ComponentName? = null + private set + + private var resolved: Boolean = false + + @WorkerThread + fun resolvePanelActivity() { + if (resolved) return + resolved = true + panelActivity = _panelActivity?.let { + val resolveInfos = mPm.queryIntentActivitiesAsUser( + Intent().setComponent(it), + PackageManager.ResolveInfoFlags.of( + MATCH_DIRECT_BOOT_AWARE.toLong() or + MATCH_DIRECT_BOOT_UNAWARE.toLong() + ), + UserHandle.of(userId) + ) + if (resolveInfos.isNotEmpty() && verifyResolveInfo(resolveInfos[0])) { + it + } else { + null + } + } + } + + /** + * Verifies that the panel activity is enabled, exported and protected by the correct + * permission. This last check is to prevent apps from forgetting to protect the activity, as + * they won't be able to see the panel until they do. + */ + @WorkerThread + private fun verifyResolveInfo(resolveInfo: ResolveInfo): Boolean { + return resolveInfo.activityInfo?.let { + it.permission == Manifest.permission.BIND_CONTROLS && + it.exported && isComponentActuallyEnabled(it) + } ?: false + } + + @WorkerThread + private fun isComponentActuallyEnabled(activityInfo: ActivityInfo): Boolean { + return when (mPm.getComponentEnabledSetting(activityInfo.componentName)) { + PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> true + PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> false + PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> activityInfo.enabled + else -> false + } + } + + override fun equals(other: Any?): Boolean { + return other is ControlsServiceInfo && + userId == other.userId && + componentName == other.componentName && + panelActivity == other.panelActivity + } + + override fun hashCode(): Int { + return Objects.hash(userId, componentName, panelActivity) + } + + fun copy(): ControlsServiceInfo { + return ControlsServiceInfo(context, serviceInfo).also { + it.panelActivity = this.panelActivity + } + } + + override fun toString(): String { + return """ + ControlsServiceInfo(serviceInfo=$serviceInfo, panelActivity=$panelActivity, resolved=$resolved) + """.trimIndent() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt index 2d76ff2774d6..115edd115ffe 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt @@ -18,17 +18,23 @@ package com.android.systemui.controls.management import android.content.ComponentName import android.content.Context -import android.content.pm.ServiceInfo import android.os.UserHandle import android.service.controls.ControlsProviderService import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.applications.ServiceListing import com.android.settingslib.widget.CandidateInfo +import com.android.systemui.Dumpable import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker +import com.android.systemui.util.asIndenting +import com.android.systemui.util.indentIfPossible +import java.io.PrintWriter import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicInteger import javax.inject.Inject @@ -57,16 +63,19 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( private val context: Context, @Background private val backgroundExecutor: Executor, private val serviceListingBuilder: (Context) -> ServiceListing, - userTracker: UserTracker -) : ControlsListingController { + private val userTracker: UserTracker, + dumpManager: DumpManager, + featureFlags: FeatureFlags +) : ControlsListingController, Dumpable { @Inject - constructor(context: Context, executor: Executor, userTracker: UserTracker): this( - context, - executor, - ::createServiceListing, - userTracker - ) + constructor( + context: Context, + @Background executor: Executor, + userTracker: UserTracker, + dumpManager: DumpManager, + featureFlags: FeatureFlags + ) : this(context, executor, ::createServiceListing, userTracker, dumpManager, featureFlags) private var serviceListing = serviceListingBuilder(context) // All operations in background thread @@ -76,27 +85,25 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( private const val TAG = "ControlsListingControllerImpl" } - private var availableComponents = emptySet<ComponentName>() - private var availableServices = emptyList<ServiceInfo>() + private var availableServices = emptyList<ControlsServiceInfo>() private var userChangeInProgress = AtomicInteger(0) override var currentUserId = userTracker.userId private set private val serviceListingCallback = ServiceListing.Callback { - val newServices = it.toList() - val newComponents = - newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() }) - backgroundExecutor.execute { if (userChangeInProgress.get() > 0) return@execute - if (!newComponents.equals(availableComponents)) { - Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}") - availableComponents = newComponents + Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}") + val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) } + if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) { + newServices.forEach(ControlsServiceInfo::resolvePanelActivity) + } + + if (newServices != availableServices) { availableServices = newServices - val currentServices = getCurrentServices() callbacks.forEach { - it.onServicesUpdated(currentServices) + it.onServicesUpdated(getCurrentServices()) } } } @@ -104,6 +111,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( init { Log.d(TAG, "Initializing") + dumpManager.registerDumpable(TAG, this) serviceListing.addCallback(serviceListingCallback) serviceListing.setListening(true) serviceListing.reload() @@ -165,7 +173,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( * [ControlsProviderService] */ override fun getCurrentServices(): List<ControlsServiceInfo> = - availableServices.map { ControlsServiceInfo(context, it) } + availableServices.map(ControlsServiceInfo::copy) /** * Get the localized label for the component. @@ -174,7 +182,15 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( * @return a label as returned by [CandidateInfo.loadLabel] or `null`. */ override fun getAppLabel(name: ComponentName): CharSequence? { - return getCurrentServices().firstOrNull { it.componentName == name } + return availableServices.firstOrNull { it.componentName == name } ?.loadLabel() } + + override fun dump(writer: PrintWriter, args: Array<out String>) { + writer.println("ControlsListingController:") + writer.asIndenting().indentIfPossible { + println("Callbacks: $callbacks") + println("Services: ${getCurrentServices()}") + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java index d3555eec0243..7ab36e84178e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java @@ -16,7 +16,6 @@ package com.android.systemui.dagger; -import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider; import com.android.systemui.statusbar.QsFrameTranslateModule; import dagger.Subcomponent; @@ -43,9 +42,4 @@ public interface ReferenceSysUIComponent extends SysUIComponent { interface Builder extends SysUIComponent.Builder { ReferenceSysUIComponent build(); } - - /** - * Member injection into the supplied argument. - */ - void inject(KeyguardQuickAffordanceProvider keyguardQuickAffordanceProvider); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 99dfefa4fa41..002ffdb68682 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -146,8 +146,10 @@ object Flags { // TODO(b/255607168): Tracking Bug @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1") + // TODO(b/252897742): Tracking Bug @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection") + // TODO(b/252897742): Tracking Bug @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay") /** @@ -160,6 +162,10 @@ object Flags { val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false) + /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */ + // TODO(b/240196500): Tracking Bug + @JvmField val ACTIVE_UNLOCK_CHIPBAR = unreleasedFlag(217, "active_unlock_chipbar") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt deleted file mode 100644 index 0f4581ce3e61..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt +++ /dev/null @@ -1,297 +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.keyguard - -import android.content.ContentProvider -import android.content.ContentValues -import android.content.Context -import android.content.UriMatcher -import android.content.pm.ProviderInfo -import android.database.Cursor -import android.database.MatrixCursor -import android.net.Uri -import android.util.Log -import com.android.systemui.SystemUIAppComponentFactoryBase -import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract -import javax.inject.Inject -import kotlinx.coroutines.runBlocking - -class KeyguardQuickAffordanceProvider : - ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer { - - @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor - - private lateinit var contextAvailableCallback: ContextAvailableCallback - - private val uriMatcher = - UriMatcher(UriMatcher.NO_MATCH).apply { - addURI( - Contract.AUTHORITY, - Contract.SlotTable.TABLE_NAME, - MATCH_CODE_ALL_SLOTS, - ) - addURI( - Contract.AUTHORITY, - Contract.AffordanceTable.TABLE_NAME, - MATCH_CODE_ALL_AFFORDANCES, - ) - addURI( - Contract.AUTHORITY, - Contract.SelectionTable.TABLE_NAME, - MATCH_CODE_ALL_SELECTIONS, - ) - } - - override fun onCreate(): Boolean { - return true - } - - override fun attachInfo(context: Context?, info: ProviderInfo?) { - contextAvailableCallback.onContextAvailable(checkNotNull(context)) - super.attachInfo(context, info) - } - - override fun setContextAvailableCallback(callback: ContextAvailableCallback) { - contextAvailableCallback = callback - } - - override fun getType(uri: Uri): String? { - val prefix = - when (uriMatcher.match(uri)) { - MATCH_CODE_ALL_SLOTS, - MATCH_CODE_ALL_AFFORDANCES, - MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd." - else -> null - } - - val tableName = - when (uriMatcher.match(uri)) { - MATCH_CODE_ALL_SLOTS -> Contract.SlotTable.TABLE_NAME - MATCH_CODE_ALL_AFFORDANCES -> Contract.AffordanceTable.TABLE_NAME - MATCH_CODE_ALL_SELECTIONS -> Contract.SelectionTable.TABLE_NAME - else -> null - } - - if (prefix == null || tableName == null) { - return null - } - - return "$prefix${Contract.AUTHORITY}.$tableName" - } - - override fun insert(uri: Uri, values: ContentValues?): Uri? { - if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { - throw UnsupportedOperationException() - } - - return insertSelection(values) - } - - override fun query( - uri: Uri, - projection: Array<out String>?, - selection: String?, - selectionArgs: Array<out String>?, - sortOrder: String?, - ): Cursor? { - return when (uriMatcher.match(uri)) { - MATCH_CODE_ALL_AFFORDANCES -> queryAffordances() - MATCH_CODE_ALL_SLOTS -> querySlots() - MATCH_CODE_ALL_SELECTIONS -> querySelections() - else -> null - } - } - - override fun update( - uri: Uri, - values: ContentValues?, - selection: String?, - selectionArgs: Array<out String>?, - ): Int { - Log.e(TAG, "Update is not supported!") - return 0 - } - - override fun delete( - uri: Uri, - selection: String?, - selectionArgs: Array<out String>?, - ): Int { - if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { - throw UnsupportedOperationException() - } - - return deleteSelection(uri, selectionArgs) - } - - private fun insertSelection(values: ContentValues?): Uri? { - if (values == null) { - throw IllegalArgumentException("Cannot insert selection, no values passed in!") - } - - if (!values.containsKey(Contract.SelectionTable.Columns.SLOT_ID)) { - throw IllegalArgumentException( - "Cannot insert selection, " + - "\"${Contract.SelectionTable.Columns.SLOT_ID}\" not specified!" - ) - } - - if (!values.containsKey(Contract.SelectionTable.Columns.AFFORDANCE_ID)) { - throw IllegalArgumentException( - "Cannot insert selection, " + - "\"${Contract.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!" - ) - } - - val slotId = values.getAsString(Contract.SelectionTable.Columns.SLOT_ID) - val affordanceId = values.getAsString(Contract.SelectionTable.Columns.AFFORDANCE_ID) - - if (slotId.isNullOrEmpty()) { - throw IllegalArgumentException("Cannot insert selection, slot ID was empty!") - } - - if (affordanceId.isNullOrEmpty()) { - throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!") - } - - val success = runBlocking { - interactor.select( - slotId = slotId, - affordanceId = affordanceId, - ) - } - - return if (success) { - Log.d(TAG, "Successfully selected $affordanceId for slot $slotId") - context?.contentResolver?.notifyChange(Contract.SelectionTable.URI, null) - Contract.SelectionTable.URI - } else { - Log.d(TAG, "Failed to select $affordanceId for slot $slotId") - null - } - } - - private fun querySelections(): Cursor { - return MatrixCursor( - arrayOf( - Contract.SelectionTable.Columns.SLOT_ID, - Contract.SelectionTable.Columns.AFFORDANCE_ID, - ) - ) - .apply { - val affordanceIdsBySlotId = runBlocking { interactor.getSelections() } - affordanceIdsBySlotId.entries.forEach { (slotId, affordanceIds) -> - affordanceIds.forEach { affordanceId -> - addRow( - arrayOf( - slotId, - affordanceId, - ) - ) - } - } - } - } - - private fun queryAffordances(): Cursor { - return MatrixCursor( - arrayOf( - Contract.AffordanceTable.Columns.ID, - Contract.AffordanceTable.Columns.NAME, - Contract.AffordanceTable.Columns.ICON, - ) - ) - .apply { - interactor.getAffordancePickerRepresentations().forEach { representation -> - addRow( - arrayOf( - representation.id, - representation.name, - representation.iconResourceId, - ) - ) - } - } - } - - private fun querySlots(): Cursor { - return MatrixCursor( - arrayOf( - Contract.SlotTable.Columns.ID, - Contract.SlotTable.Columns.CAPACITY, - ) - ) - .apply { - interactor.getSlotPickerRepresentations().forEach { representation -> - addRow( - arrayOf( - representation.id, - representation.maxSelectedAffordances, - ) - ) - } - } - } - - private fun deleteSelection( - uri: Uri, - selectionArgs: Array<out String>?, - ): Int { - if (selectionArgs == null) { - throw IllegalArgumentException( - "Cannot delete selection, selection arguments not included!" - ) - } - - val (slotId, affordanceId) = - when (selectionArgs.size) { - 1 -> Pair(selectionArgs[0], null) - 2 -> Pair(selectionArgs[0], selectionArgs[1]) - else -> - throw IllegalArgumentException( - "Cannot delete selection, selection arguments has wrong size, expected to" + - " have 1 or 2 arguments, had ${selectionArgs.size} instead!" - ) - } - - val deleted = runBlocking { - interactor.unselect( - slotId = slotId, - affordanceId = affordanceId, - ) - } - - return if (deleted) { - Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId") - context?.contentResolver?.notifyChange(uri, null) - 1 - } else { - Log.d(TAG, "Failed to unselect $affordanceId for slot $slotId") - 0 - } - } - - companion object { - private const val TAG = "KeyguardQuickAffordanceProvider" - private const val MATCH_CODE_ALL_SLOTS = 1 - private const val MATCH_CODE_ALL_AFFORDANCES = 2 - private const val MATCH_CODE_ALL_SELECTIONS = 3 - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index d4514c5cb7aa..783f752cbd20 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -16,9 +16,7 @@ package com.android.systemui.keyguard.data.repository -import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.KeyguardUpdateMonitor -import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.ViewMediatorCallback import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel @@ -70,33 +68,15 @@ constructor( private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null) /** Determines if user is already unlocked */ val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow() - - var bouncerPromptReason: Int? = null private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null) val showMessage = _showMessage.asStateFlow() private val _resourceUpdateRequests = MutableStateFlow(false) val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow() - + val bouncerPromptReason: Int + get() = viewMediatorCallback.bouncerPromptReason val bouncerErrorMessage: CharSequence? get() = viewMediatorCallback.consumeCustomMessage() - init { - val callback = - object : KeyguardUpdateMonitorCallback() { - override fun onStrongAuthStateChanged(userId: Int) { - bouncerPromptReason = viewMediatorCallback.bouncerPromptReason - } - - override fun onLockedOutStateChanged(type: BiometricSourceType) { - if (type == BiometricSourceType.FINGERPRINT) { - bouncerPromptReason = viewMediatorCallback.bouncerPromptReason - } - } - } - - keyguardUpdateMonitor.registerCallback(callback) - } - fun setPrimaryScrimmed(isScrimmed: Boolean) { _primaryBouncerScrimmed.value = isScrimmed } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index ca25282ec2f0..9d5d8bbd4f40 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.Position @@ -69,6 +70,9 @@ interface KeyguardRepository { */ val isKeyguardShowing: Flow<Boolean> + /** Observable for the signal that keyguard is about to go away. */ + val isKeyguardGoingAway: Flow<Boolean> + /** Observable for whether the bouncer is showing. */ val isBouncerShowing: Flow<Boolean> @@ -85,6 +89,14 @@ interface KeyguardRepository { val isDozing: Flow<Boolean> /** + * Observable for whether the device is dreaming. + * + * Dozing/AOD is a specific type of dream, but it is also possible for other non-systemui dreams + * to be active, such as screensavers. + */ + val isDreaming: Flow<Boolean> + + /** * Observable for the amount of doze we are currently in. * * While in doze state, this amount can change - driving a cycle of animations designed to avoid @@ -136,12 +148,12 @@ interface KeyguardRepository { class KeyguardRepositoryImpl @Inject constructor( - statusBarStateController: StatusBarStateController, - dozeHost: DozeHost, - wakefulnessLifecycle: WakefulnessLifecycle, - biometricUnlockController: BiometricUnlockController, - private val keyguardStateController: KeyguardStateController, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + statusBarStateController: StatusBarStateController, + dozeHost: DozeHost, + wakefulnessLifecycle: WakefulnessLifecycle, + biometricUnlockController: BiometricUnlockController, + private val keyguardStateController: KeyguardStateController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -176,6 +188,29 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } + override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow { + val callback = + object : KeyguardStateController.Callback { + override fun onKeyguardGoingAwayChanged() { + trySendWithFailureLogging( + keyguardStateController.isKeyguardGoingAway, + TAG, + "updated isKeyguardGoingAway" + ) + } + } + + keyguardStateController.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + keyguardStateController.isKeyguardGoingAway, + TAG, + "initial isKeyguardGoingAway" + ) + + awaitClose { keyguardStateController.removeCallback(callback) } + } + override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -218,6 +253,25 @@ constructor( } .distinctUntilChanged() + override val isDreaming: Flow<Boolean> = + conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onDreamingStateChanged(isDreaming: Boolean) { + trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming") + } + } + keyguardUpdateMonitor.registerCallback(callback) + trySendWithFailureLogging( + keyguardUpdateMonitor.isDreaming, + TAG, + "initial isDreaming", + ) + + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .distinctUntilChanged() + override val dozeAmount: Flow<Float> = conflatedCallbackFlow { val callback = object : StatusBarStateController.StateListener { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index e3d1a27dad2b..bce7d92cd8fb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -94,11 +94,13 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio */ private val _transitions = MutableSharedFlow<TransitionStep>( + replay = 2, extraBufferCapacity = 10, - onBufferOverflow = BufferOverflow.DROP_OLDEST + onBufferOverflow = BufferOverflow.DROP_OLDEST, ) override val transitions = _transitions.asSharedFlow().distinctUntilChanged() private var lastStep: TransitionStep = TransitionStep() + private var lastAnimator: ValueAnimator? = null /* * When manual control of the transition is requested, a unique [UUID] is used as the handle @@ -106,19 +108,39 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio */ private var updateTransitionId: UUID? = null + init { + // Seed with transitions signaling a boot into lockscreen state + emitTransition( + TransitionStep( + KeyguardState.OFF, + KeyguardState.LOCKSCREEN, + 0f, + TransitionState.STARTED, + ) + ) + emitTransition( + TransitionStep( + KeyguardState.OFF, + KeyguardState.LOCKSCREEN, + 1f, + TransitionState.FINISHED, + ) + ) + } + override fun startTransition(info: TransitionInfo): UUID? { if (lastStep.transitionState != TransitionState.FINISHED) { - // Open questions: - // * Queue of transitions? buffer of 1? - // * Are transitions cancellable if a new one is triggered? - // * What validation does this need to do? - Log.wtf(TAG, "Transition still active: $lastStep") - return null + Log.i(TAG, "Transition still active: $lastStep, canceling") } + val startingValue = 1f - lastStep.value + lastAnimator?.cancel() + lastAnimator = info.animator + info.animator?.let { animator -> // An animator was provided, so use it to run the transition - animator.setFloatValues(0f, 1f) + animator.setFloatValues(startingValue, 1f) + animator.duration = ((1f - startingValue) * animator.duration).toLong() val updateListener = object : AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { @@ -134,15 +156,24 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio val adapter = object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator) { - emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) + emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED)) } override fun onAnimationCancel(animation: Animator) { - Log.i(TAG, "Cancelling transition: $info") + endAnimation(animation, lastStep.value, TransitionState.CANCELED) } override fun onAnimationEnd(animation: Animator) { - emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED)) + endAnimation(animation, 1f, TransitionState.FINISHED) + } + + private fun endAnimation( + animation: Animator, + value: Float, + state: TransitionState + ) { + emitTransition(TransitionStep(info, value, state)) animator.removeListener(this) animator.removeUpdateListener(updateListener) + lastAnimator = null } } animator.addListener(adapter) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt index 0aeff7fc69fd..e5521c76705d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt @@ -20,10 +20,11 @@ import android.animation.ValueAnimator import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -35,18 +36,30 @@ class AodLockscreenTransitionInteractor @Inject constructor( @Application private val scope: CoroutineScope, - private val keyguardRepository: KeyguardRepository, + private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) : TransitionInteractor("AOD<->LOCKSCREEN") { override fun start() { scope.launch { - keyguardRepository.isDozing - .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + /* + * Listening to the startedKeyguardTransitionStep (last started step) allows this code + * to interrupt an active transition, as long as they were either going to LOCKSCREEN or + * AOD state. One example is when the user presses the power button in the middle of an + * active transition. + */ + keyguardInteractor.wakefulnessState + .sample( + keyguardTransitionInteractor.startedKeyguardTransitionStep, + { a, b -> Pair(a, b) } + ) .collect { pair -> - val (isDozing, keyguardState) = pair - if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) { + val (wakefulnessState, lastStartedStep) = pair + if ( + isSleepingOrStartingToSleep(wakefulnessState) && + lastStartedStep.to == KeyguardState.LOCKSCREEN + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, @@ -55,7 +68,10 @@ constructor( getAnimator(), ) ) - } else if (!isDozing && keyguardState == KeyguardState.AOD) { + } else if ( + isWakingOrStartingToWake(wakefulnessState) && + lastStartedStep.to == KeyguardState.AOD + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt new file mode 100644 index 000000000000..dd2967334307 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class BouncerToGoneTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val shadeRepository: ShadeRepository, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor +) : TransitionInteractor("BOUNCER->GONE") { + + private var transitionId: UUID? = null + + override fun start() { + listenForKeyguardGoingAway() + } + + private fun listenForKeyguardGoingAway() { + scope.launch { + keyguardInteractor.isKeyguardGoingAway + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (isKeyguardGoingAway && keyguardState == KeyguardState.BOUNCER) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.BOUNCER, + to = KeyguardState.GONE, + animator = getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 300L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt new file mode 100644 index 000000000000..c44cda42c68d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt @@ -0,0 +1,81 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingLockscreenTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING<->LOCKSCREEN") { + + override fun start() { + scope.launch { + keyguardInteractor.isDreaming + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isDreaming, keyguardState) = pair + if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + getAnimator(), + ) + ) + } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt new file mode 100644 index 000000000000..9e2b7241ade2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingToAodTransitionInteractor.kt @@ -0,0 +1,76 @@ +/* + * 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.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class DreamingToAodTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("DREAMING->AOD") { + + override fun start() { + scope.launch { + keyguardInteractor.wakefulnessState + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (wakefulnessState, keyguardState) = pair + if ( + isSleepingOrStartingToSleep(wakefulnessState) && + keyguardState == KeyguardState.DREAMING + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.DREAMING, + KeyguardState.AOD, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 300L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 614ff8d930d8..5a1c70264ee4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -41,8 +41,15 @@ constructor( val dozeAmount: Flow<Float> = repository.dozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing + /** + * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true, + * but not vice-versa. + */ + val isDreaming: Flow<Boolean> = repository.isDreaming /** Whether the keyguard is showing or not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** Whether the keyguard is going away. */ + val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway /** Whether the bouncer is showing or not. */ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing /** The device wake/sleep state */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 57fb4a114700..58a8093d49d2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -41,12 +41,24 @@ constructor( } scope.launch { + keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) } + } + + scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } } + + scope.launch { interactor.finishedKeyguardTransitionStep.collect { logger.i("Finished transition", it) } } scope.launch { + interactor.canceledKeyguardTransitionStep.collect { + logger.i("Canceled transition", it) + } + } + + scope.launch { interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index a7c6d4450336..43dd358e4808 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -42,6 +42,9 @@ constructor( is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it") is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") + is DreamingToAodTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 749183e241e5..54a4f493d21d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -57,6 +57,14 @@ constructor( lockscreenToAodTransition, ) + /* The last [TransitionStep] with a [TransitionState] of STARTED */ + val startedKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED } + + /* The last [TransitionStep] with a [TransitionState] of CANCELED */ + val canceledKeyguardTransitionStep: Flow<TransitionStep> = + repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED } + /* The last [TransitionStep] with a [TransitionState] of FINISHED */ val finishedKeyguardTransitionStep: Flow<TransitionStep> = repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED } @@ -64,8 +72,4 @@ constructor( /* The last completed [KeyguardState] transition */ val finishedKeyguardState: Flow<KeyguardState> = finishedKeyguardTransitionStep.map { step -> step.to } - - /* The last [TransitionStep] with a [TransitionState] of STARTED */ - val startedKeyguardTransitionStep: Flow<TransitionStep> = - repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt index fd4814d2bc94..cca2d566556e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -56,10 +56,20 @@ constructor( private fun listenForBouncerHiding() { scope.launch { keyguardInteractor.isBouncerShowing - .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) }) - .collect { pair -> - val (isBouncerShowing, wakefulnessState) = pair - if (!isBouncerShowing) { + .sample( + combine( + keyguardInteractor.wakefulnessState, + keyguardTransitionInteractor.startedKeyguardTransitionStep, + ) { a, b -> + Pair(a, b) + }, + { a, bc -> Triple(a, bc.first, bc.second) } + ) + .collect { triple -> + val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple + if ( + !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER + ) { val to = if ( wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP || @@ -90,10 +100,10 @@ constructor( combine( keyguardTransitionInteractor.finishedKeyguardState, keyguardInteractor.statusBarState, - ) { keyguardState, statusBarState -> - Pair(keyguardState, statusBarState) + ) { a, b -> + Pair(a, b) }, - { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) } + { a, bc -> Triple(a, bc.first, bc.second) } ) .collect { triple -> val (shadeModel, keyguardState, statusBarState) = triple @@ -116,8 +126,7 @@ constructor( ) } else { // TODO (b/251849525): Remove statusbarstate check when that state is - // integrated - // into KeyguardTransitionRepository + // integrated into KeyguardTransitionRepository if ( keyguardState == KeyguardState.LOCKSCREEN && shadeModel.isUserDragging && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt index 6c1adbd68ef2..4100f7a8413a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect @@ -34,23 +35,27 @@ class LockscreenGoneTransitionInteractor constructor( @Application private val scope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardTransitionRepository: KeyguardTransitionRepository, ) : TransitionInteractor("LOCKSCREEN->GONE") { override fun start() { scope.launch { - keyguardInteractor.isKeyguardShowing.collect { isShowing -> - if (!isShowing) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.LOCKSCREEN, - KeyguardState.GONE, - getAnimator(), + keyguardInteractor.isKeyguardGoingAway + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isKeyguardGoingAway, keyguardState) = pair + if (!isKeyguardGoingAway && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.GONE, + getAnimator(), + ) ) - ) + } } - } } } 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 c22f4e7a2634..fcd653b33a6f 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 @@ -210,9 +210,12 @@ constructor( expansion == KeyguardBouncer.EXPANSION_HIDDEN && oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN ) { - repository.setPrimaryVisible(false) - repository.setPrimaryShow(null) - falsingCollector.onBouncerHidden() + /* + * There are cases where #hide() was not invoked, such as when + * NotificationPanelViewController controls the hide animation. Make sure the state gets + * updated by calling #hide() directly. + */ + hide() DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() } primaryBouncerCallbackInteractor.dispatchFullyHidden() } else if ( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index 37f33afbf53e..dbffeab436a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -46,5 +46,19 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet + abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingLockscreen( + impl: DreamingLockscreenTransitionInteractor + ): TransitionInteractor + + @Binds + @IntoSet + abstract fun dreamingToAod(impl: DreamingToAodTransitionInteractor): TransitionInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index 7958033ba017..dd908c420fcb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -17,12 +17,29 @@ package com.android.systemui.keyguard.shared.model /** List of all possible states to transition to/from */ enum class KeyguardState { + /* + * The display is completely off, as well as any sensors that would trigger the device to wake + * up. + */ + OFF, /** - * For initialization as well as when the security method is set to NONE, indicating that - * the keyguard should never be shown. + * The device has entered a special low-power mode within SystemUI. Doze is technically a + * special dream service implementation. No UI is visible. In this state, a least some + * low-powered sensors such as lift to wake or tap to wake are enabled, or wake screen for + * notifications is enabled, allowing the device to quickly wake up. + */ + DOZING, + /* + * A device state after the device times out, which can be from both LOCKSCREEN or GONE states. + * DOZING is an example of special version of this state. Dreams may be implemented by third + * parties to present their own UI over keyguard, like a screensaver. + */ + DREAMING, + /** + * The device has entered a special low-power mode within SystemUI, also called the Always-on + * Display (AOD). A minimal UI is presented to show critical information. If the device is in + * low-power mode without a UI, then it is DOZING. */ - NONE, - /* Always-on Display. The device is in a low-power mode with a minimal UI visible */ AOD, /* * The security screen prompt UI, containing PIN, Password, Pattern, and all FPS @@ -34,7 +51,6 @@ enum class KeyguardState { * unlocked if SWIPE security method is used, or if face lockscreen bypass is false. */ LOCKSCREEN, - /* * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard * is being removed, but there are other cases where the user is swiping away keyguard, such as diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt index 0e0465bb5207..38a93b50ea97 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt @@ -17,7 +17,12 @@ package com.android.systemui.keyguard.shared.model /** Possible states for a running transition between [State] */ enum class TransitionState { + /* Transition has begun. */ STARTED, + /* Transition is actively running. */ RUNNING, - FINISHED + /* Transition has completed successfully. */ + FINISHED, + /* Transition has been interrupted, and not completed successfully. */ + CANCELED, } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 732a6f7b887a..767fd58f78e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -17,8 +17,8 @@ package com.android.systemui.keyguard.shared.model /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */ data class TransitionStep( - val from: KeyguardState = KeyguardState.NONE, - val to: KeyguardState = KeyguardState.NONE, + val from: KeyguardState = KeyguardState.OFF, + val to: KeyguardState = KeyguardState.OFF, val value: Float = 0f, // constrained [0.0, 1.0] val transitionState: TransitionState = TransitionState.FINISHED, val ownerName: String = "", diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt index 64f834d6c5ab..92040f4f0348 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt @@ -24,5 +24,15 @@ enum class WakefulnessModel { /** Device is now fully awake and interactive. */ AWAKE, /** Signal that the device is now going to sleep. */ - STARTING_TO_SLEEP, + STARTING_TO_SLEEP; + + companion object { + fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean { + return model == ASLEEP || model == STARTING_TO_SLEEP + } + + fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean { + return model == AWAKE || model == STARTING_TO_WAKE + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index a22958b74bb9..7739a456fcb7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -94,6 +94,10 @@ object KeyguardBouncerViewBinder { viewModel.setBouncerViewDelegate(delegate) launch { viewModel.show.collect { + hostViewController.showPromptReason(it.promptReason) + it.errorMessage?.let { errorMessage -> + hostViewController.showErrorMessage(errorMessage) + } hostViewController.showPrimarySecurityScreen() hostViewController.appear( SystemBarUtils.getStatusBarHeight(view.context) @@ -102,18 +106,6 @@ object KeyguardBouncerViewBinder { } launch { - viewModel.showPromptReason.collect { prompt -> - hostViewController.showPromptReason(prompt) - } - } - - launch { - viewModel.showBouncerErrorMessage.collect { errorMessage -> - hostViewController.showErrorMessage(errorMessage) - } - } - - launch { viewModel.showWithFullExpansion.collect { model -> hostViewController.resetSecurityContainer() hostViewController.showPromptReason(model.promptReason) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt index 07816001f45c..526ae741793c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -26,7 +26,6 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map /** Models UI state for the lock screen bouncer; handles user input. */ @@ -45,13 +44,6 @@ constructor( /** Observe whether bouncer is showing. */ val show: Flow<KeyguardBouncerModel> = interactor.show - /** Observe bouncer prompt when bouncer is showing. */ - val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason } - - /** Observe bouncer error message when bouncer is showing. */ - val showBouncerErrorMessage: Flow<CharSequence> = - interactor.show.map { it.errorMessage }.filterNotNull() - /** Observe visible expansion when bouncer is showing. */ val showWithFullExpansion: Flow<KeyguardBouncerModel> = interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt index b682bd172837..d4991f90a86b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt @@ -148,6 +148,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 val currentRotation: Int = display.rotation val displayWidthPx = windowMetrics.bounds.width() + val displayHeightPx = windowMetrics.bounds.height() val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL val isTablet = isTablet(context) val taskbarSize = @@ -163,6 +164,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 measuredWidth, measuredHeight, displayWidthPx, + displayHeightPx, taskbarSize, isTablet, currentRotation, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 920a108382fb..d9be2810d165 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -515,7 +515,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void setExpanded(boolean expanded) { if (DEBUG) Log.d(TAG, "setExpanded " + expanded); mQsExpanded = expanded; - updateQsPanelControllerListening(); + if (mInSplitShade && mQsExpanded) { + // in split shade QS is expanded immediately when shade expansion starts and then we + // also need to listen to changes - otherwise QS is updated only once its fully expanded + setListening(true); + } else { + updateQsPanelControllerListening(); + } updateQsState(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index cffd28f9fc96..19bb15a5c2d8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -24,8 +24,9 @@ import android.os.Handler import android.os.Looper import android.os.ResultReceiver import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.widget.AdapterView -import android.widget.AdapterView.OnItemClickListener import android.widget.ArrayAdapter import android.widget.Spinner import android.widget.Switch @@ -47,6 +48,7 @@ class ScreenRecordPermissionDialog( private val onStartRecordingClicked: Runnable? ) : BaseScreenSharePermissionDialog(context, createOptionList(), null) { private lateinit var tapsSwitch: Switch + private lateinit var tapsView: View private lateinit var audioSwitch: Switch private lateinit var options: Spinner override fun onCreate(savedInstanceState: Bundle?) { @@ -84,16 +86,25 @@ class ScreenRecordPermissionDialog( private fun initRecordOptionsView() { audioSwitch = findViewById(R.id.screenrecord_audio_switch) tapsSwitch = findViewById(R.id.screenrecord_taps_switch) + tapsView = findViewById(R.id.show_taps) + updateTapsViewVisibility() options = findViewById(R.id.screen_recording_options) val a: ArrayAdapter<*> = ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES) a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) options.adapter = a - options.setOnItemClickListenerInt( - OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long -> - audioSwitch.isChecked = true - } - ) + options.setOnItemClickListenerInt { _: AdapterView<*>?, _: View?, _: Int, _: Long -> + audioSwitch.isChecked = true + } + } + + override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) { + super.onItemSelected(adapterView, view, pos, id) + updateTapsViewVisibility() + } + + private fun updateTapsViewVisibility() { + tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE } /** @@ -103,7 +114,7 @@ class ScreenRecordPermissionDialog( */ private fun requestScreenCapture(captureTarget: MediaProjectionCaptureTarget?) { val userContext = userContextProvider.userContext - val showTaps = tapsSwitch.isChecked + val showTaps = selectedScreenShareOption.mode != SINGLE_APP && tapsSwitch.isChecked val audioMode = if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource else ScreenRecordingAudioSource.NONE diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 5961635a0dba..01e32b7ada5f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -32,7 +32,7 @@ import android.view.WindowManagerGlobal import com.android.internal.infra.ServiceConnector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import javax.inject.Inject import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineDispatcher @@ -45,7 +45,7 @@ class ActionIntentExecutor @Inject constructor( @Application private val applicationScope: CoroutineScope, - @Background private val bgDispatcher: CoroutineDispatcher, + @Main private val mainDispatcher: CoroutineDispatcher, private val context: Context, ) { /** @@ -70,23 +70,21 @@ constructor( userId: Int, overrideTransition: Boolean, ) { - withContext(bgDispatcher) { - dismissKeyguard() + dismissKeyguard() - if (userId == UserHandle.myUserId()) { - context.startActivity(intent, bundle) - } else { - launchCrossProfileIntent(userId, intent, bundle) - } + if (userId == UserHandle.myUserId()) { + withContext(mainDispatcher) { context.startActivity(intent, bundle) } + } else { + launchCrossProfileIntent(userId, intent, bundle) + } - if (overrideTransition) { - val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0) - try { - WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY) - } catch (e: Exception) { - Log.e(TAG, "Error overriding screenshot app transition", e) - } + if (overrideTransition) { + val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0) + try { + WindowManagerGlobal.getWindowManagerService() + .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY) + } catch (e: Exception) { + Log.e(TAG, "Error overriding screenshot app transition", e) } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 8bf956b86683..5450db98af52 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -46,6 +46,8 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; import com.google.common.util.concurrent.ListenableFuture; @@ -67,6 +69,7 @@ public class LongScreenshotActivity extends Activity { private static final String TAG = LogConfig.logTag(LongScreenshotActivity.class); public static final String EXTRA_CAPTURE_RESPONSE = "capture-response"; + public static final String EXTRA_SCREENSHOT_USER_HANDLE = "screenshot-userhandle"; private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path"; private final UiEventLogger mUiEventLogger; @@ -74,6 +77,8 @@ public class LongScreenshotActivity extends Activity { private final Executor mBackgroundExecutor; private final ImageExporter mImageExporter; private final LongScreenshotData mLongScreenshotHolder; + private final ActionIntentExecutor mActionExecutor; + private final FeatureFlags mFeatureFlags; private ImageView mPreview; private ImageView mTransitionView; @@ -85,6 +90,7 @@ public class LongScreenshotActivity extends Activity { private CropView mCropView; private MagnifierView mMagnifierView; private ScrollCaptureResponse mScrollCaptureResponse; + private UserHandle mScreenshotUserHandle; private File mSavedImagePath; private ListenableFuture<File> mCacheSaveFuture; @@ -103,12 +109,15 @@ public class LongScreenshotActivity extends Activity { @Inject public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter, @Main Executor mainExecutor, @Background Executor bgExecutor, - LongScreenshotData longScreenshotHolder) { + LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor, + FeatureFlags featureFlags) { mUiEventLogger = uiEventLogger; mUiExecutor = mainExecutor; mBackgroundExecutor = bgExecutor; mImageExporter = imageExporter; mLongScreenshotHolder = longScreenshotHolder; + mActionExecutor = actionExecutor; + mFeatureFlags = featureFlags; } @@ -139,6 +148,11 @@ public class LongScreenshotActivity extends Activity { Intent intent = getIntent(); mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE); + mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE, + UserHandle.class); + if (mScreenshotUserHandle == null) { + mScreenshotUserHandle = Process.myUserHandle(); + } if (savedInstanceState != null) { String savedImagePath = savedInstanceState.getString(KEY_SAVED_IMAGE_PATH); @@ -318,36 +332,51 @@ public class LongScreenshotActivity extends Activity { } private void doEdit(Uri uri) { - String editorPackage = getString(R.string.config_screenshotEditor); - Intent intent = new Intent(Intent.ACTION_EDIT); - if (!TextUtils.isEmpty(editorPackage)) { - intent.setComponent(ComponentName.unflattenFromString(editorPackage)); + if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) && mScreenshotUserHandle + != Process.myUserHandle()) { + // TODO: Fix transition for work profile. Omitting it in the meantime. + mActionExecutor.launchIntentAsync( + ActionIntentCreator.INSTANCE.createEditIntent(uri, this), + null, + mScreenshotUserHandle.getIdentifier(), false); + } else { + String editorPackage = getString(R.string.config_screenshotEditor); + Intent intent = new Intent(Intent.ACTION_EDIT); + if (!TextUtils.isEmpty(editorPackage)) { + intent.setComponent(ComponentName.unflattenFromString(editorPackage)); + } + intent.setDataAndType(uri, "image/png"); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + mTransitionView.setImageBitmap(mOutputBitmap); + mTransitionView.setVisibility(View.VISIBLE); + mTransitionView.setTransitionName( + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); + // TODO: listen for transition completing instead of finishing onStop + mTransitionStarted = true; + startActivity(intent, + ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()); } - intent.setDataAndType(uri, "image/png"); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - mTransitionView.setImageBitmap(mOutputBitmap); - mTransitionView.setVisibility(View.VISIBLE); - mTransitionView.setTransitionName( - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME); - // TODO: listen for transition completing instead of finishing onStop - mTransitionStarted = true; - startActivity(intent, - ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView, - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle()); } private void doShare(Uri uri) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("image/png"); - intent.putExtra(Intent.EXTRA_STREAM, uri); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_GRANT_READ_URI_PERMISSION); - Intent sharingChooserIntent = Intent.createChooser(intent, null) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT); + if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) { + Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null); + mActionExecutor.launchIntentAsync(shareIntent, null, + mScreenshotUserHandle.getIdentifier(), false); + } else { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("image/png"); + intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_GRANT_READ_URI_PERMISSION); + Intent sharingChooserIntent = Intent.createChooser(intent, null) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT); + } } private void onClicked(View v) { @@ -389,8 +418,8 @@ public class LongScreenshotActivity extends Activity { mOutputBitmap = renderBitmap(drawable, bounds); ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(), - // TODO: Owner must match the owner of the captured window. - Process.myUserHandle()); + mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) + ? mScreenshotUserHandle : Process.myUserHandle()); exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index d395bd33241d..d94c8277b82c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -591,7 +591,7 @@ public class ScreenshotController { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). withWindowAttached(() -> { - requestScrollCapture(); + requestScrollCapture(owner); mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( new ViewRootImpl.ActivityConfigCallback() { @Override @@ -603,11 +603,11 @@ public class ScreenshotController { mScreenshotView.hideScrollChip(); // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. - mScreenshotHandler.postDelayed( - ScreenshotController.this::requestScrollCapture, 150); + mScreenshotHandler.postDelayed(() -> { + requestScrollCapture(owner); + }, 150); mScreenshotView.updateInsets( - mWindowManager.getCurrentWindowMetrics() - .getWindowInsets()); + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); // Screenshot animation calculations won't be valid anymore, // so just end if (mScreenshotAnimation != null @@ -655,7 +655,7 @@ public class ScreenshotController { mScreenshotHandler.cancelTimeout(); // restarted after animation } - private void requestScrollCapture() { + private void requestScrollCapture(UserHandle owner) { if (!allowLongScreenshots()) { Log.d(TAG, "Long screenshots not supported on this device"); return; @@ -668,10 +668,11 @@ public class ScreenshotController { mScrollCaptureClient.request(DEFAULT_DISPLAY); mLastScrollCaptureRequest = future; mLastScrollCaptureRequest.addListener(() -> - onScrollCaptureResponseReady(future), mMainExecutor); + onScrollCaptureResponseReady(future, owner), mMainExecutor); } - private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) { + private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture, + UserHandle owner) { try { if (mLastScrollCaptureResponse != null) { mLastScrollCaptureResponse.close(); @@ -701,7 +702,7 @@ public class ScreenshotController { mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait); // delay starting scroll capture to make sure the scrim is up before the app moves - mScreenshotView.post(() -> runBatchScrollCapture(response)); + mScreenshotView.post(() -> runBatchScrollCapture(response, owner)); }); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "requestScrollCapture failed", e); @@ -710,7 +711,7 @@ public class ScreenshotController { ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture; - private void runBatchScrollCapture(ScrollCaptureResponse response) { + private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) { // Clear the reference to prevent close() in dismissScreenshot mLastScrollCaptureResponse = null; @@ -744,6 +745,8 @@ public class ScreenshotController { longScreenshot)); final Intent intent = new Intent(mContext, LongScreenshotActivity.class); + intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, + owner); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt index c41e2bc14afc..4cb91e134003 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt @@ -15,12 +15,17 @@ */ package com.android.systemui.screenshot -import android.app.Service import android.content.Intent import android.os.IBinder import android.util.Log +import androidx.lifecycle.LifecycleService +import androidx.lifecycle.lifecycleScope +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.phone.CentralSurfaces +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.util.Optional import javax.inject.Inject @@ -30,7 +35,8 @@ import javax.inject.Inject internal class ScreenshotProxyService @Inject constructor( private val mExpansionMgr: ShadeExpansionStateManager, private val mCentralSurfacesOptional: Optional<CentralSurfaces>, -) : Service() { + @Main private val mMainDispatcher: CoroutineDispatcher, +) : LifecycleService() { private val mBinder: IBinder = object : IScreenshotProxy.Stub() { /** @@ -43,20 +49,28 @@ internal class ScreenshotProxyService @Inject constructor( } override fun dismissKeyguard(callback: IOnDoneCallback) { - if (mCentralSurfacesOptional.isPresent) { - mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard( - Runnable { - callback.onDone(true) - }, null, - true /* dismissShade */, true /* afterKeyguardGone */, - true /* deferred */ - ) - } else { - callback.onDone(false) + lifecycleScope.launch { + executeAfterDismissing(callback) } } } + private suspend fun executeAfterDismissing(callback: IOnDoneCallback) = + withContext(mMainDispatcher) { + mCentralSurfacesOptional.ifPresentOrElse( + { + it.executeRunnableDismissingKeyguard( + Runnable { + callback.onDone(true) + }, null, + true /* dismissShade */, true /* afterKeyguardGone */, + true /* deferred */ + ) + }, + { callback.onDone(false) } + ) + } + override fun onBind(intent: Intent): IBinder? { Log.d(TAG, "onBind: $intent") return mBinder diff --git a/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java new file mode 100644 index 000000000000..fc61e90ab8f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/CameraLauncher.java @@ -0,0 +1,73 @@ +/* + * 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.shade; + +import com.android.systemui.camera.CameraGestureHelper; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.phone.KeyguardBypassController; + +import javax.inject.Inject; + +/** Handles launching camera from Shade. */ +@SysUISingleton +public class CameraLauncher { + private final CameraGestureHelper mCameraGestureHelper; + private final KeyguardBypassController mKeyguardBypassController; + + private boolean mLaunchingAffordance; + + @Inject + public CameraLauncher( + CameraGestureHelper cameraGestureHelper, + KeyguardBypassController keyguardBypassController + ) { + mCameraGestureHelper = cameraGestureHelper; + mKeyguardBypassController = keyguardBypassController; + } + + /** Launches the camera. */ + public void launchCamera(int source, boolean isShadeFullyCollapsed) { + if (!isShadeFullyCollapsed) { + setLaunchingAffordance(true); + } + + mCameraGestureHelper.launchCamera(source); + } + + /** + * Set whether we are currently launching an affordance. This is currently only set when + * launched via a camera gesture. + */ + public void setLaunchingAffordance(boolean launchingAffordance) { + mLaunchingAffordance = launchingAffordance; + mKeyguardBypassController.setLaunchingAffordance(launchingAffordance); + } + + /** + * Return true when a bottom affordance is launching an occluded activity with a splash screen. + */ + public boolean isLaunchingAffordance() { + return mLaunchingAffordance; + } + + /** + * Whether the camera application can be launched for the camera launch gesture. + */ + public boolean canCameraGestureBeLaunched(int barState) { + return mCameraGestureHelper.canCameraGestureBeLaunched(barState); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8c1b5746f61e..ceef8c8ff31c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -124,7 +124,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.camera.CameraGestureHelper; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.DisplayId; @@ -464,7 +463,6 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mCollapsedOnDown; private boolean mClosingWithAlphaFadeOut; private boolean mHeadsUpAnimatingAway; - private boolean mLaunchingAffordance; private final FalsingManager mFalsingManager; private final FalsingCollector mFalsingCollector; @@ -575,7 +573,7 @@ public final class NotificationPanelViewController implements Dumpable { /** Whether the current animator is resetting the pulse expansion after a drag down. */ private boolean mIsPulseExpansionResetAnimator; - private final Rect mKeyguardStatusAreaClipBounds = new Rect(); + private final Rect mLastQsClipBounds = new Rect(); private final Region mQsInterceptRegion = new Region(); /** Alpha of the views which only show on the keyguard but not in shade / shade locked. */ private float mKeyguardOnlyContentAlpha = 1.0f; @@ -615,7 +613,6 @@ public final class NotificationPanelViewController implements Dumpable { private final NotificationListContainer mNotificationListContainer; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; private final NPVCDownEventState.Buffer mLastDownEvents; - private final CameraGestureHelper mCameraGestureHelper; private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; private float mMinExpandHeight; @@ -743,7 +740,6 @@ public final class NotificationPanelViewController implements Dumpable { UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, ShadeTransitionController shadeTransitionController, SystemClock systemClock, - CameraGestureHelper cameraGestureHelper, KeyguardBottomAreaViewModel keyguardBottomAreaViewModel, KeyguardBottomAreaInteractor keyguardBottomAreaInteractor, DumpManager dumpManager) { @@ -924,7 +920,6 @@ public final class NotificationPanelViewController implements Dumpable { unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay); } }); - mCameraGestureHelper = cameraGestureHelper; mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; dumpManager.registerDumpable(this); } @@ -2814,7 +2809,7 @@ public final class NotificationPanelViewController implements Dumpable { */ private void applyQSClippingBounds(int left, int top, int right, int bottom, boolean qsVisible) { - if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) { + if (!mAnimateNextNotificationBounds || mLastQsClipBounds.isEmpty()) { if (mQsClippingAnimation != null) { // update the end position of the animator mQsClippingAnimationEndBounds.set(left, top, right, bottom); @@ -2823,10 +2818,10 @@ public final class NotificationPanelViewController implements Dumpable { } } else { mQsClippingAnimationEndBounds.set(left, top, right, bottom); - final int startLeft = mKeyguardStatusAreaClipBounds.left; - final int startTop = mKeyguardStatusAreaClipBounds.top; - final int startRight = mKeyguardStatusAreaClipBounds.right; - final int startBottom = mKeyguardStatusAreaClipBounds.bottom; + final int startLeft = mLastQsClipBounds.left; + final int startTop = mLastQsClipBounds.top; + final int startRight = mLastQsClipBounds.right; + final int startBottom = mLastQsClipBounds.bottom; if (mQsClippingAnimation != null) { mQsClippingAnimation.cancel(); } @@ -2863,12 +2858,10 @@ public final class NotificationPanelViewController implements Dumpable { private void applyQSClippingImmediately(int left, int top, int right, int bottom, boolean qsVisible) { - // Fancy clipping for quick settings int radius = mScrimCornerRadius; boolean clipStatusView = false; + mLastQsClipBounds.set(left, top, right, bottom); if (mIsFullWidth) { - // The padding on this area is large enough that we can use a cheaper clipping strategy - mKeyguardStatusAreaClipBounds.set(left, top, right, bottom); clipStatusView = qsVisible; float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, @@ -2903,8 +2896,8 @@ public final class NotificationPanelViewController implements Dumpable { radius, qsVisible && !mSplitShadeEnabled); } - mKeyguardStatusViewController.setClipBounds( - clipStatusView ? mKeyguardStatusAreaClipBounds : null); + // The padding on this area is large enough that we can use a cheaper clipping strategy + mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null); if (!qsVisible && mSplitShadeEnabled) { // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to // be visible, otherwise you can see the bounds once swiping up to see bouncer @@ -3948,6 +3941,10 @@ public final class NotificationPanelViewController implements Dumpable { } } + public int getBarState() { + return mBarState; + } + private boolean isOnKeyguard() { return mBarState == KEYGUARD; } @@ -3993,35 +3990,6 @@ public final class NotificationPanelViewController implements Dumpable { && mBarState == StatusBarState.SHADE; } - /** Launches the camera. */ - public void launchCamera(int source) { - if (!isFullyCollapsed()) { - setLaunchingAffordance(true); - } - - mCameraGestureHelper.launchCamera(source); - } - - public void onAffordanceLaunchEnded() { - setLaunchingAffordance(false); - } - - /** Set whether we are currently launching an affordance (i.e. camera gesture). */ - private void setLaunchingAffordance(boolean launchingAffordance) { - mLaunchingAffordance = launchingAffordance; - mKeyguardBypassController.setLaunchingAffordance(launchingAffordance); - } - - /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */ - public boolean isLaunchingAffordanceWithPreview() { - return mLaunchingAffordance; - } - - /** Whether the camera application can be launched by the camera launch gesture. */ - public boolean canCameraGestureBeLaunched() { - return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState); - } - public boolean hideStatusBarIconsWhenExpanded() { if (mIsLaunchAnimationRunning) { return mHideIconsDuringLaunchAnimation; @@ -4360,7 +4328,6 @@ public final class NotificationPanelViewController implements Dumpable { ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown); ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut); ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway); - ipw.print("mLaunchingAffordance="); ipw.println(mLaunchingAffordance); ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded); ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding); ipw.print("mAmbientIndicationBottomPadding="); ipw.println(mAmbientIndicationBottomPadding); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 3670d0987a03..0deb47d73460 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1263,7 +1263,8 @@ public class KeyguardIndicationController { showErrorMessageNowOrLater(errString, followupMessage); } else if (!mAuthController.isUdfpsFingerDown()) { // On subsequent lockouts, we show a more generic locked out message. - showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable), + showErrorMessageNowOrLater( + mContext.getString(R.string.keyguard_face_unlock_unavailable), followupMessage); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java index ec221b7eccc0..c523d22456f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java @@ -15,9 +15,7 @@ */ package com.android.systemui.statusbar.connectivity; -import static com.android.settingslib.mobile.MobileMappings.getDefaultIcons; -import static com.android.settingslib.mobile.MobileMappings.getIconKey; -import static com.android.settingslib.mobile.MobileMappings.mapIconSets; +import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID; import android.content.Context; import android.content.Intent; @@ -46,6 +44,7 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.systemui.R; +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy; import com.android.systemui.util.CarrierConfigTracker; import java.io.PrintWriter; @@ -63,6 +62,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile private final TelephonyManager mPhone; private final CarrierConfigTracker mCarrierConfigTracker; private final SubscriptionDefaults mDefaults; + private final MobileMappingsProxy mMobileMappingsProxy; private final String mNetworkNameDefault; private final String mNetworkNameSeparator; private final ContentObserver mObserver; @@ -121,6 +121,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile TelephonyManager phone, CallbackHandler callbackHandler, NetworkControllerImpl networkController, + MobileMappingsProxy mobileMappingsProxy, SubscriptionInfo info, SubscriptionDefaults defaults, Looper receiverLooper, @@ -135,13 +136,14 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mPhone = phone; mDefaults = defaults; mSubscriptionInfo = info; + mMobileMappingsProxy = mobileMappingsProxy; mNetworkNameSeparator = getTextIfExists( R.string.status_bar_network_name_separator).toString(); mNetworkNameDefault = getTextIfExists( com.android.internal.R.string.lockscreen_carrier_default).toString(); - mNetworkToIconLookup = mapIconSets(mConfig); - mDefaultIcons = getDefaultIcons(mConfig); + mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig); + mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig); String networkName = info.getCarrierName() != null ? info.getCarrierName().toString() : mNetworkNameDefault; @@ -161,8 +163,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile void setConfiguration(Config config) { mConfig = config; updateInflateSignalStrength(); - mNetworkToIconLookup = mapIconSets(mConfig); - mDefaultIcons = getDefaultIcons(mConfig); + mNetworkToIconLookup = mMobileMappingsProxy.mapIconSets(mConfig); + mDefaultIcons = mMobileMappingsProxy.getDefaultIcons(mConfig); updateTelephony(); } @@ -271,8 +273,9 @@ public class MobileSignalController extends SignalController<MobileState, Mobile dataContentDescription = mContext.getString(R.string.data_connection_no_internet); } - final QsInfo qsInfo = getQsInfo(contentDescription, icons.dataType); - final SbInfo sbInfo = getSbInfo(contentDescription, icons.dataType); + int iconId = mCurrentState.getNetworkTypeIcon(mContext); + final QsInfo qsInfo = getQsInfo(contentDescription, iconId); + final SbInfo sbInfo = getSbInfo(contentDescription, iconId); MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( sbInfo.icon, @@ -373,6 +376,10 @@ public class MobileSignalController extends SignalController<MobileState, Mobile } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) { updateDataSim(); notifyListenersIfNecessary(); + } else if (action.equals(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) { + int carrierId = intent.getIntExtra( + TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID); + mCurrentState.setCarrierId(carrierId); } } @@ -477,7 +484,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile mCurrentState.level = getSignalLevel(mCurrentState.signalStrength); } - String iconKey = getIconKey(mCurrentState.telephonyDisplayInfo); + mCurrentState.setCarrierId(mPhone.getSimCarrierId()); + String iconKey = mMobileMappingsProxy.getIconKey(mCurrentState.telephonyDisplayInfo); if (mNetworkToIconLookup.get(iconKey) != null) { mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt index 793817948803..a323454a7ed8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt @@ -22,6 +22,7 @@ import android.telephony.TelephonyManager import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileStatusTracker import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker import javax.inject.Inject @@ -33,6 +34,7 @@ internal class MobileSignalControllerFactory @Inject constructor( val context: Context, val callbackHandler: CallbackHandler, val carrierConfigTracker: CarrierConfigTracker, + val mobileMappings: MobileMappingsProxy, ) { fun createMobileSignalController( config: MobileMappings.Config, @@ -56,6 +58,7 @@ internal class MobileSignalControllerFactory @Inject constructor( phone, callbackHandler, networkController, + mobileMappings, subscriptionInfo, subscriptionDefaults, receiverLooper, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt index f20d20631c95..1fb6a982fdf3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt @@ -16,10 +16,14 @@ package com.android.systemui.statusbar.connectivity +import android.annotation.DrawableRes +import android.content.Context import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyManager +import com.android.internal.annotations.VisibleForTesting +import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.Utils import com.android.settingslib.mobile.MobileStatusTracker.MobileStatus import com.android.settingslib.mobile.TelephonyIcons @@ -41,7 +45,7 @@ internal class MobileState( @JvmField var roaming: Boolean = false, @JvmField var dataState: Int = TelephonyManager.DATA_DISCONNECTED, // Tracks the on/off state of the defaultDataSubscription - @JvmField var defaultDataOff: Boolean = false + @JvmField var defaultDataOff: Boolean = false, ) : ConnectivityState() { @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN, @@ -49,6 +53,11 @@ internal class MobileState( @JvmField var serviceState: ServiceState? = null @JvmField var signalStrength: SignalStrength? = null + var carrierId = TelephonyManager.UNKNOWN_CARRIER_ID + + @VisibleForTesting + var networkTypeResIdCache: NetworkTypeResIdCache = NetworkTypeResIdCache() + /** @return true if this state is disabled or not default data */ val isDataDisabledOrNotDefault: Boolean get() = (iconGroup === TelephonyIcons.DATA_DISABLED || @@ -125,6 +134,21 @@ internal class MobileState( return serviceState != null && serviceState!!.roaming } + /** + * + * Load the (potentially customized) icon resource id for the current network type. Note that + * this operation caches the result. Note that reading the [MobileIconGroup.dataType] field + * directly will not yield correct results in cases where the carrierId has an associated + * override. This is the preferred method for getting the network type indicator. + * + * @return a drawable res id appropriate for the current (carrierId, networkType) pair + */ + @DrawableRes + fun getNetworkTypeIcon(context: Context): Int { + val icon = (iconGroup as MobileIconGroup) + return networkTypeResIdCache.get(icon, carrierId, context) + } + fun setFromMobileStatus(mobileStatus: MobileStatus) { activityIn = mobileStatus.activityIn activityOut = mobileStatus.activityOut @@ -140,6 +164,7 @@ internal class MobileState( super.toString(builder) builder.append(',') builder.append("dataSim=$dataSim,") + builder.append("carrierId=$carrierId") builder.append("networkName=$networkName,") builder.append("networkNameData=$networkNameData,") builder.append("dataConnected=$dataConnected,") @@ -157,6 +182,8 @@ internal class MobileState( builder.append("voiceServiceState=${getVoiceServiceState()},") builder.append("isInService=${isInService()},") + builder.append("networkTypeIconCache=$networkTypeResIdCache") + builder.append("serviceState=${serviceState?.minLog() ?: "(null)"},") builder.append("signalStrength=${signalStrength?.minLog() ?: "(null)"},") builder.append("displayInfo=$telephonyDisplayInfo") @@ -164,6 +191,7 @@ internal class MobileState( override fun tableColumns(): List<String> { val columns = listOf("dataSim", + "carrierId", "networkName", "networkNameData", "dataConnected", @@ -178,6 +206,7 @@ internal class MobileState( "showQuickSettingsRatIcon", "voiceServiceState", "isInService", + "networkTypeIconCache", "serviceState", "signalStrength", "displayInfo") @@ -187,6 +216,7 @@ internal class MobileState( override fun tableData(): List<String> { val columns = listOf(dataSim, + carrierId, networkName, networkNameData, dataConnected, @@ -201,6 +231,7 @@ internal class MobileState( showQuickSettingsRatIcon(), getVoiceServiceState(), isInService(), + networkTypeResIdCache, serviceState?.minLog() ?: "(null)", signalStrength?.minLog() ?: "(null)", telephonyDisplayInfo).map { it.toString() } @@ -217,6 +248,7 @@ internal class MobileState( if (networkName != other.networkName) return false if (networkNameData != other.networkNameData) return false + if (carrierId != other.carrierId) return false if (dataSim != other.dataSim) return false if (dataConnected != other.dataConnected) return false if (isEmergency != other.isEmergency) return false @@ -238,6 +270,7 @@ internal class MobileState( var result = super.hashCode() result = 31 * result + (networkName?.hashCode() ?: 0) result = 31 * result + (networkNameData?.hashCode() ?: 0) + result = 31 * result + (carrierId.hashCode()) result = 31 * result + dataSim.hashCode() result = 31 * result + dataConnected.hashCode() result = 31 * result + isEmergency.hashCode() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 450b757295bc..73d6483e65fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -22,6 +22,7 @@ import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE; import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -138,7 +139,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final MobileSignalControllerFactory mMobileFactory; private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener; - private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private int mActiveMobileDataSubscription = INVALID_SUBSCRIPTION_ID; // Subcontrollers. @VisibleForTesting @@ -502,6 +503,7 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED); filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED); + filter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; @@ -792,6 +794,20 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = Config.readConfig(mContext); mReceiverHandler.post(this::handleConfigurationChanged); break; + + case TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED: { + // Notify the relevant MobileSignalController of the change + int subId = intent.getIntExtra( + TelephonyManager.EXTRA_SUBSCRIPTION_ID, + INVALID_SUBSCRIPTION_ID + ); + if (SubscriptionManager.isValidSubscriptionId(subId)) { + if (mMobileSignalControllers.indexOfKey(subId) >= 0) { + mMobileSignalControllers.get(subId).handleBroadcast(intent); + } + } + } + break; case Intent.ACTION_SIM_STATE_CHANGED: // Avoid rebroadcast because SysUI is direct boot aware. if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) { @@ -819,7 +835,7 @@ public class NetworkControllerImpl extends BroadcastReceiver break; default: int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, - SubscriptionManager.INVALID_SUBSCRIPTION_ID); + INVALID_SUBSCRIPTION_ID); if (SubscriptionManager.isValidSubscriptionId(subId)) { if (mMobileSignalControllers.indexOfKey(subId) >= 0) { mMobileSignalControllers.get(subId).handleBroadcast(intent); @@ -1335,6 +1351,9 @@ public class NetworkControllerImpl extends BroadcastReceiver String slotString = args.getString("slot"); int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString); slot = MathUtils.constrain(slot, 0, 8); + String carrierIdString = args.getString("carrierid"); + int carrierId = TextUtils.isEmpty(carrierIdString) ? 0 + : Integer.parseInt(carrierIdString); // Ensure we have enough sim slots List<SubscriptionInfo> subs = new ArrayList<>(); while (mMobileSignalControllers.size() <= slot) { @@ -1346,6 +1365,9 @@ public class NetworkControllerImpl extends BroadcastReceiver } // Hack to index linearly for easy use. MobileSignalController controller = mMobileSignalControllers.valueAt(slot); + if (carrierId != 0) { + controller.getState().setCarrierId(carrierId); + } controller.getState().dataSim = datatype != null; controller.getState().isDefault = datatype != null; controller.getState().dataConnected = datatype != null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt new file mode 100644 index 000000000000..9be7ee99cf02 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCache.kt @@ -0,0 +1,78 @@ +/* + * 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.statusbar.connectivity + +import android.annotation.DrawableRes +import android.content.Context +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides +import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl + +/** + * Cache for network type resource IDs. + * + * The default framework behavior is to have a statically defined icon per network type. See + * [MobileIconGroup] for the standard mapping. + * + * For the case of carrierId-defined overrides, we want to check [MobileIconCarrierIdOverrides] for + * an existing icon override, and cache the result of the operation + */ +class NetworkTypeResIdCache( + private val overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() +) { + @DrawableRes private var cachedResId: Int = 0 + private var lastCarrierId: Int? = null + private var lastIconGroup: MobileIconGroup? = null + private var isOverridden: Boolean = false + + @DrawableRes + fun get(iconGroup: MobileIconGroup, carrierId: Int, context: Context): Int { + if (lastCarrierId != carrierId || lastIconGroup != iconGroup) { + lastCarrierId = carrierId + lastIconGroup = iconGroup + + val maybeOverride = calculateOverriddenIcon(iconGroup, carrierId, context) + if (maybeOverride > 0) { + cachedResId = maybeOverride + isOverridden = true + } else { + cachedResId = iconGroup.dataType + isOverridden = false + } + } + + return cachedResId + } + + override fun toString(): String { + return "networkTypeResIdCache={id=$cachedResId, isOverridden=$isOverridden}" + } + + @DrawableRes + private fun calculateOverriddenIcon( + iconGroup: MobileIconGroup, + carrierId: Int, + context: Context, + ): Int { + val name = iconGroup.name + if (!overrides.carrierIdEntryExists(carrierId)) { + return 0 + } + + return overrides.getOverrideFor(carrierId, name, context.resources) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt index a02dd3490341..42b874fd7156 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ui/MobileContextProvider.kt @@ -37,6 +37,13 @@ import javax.inject.Inject * own [Configuration] and track resources based on the full set of available mcc-mnc combinations. * * (for future reference: b/240555502 is the initiating bug for this) + * + * NOTE: MCC/MNC qualifiers are not sufficient to fully describe a network type icon qualified by + * network type + carrier ID. This class exists to keep the legacy behavior of using the MCC/MNC + * resource qualifiers working, but if a carrier-specific icon is requested, then the override + * provided by [MobileIconCarrierIdOverrides] will take precedence. + * + * TODO(b/258503704): consider removing this class in favor of the `carrierId` overrides */ @SysUISingleton class MobileContextProvider diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 41f0520a9d14..f3482f490d92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -55,6 +55,7 @@ import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; @@ -71,6 +72,8 @@ import java.util.Optional; import javax.inject.Inject; +import dagger.Lazy; + /** */ @CentralSurfacesComponent.CentralSurfacesScope public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks { @@ -99,6 +102,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final boolean mVibrateOnOpening; private final VibrationEffect mCameraLaunchGestureVibrationEffect; private final SystemBarAttributesListener mSystemBarAttributesListener; + private final Lazy<CameraLauncher> mCameraLauncherLazy; private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @@ -128,8 +132,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba Optional<Vibrator> vibratorOptional, DisableFlagsLogger disableFlagsLogger, @DisplayId int displayId, - SystemBarAttributesListener systemBarAttributesListener) { - + SystemBarAttributesListener systemBarAttributesListener, + Lazy<CameraLauncher> cameraLauncherLazy) { mCentralSurfaces = centralSurfaces; mContext = context; mShadeController = shadeController; @@ -152,6 +156,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mVibratorOptional = vibratorOptional; mDisableFlagsLogger = disableFlagsLogger; mDisplayId = displayId; + mCameraLauncherLazy = cameraLauncherLazy; mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation); mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect( @@ -346,7 +351,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mCentralSurfaces.setLaunchCameraOnFinishedGoingToSleep(true); return; } - if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) { + if (!mCameraLauncherLazy.get().canCameraGestureBeLaunched( + mNotificationPanelViewController.getBarState())) { if (CentralSurfaces.DEBUG_CAMERA_LIFT) { Slog.d(CentralSurfaces.TAG, "Can't launch camera right now"); } @@ -383,7 +389,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.reset(true /* hide */); } - mNotificationPanelViewController.launchCamera(source); + mCameraLauncherLazy.get().launchCamera(source, + mNotificationPanelViewController.isFullyCollapsed()); mCentralSurfaces.updateScrimController(); } else { // We need to defer the camera launch until the screen comes on, since otherwise diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 71609f887ebf..334f1aff0d7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -175,6 +175,7 @@ import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.ripple.RippleShader.RippleShape; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -468,6 +469,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final PluginManager mPluginManager; private final ShadeController mShadeController; private final InitController mInitController; + private final Lazy<CameraLauncher> mCameraLauncherLazy; private final PluginDependencyProvider mPluginDependencyProvider; private final KeyguardDismissUtil mKeyguardDismissUtil; @@ -600,6 +602,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private Runnable mLaunchTransitionEndRunnable; private Runnable mLaunchTransitionCancelRunnable; + private boolean mLaunchingAffordance; private boolean mLaunchCameraWhenFinishedWaking; private boolean mLaunchCameraOnFinishedGoingToSleep; private boolean mLaunchEmergencyActionWhenFinishedWaking; @@ -744,7 +747,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { InteractionJankMonitor jankMonitor, DeviceStateManager deviceStateManager, WiredChargingRippleController wiredChargingRippleController, - IDreamManager dreamManager) { + IDreamManager dreamManager, + Lazy<CameraLauncher> cameraLauncherLazy) { mContext = context; mNotificationsController = notificationsController; mFragmentService = fragmentService; @@ -821,6 +825,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mMessageRouter = messageRouter; mWallpaperManager = wallpaperManager; mJankMonitor = jankMonitor; + mCameraLauncherLazy = cameraLauncherLazy; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -2949,7 +2954,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void onLaunchTransitionFadingEnded() { mNotificationPanelViewController.resetAlpha(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); releaseGestureWakeLock(); runLaunchTransitionEndRunnable(); mKeyguardStateController.setLaunchTransitionFadingAway(false); @@ -3019,7 +3024,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void onLaunchTransitionTimeout() { Log.w(TAG, "Launch transition: Timeout!"); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); releaseGestureWakeLock(); mNotificationPanelViewController.resetViews(false /* animate */); } @@ -3072,7 +3077,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); releaseGestureWakeLock(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); mNotificationPanelViewController.resetAlpha(); mNotificationPanelViewController.resetTranslation(); mNotificationPanelViewController.resetViewGroupFade(); @@ -3230,7 +3235,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void endAffordanceLaunch() { releaseGestureWakeLock(); - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); } /** @@ -3503,7 +3508,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() { @Override public void onFinishedGoingToSleep() { - mNotificationPanelViewController.onAffordanceLaunchEnded(); + mCameraLauncherLazy.get().setLaunchingAffordance(false); releaseGestureWakeLock(); mLaunchCameraWhenFinishedWaking = false; mDeviceInteractive = false; @@ -3604,7 +3609,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { .updateSensitivenessForOccludedWakeup(); } if (mLaunchCameraWhenFinishedWaking) { - mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource); + mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource, + mNotificationPanelViewController.isFullyCollapsed()); mLaunchCameraWhenFinishedWaking = false; } if (mLaunchEmergencyActionWhenFinishedWaking) { @@ -3795,8 +3801,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); - boolean launchingAffordanceWithPreview = - mNotificationPanelViewController.isLaunchingAffordanceWithPreview(); + boolean launchingAffordanceWithPreview = mLaunchingAffordance; mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index d32847227c1a..aa0757e1d572 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -279,10 +279,7 @@ public class KeyguardBouncer { * @see #onFullyShown() */ private void onFullyHidden() { - cancelShowRunnable(); - setVisibility(View.INVISIBLE); - mFalsingCollector.onBouncerHidden(); - DejankUtils.postAfterTraversal(mResetRunnable); + } private void setVisibility(@View.Visibility int visibility) { @@ -459,7 +456,13 @@ public class KeyguardBouncer { onFullyShown(); dispatchFullyShown(); } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) { - onFullyHidden(); + DejankUtils.postAfterTraversal(mResetRunnable); + /* + * There are cases where #hide() was not invoked, such as when + * NotificationPanelViewController controls the hide animation. Make sure the state gets + * updated by calling #hide() directly. + */ + hide(false /* destroyView */); dispatchFullyHidden(); } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) { dispatchStartingToHide(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 86e27aba65f0..d54a8638f2e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -895,7 +895,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump float stateBehind = mClipsQsScrim ? state.getNotifAlpha() : state.getBehindAlpha(); float behindAlpha; - int behindTint; + int behindTint = state.getBehindTint(); if (mDarkenWhileDragging) { behindAlpha = MathUtils.lerp(mDefaultScrimAlpha, stateBehind, interpolatedFract); @@ -903,12 +903,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump behindAlpha = MathUtils.lerp(0 /* start */, stateBehind, interpolatedFract); } - if (mClipsQsScrim) { - behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), + if (mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()) { + if (mClipsQsScrim) { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getNotifTint(), state.getNotifTint(), interpolatedFract); - } else { - behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), + } else { + behindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(), state.getBehindTint(), interpolatedFract); + } } if (mQsExpansion > 0) { behindAlpha = MathUtils.lerp(behindAlpha, mDefaultScrimAlpha, mQsExpansion); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index e9d4e55c82f2..681d21bf078d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -165,6 +165,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onFullyHidden() { mPrimaryBouncerAnimating = false; + updateStates(); } @Override @@ -1183,12 +1184,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb updateNavigationBarVisibility(navBarVisible); } - if (primaryBouncerShowing != mLastPrimaryBouncerShowing || mFirstUpdate) { + boolean isPrimaryBouncerShowingChanged = + primaryBouncerShowing != mLastPrimaryBouncerShowing; + mLastPrimaryBouncerShowing = primaryBouncerShowing; + + if (isPrimaryBouncerShowingChanged || mFirstUpdate) { mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing); mCentralSurfaces.setBouncerShowing(primaryBouncerShowing); } if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate - || primaryBouncerShowing != mLastPrimaryBouncerShowing) { + || isPrimaryBouncerShowingChanged) { mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerShowing); } @@ -1197,7 +1202,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastShowing = showing; mLastGlobalActionsVisible = mGlobalActionsVisible; mLastOccluded = occluded; - mLastPrimaryBouncerShowing = primaryBouncerShowing; mLastPrimaryBouncerIsOrWillBeShowing = primaryBouncerIsOrWillBeShowing; mLastBouncerDismissible = primaryBouncerDismissible; mLastRemoteInputActive = remoteInputActive; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt index 60bd0383f8c7..501467f13007 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt @@ -32,6 +32,7 @@ import javax.inject.Inject interface MobileMappingsProxy { fun mapIconSets(config: Config): Map<String, MobileIconGroup> fun getDefaultIcons(config: Config): MobileIconGroup + fun getIconKey(displayInfo: TelephonyDisplayInfo): String fun toIconKey(@NetworkType networkType: Int): String fun toIconKeyOverride(@NetworkType networkType: Int): String } @@ -44,6 +45,9 @@ class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy { override fun getDefaultIcons(config: Config): MobileIconGroup = MobileMappings.getDefaultIcons(config) + override fun getIconKey(displayInfo: TelephonyDisplayInfo): String = + MobileMappings.getIconKey(displayInfo) + override fun toIconKey(@NetworkType networkType: Int): String = MobileMappings.toIconKey(networkType) diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 4cb41f3a977e..82703364a1d5 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -65,8 +65,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora height = WindowManager.LayoutParams.WRAP_CONTENT type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL format = PixelFormat.TRANSLUCENT setTrustedOverlay() } @@ -120,20 +119,27 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora // At this point, we're guaranteed to no longer be displaying a view. // So, set up all our callbacks and inflate the view. configurationController.addCallback(displayScaleListener) - // Wake the screen if necessary so the user will see the view. (Per b/239426653, we want - // the view to show over the dream state, so we should only wake up if the screen is - // completely off.) - if (!powerManager.isScreenOn) { - wakeLock = wakeLockBuilder + + wakeLock = if (!powerManager.isScreenOn) { + // If the screen is off, fully wake it so the user can see the view. + wakeLockBuilder .setTag(newInfo.windowTitle) .setLevelsAndFlags( - PowerManager.FULL_WAKE_LOCK or - PowerManager.ACQUIRE_CAUSES_WAKEUP + PowerManager.FULL_WAKE_LOCK or + PowerManager.ACQUIRE_CAUSES_WAKEUP ) .build() - wakeLock?.acquire(newInfo.wakeReason) - wakeReasonAcquired = newInfo.wakeReason + } else { + // Per b/239426653, we want the view to show over the dream state. + // If the screen is on, using screen bright level will leave screen on the dream + // state but ensure the screen will not go off before wake lock is released. + wakeLockBuilder + .setTag(newInfo.windowTitle) + .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK) + .build() } + wakeLock?.acquire(newInfo.wakeReason) + wakeReasonAcquired = newInfo.wakeReason logger.logViewAddition(newInfo.windowTitle) inflateAndUpdateView(newInfo) } diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index bf706735d531..3507cb7c40a4 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -381,7 +381,7 @@ public class StorageNotification implements CoreStartable { // Don't annoy when user dismissed in past. (But make sure the disk is adoptable; we // used to allow snoozing non-adoptable disks too.) - if (rec.isSnoozed() && disk.isAdoptable()) { + if (rec == null || (rec.isSnoozed() && disk.isAdoptable())) { return null; } if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 903aba1a74a4..2c64fe1e259b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -1230,6 +1230,9 @@ public class VolumeDialogImpl implements VolumeDialog, effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); break; case RINGER_MODE_VIBRATE: + // Feedback handled by onStateChange, for feedback both when user toggles + // directly in volume dialog, or drags slider to a value of 0 in settings. + break; default: effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); } @@ -1630,9 +1633,8 @@ public class VolumeDialogImpl implements VolumeDialog, && mState.ringerModeInternal != -1 && mState.ringerModeInternal != state.ringerModeInternal && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { - mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK)); + mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)); } - mState = state; mDynamic.clear(); // add any new dynamic rows diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index 5d2b0ca4e7ea..829008403e02 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -16,8 +16,11 @@ package com.android.keyguard; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -90,4 +93,11 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { mMessageAreaController.setIsVisible(true); verify(mKeyguardMessageArea).setIsVisible(true); } + + @Test + public void testGetMessage() { + String msg = "abc"; + when(mKeyguardMessageArea.getText()).thenReturn(msg); + assertThat(mMessageAreaController.getMessage()).isEqualTo(msg); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index b369098cafc0..ffd95f4041f9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -31,6 +31,7 @@ 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.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -118,4 +119,14 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { keyguardPasswordViewController.startAppearAnimation() verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password) } + + @Test + fun startAppearAnimation_withExistingMessage() { + `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") + keyguardPasswordViewController.startAppearAnimation() + verify( + mKeyguardMessageAreaController, + never() + ).setMessage(R.string.keyguard_enter_your_password) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 9eff70487c74..b3d1c8f909d8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -33,6 +33,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` +import org.mockito.Mockito.never import org.mockito.MockitoAnnotations @SmallTest @@ -112,4 +113,14 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardPatternViewController.startAppearAnimation() verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern) } + + @Test + fun startAppearAnimation_withExistingMessage() { + `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.") + mKeyguardPatternViewController.startAppearAnimation() + verify( + mKeyguardMessageAreaController, + never() + ).setMessage(R.string.keyguard_enter_your_password) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index d9efdeaea04c..8bcfe6f2b6f5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -100,4 +100,12 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { pinViewController.startAppearAnimation() verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin) } + + @Test + fun startAppearAnimation_withExistingMessage() { + Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.") + pinViewController.startAppearAnimation() + verify(keyguardMessageAreaController, Mockito.never()) + .setMessage(R.string.keyguard_enter_your_password) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 1bd14e558fa0..1d2b09ce0ce2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -262,9 +262,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { ConstraintSet.Constraint userSwitcherConstraint = getViewConstraint(R.id.keyguard_bouncer_user_switcher); - assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(viewFlipperConstraint.layout.topToBottom).isEqualTo( + R.id.keyguard_bouncer_user_switcher); assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID); assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID); + assertThat(userSwitcherConstraint.layout.bottomToTop).isEqualTo( + mSecurityViewFlipper.getId()); assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo( getContext().getResources().getDimensionPixelSize( R.dimen.bouncer_user_switcher_y_trans)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt index a4e0825360df..588620646b73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt @@ -16,6 +16,7 @@ package com.android.systemui +import android.content.Context import android.graphics.Canvas import android.graphics.Insets import android.graphics.Path @@ -48,6 +49,7 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { @Mock private lateinit var mockCanvas: Canvas @Mock private lateinit var mockRootView: View @Mock private lateinit var mockDisplay: Display + @Mock private lateinit var mockContext: Context private lateinit var cutoutBaseView: DisplayCutoutBaseView private val cutout: DisplayCutout = DisplayCutout.Builder() @@ -168,7 +170,9 @@ class DisplayCutoutBaseViewTest : SysuiTestCase() { R.bool.config_fillMainBuiltInDisplayCutout, fillCutout) cutoutBaseView = spy(DisplayCutoutBaseView(mContext)) - whenever(cutoutBaseView.display).thenReturn(mockDisplay) + + whenever(cutoutBaseView.context).thenReturn(mockContext) + whenever(mockContext.display).thenReturn(mockDisplay) whenever(mockDisplay.uniqueId).thenReturn("mockDisplayUniqueId") whenever(cutoutBaseView.rootView).thenReturn(mockRootView) whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt index 054650bb8a75..8207fa6958f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui +import android.content.Context import android.graphics.Insets import android.graphics.PixelFormat import android.graphics.Rect @@ -44,6 +45,7 @@ class ScreenDecorHwcLayerTest : SysuiTestCase() { @Mock private lateinit var mockDisplay: Display @Mock private lateinit var mockRootView: View + @Mock private lateinit var mockContext: Context private val displayWidth = 100 private val displayHeight = 200 @@ -75,7 +77,8 @@ class ScreenDecorHwcLayerTest : SysuiTestCase() { decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport)) whenever(decorHwcLayer.width).thenReturn(displayWidth) whenever(decorHwcLayer.height).thenReturn(displayHeight) - whenever(decorHwcLayer.display).thenReturn(mockDisplay) + whenever(decorHwcLayer.context).thenReturn(mockContext) + whenever(mockContext.display).thenReturn(mockDisplay) whenever(decorHwcLayer.rootView).thenReturn(mockRootView) whenever(mockRootView.left).thenReturn(0) whenever(mockRootView.top).thenReturn(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 1b5f9b6d45cd..acdafe3e1c7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -114,10 +114,8 @@ public class UdfpsControllerTest extends SysuiTestCase { @Rule public MockitoRule rule = MockitoJUnit.rule(); - // Unit under test private UdfpsController mUdfpsController; - // Dependencies private FakeExecutor mBiometricsExecutor; @Mock @@ -171,7 +169,6 @@ public class UdfpsControllerTest extends SysuiTestCase { private UdfpsDisplayMode mUdfpsDisplayMode; @Mock private FeatureFlags mFeatureFlags; - // Stuff for configuring mocks @Mock private UdfpsView mUdfpsView; @@ -249,54 +246,42 @@ public class UdfpsControllerTest extends SysuiTestCase { FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC, true /* resetLockoutRequiresHardwareAuthToken */); - List<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); - props.add(mOpticalProps); - props.add(mUltrasonicProps); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props); - mFgExecutor = new FakeExecutor(new FakeSystemClock()); // Create a fake background executor. mBiometricsExecutor = new FakeExecutor(new FakeSystemClock()); - mUdfpsController = new UdfpsController( - mContext, - execution, - mLayoutInflater, - mFingerprintManager, - mWindowManager, - mStatusBarStateController, - mFgExecutor, - new ShadeExpansionStateManager(), - mStatusBarKeyguardViewManager, - mDumpManager, - mKeyguardUpdateMonitor, - mFeatureFlags, - mFalsingManager, - mPowerManager, - mAccessibilityManager, - mLockscreenShadeTransitionController, - mScreenLifecycle, - mVibrator, - mUdfpsHapticsSimulator, - mUdfpsShell, - mKeyguardStateController, - mDisplayManager, - mHandler, - mConfigurationController, - mSystemClock, - mUnlockedScreenOffAnimationController, - mSystemUIDialogManager, - mLatencyTracker, - mActivityLaunchAnimator, - Optional.of(mAlternateTouchProvider), - mBiometricsExecutor, + initUdfpsController(true /* hasAlternateTouchProvider */); + } + + private void initUdfpsController(boolean hasAlternateTouchProvider) { + initUdfpsController(mOpticalProps, hasAlternateTouchProvider); + } + + private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps, + boolean hasAlternateTouchProvider) { + reset(mFingerprintManager); + reset(mScreenLifecycle); + + final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider = + hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty(); + + mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater, + mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor, + new ShadeExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager, + mKeyguardUpdateMonitor, mFeatureFlags, mFalsingManager, mPowerManager, + mAccessibilityManager, mLockscreenShadeTransitionController, mScreenLifecycle, + mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, mKeyguardStateController, + mDisplayManager, mHandler, mConfigurationController, mSystemClock, + mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, + mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor, mPrimaryBouncerInteractor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); mScreenObserver = mScreenObserverCaptor.getValue(); - mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams()); + + mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams()); mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode); } @@ -333,8 +318,7 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() - throws RemoteException { + public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */); } @@ -521,8 +505,37 @@ public class UdfpsControllerTest extends SysuiTestCase { new MotionEvent.PointerCoords[]{pc}, 0, 0, 1f, 1f, 0, 0, 0, 0); } + private static class TestParams { + public final FingerprintSensorPropertiesInternal sensorProps; + public final boolean hasAlternateTouchProvider; + + TestParams(FingerprintSensorPropertiesInternal sensorProps, + boolean hasAlternateTouchProvider) { + this.sensorProps = sensorProps; + this.hasAlternateTouchProvider = hasAlternateTouchProvider; + } + } + + private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) { + for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, + mUltrasonicProps)) { + for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) { + initUdfpsController(sensorProps, hasAlternateTouchProvider); + testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider)); + } + } + } + @Test - public void onTouch_propagatesTouchInNativeOrientationAndResolution() throws RemoteException { + public void onTouch_propagatesTouchInNativeOrientationAndResolution() { + runWithAllParams( + this::onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized); + } + + private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized( + TestParams testParams) throws RemoteException { + reset(mUdfpsView); + final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner. final int displayWidth = 1080; final int displayHeight = 1920; @@ -541,13 +554,13 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // Show the overlay. - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); // Test ROTATION_0 - mUdfpsController.updateOverlayParams(mOpticalProps, + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_0)); MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, @@ -559,12 +572,19 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); + } // Test ROTATION_90 reset(mAlternateTouchProvider); - mUdfpsController.updateOverlayParams(mOpticalProps, + reset(mFingerprintManager); + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_90)); event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor); @@ -575,12 +595,19 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); + } // Test ROTATION_270 reset(mAlternateTouchProvider); - mUdfpsController.updateOverlayParams(mOpticalProps, + reset(mFingerprintManager); + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_270)); event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor); @@ -591,12 +618,19 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); + } // Test ROTATION_180 reset(mAlternateTouchProvider); - mUdfpsController.updateOverlayParams(mOpticalProps, + reset(mFingerprintManager); + mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, scaleFactor, Surface.ROTATION_180)); // ROTATION_180 is not supported. It should be treated like ROTATION_0. @@ -608,26 +642,22 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricsExecutor.runAllReady(); event.recycle(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } - - private void runForAllUdfpsTypes( - ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) { - for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, - mUltrasonicProps)) { - mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams()); - sensorPropsConsumer.accept(sensorProps); + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), + eq(expectedY), eq(expectedMinor), eq(expectedMajor)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor)); } } @Test public void fingerDown() { - runForAllUdfpsTypes(this::fingerDownForSensor); + runWithAllParams(this::fingerDownParameterized); } - private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void fingerDownParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker, mKeyguardUpdateMonitor); @@ -637,7 +667,7 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -655,14 +685,22 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.runAllReady(); - // THEN FingerprintManager is notified about onPointerDown - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f), - eq(0f)); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); + // THEN the touch provider is notified about onPointerDown. + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f), + eq(0f)); + verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), + anyInt(), anyFloat(), anyFloat()); + verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f)); + verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(), + anyFloat(), anyFloat()); + } // AND display configuration begins - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); } else { @@ -671,16 +709,27 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView, never()).configureDisplay(any()); } verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // AND onDisplayConfigured notifies FingerprintManager about onUiReady mOnDisplayConfiguredCaptor.getValue().run(); mBiometricsExecutor.runAllReady(); - InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker); - inOrder.verify(mAlternateTouchProvider).onUiReady(); - inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); + if (testParams.hasAlternateTouchProvider) { + InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker); + inOrder.verify(mAlternateTouchProvider).onUiReady(); + inOrder.verify(mLatencyTracker).onActionEnd( + eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); + verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt()); + } else { + InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); + inOrder.verify(mFingerprintManager).onUiReady(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId)); + inOrder.verify(mLatencyTracker).onActionEnd( + eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); + verify(mAlternateTouchProvider, never()).onUiReady(); + } } else { + verify(mFingerprintManager, never()).onUiReady(anyLong(), anyInt()); verify(mAlternateTouchProvider, never()).onUiReady(); verify(mLatencyTracker, never()).onActionEnd( eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); @@ -689,24 +738,23 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt() { - runForAllUdfpsTypes(this::aodInterruptForSensor); + runWithAllParams(this::aodInterruptParameterized); } - private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void aodInterruptParameterized(TestParams testParams) throws RemoteException { mUdfpsController.cancelAodInterrupt(); reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); // GIVEN that the overlay is showing and screen is on and fp is running - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN display configuration begins // AND onDisplayConfigured notifies FingerprintManager about onUiReady verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); @@ -715,29 +763,37 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture()); } mBiometricsExecutor.runAllReady(); - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), - eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); + + if (testParams.hasAlternateTouchProvider) { + verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), + eq(3f) /* minor */, eq(2f) /* major */); + verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), + anyInt(), anyFloat(), anyFloat()); + verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); + } else { + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */, + eq(2f) /* major */); + verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat()); + } } @Test public void cancelAodInterrupt() { - runForAllUdfpsTypes(this::cancelAodInterruptForSensor); + runWithAllParams(this::cancelAodInterruptParameterized); } - private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void cancelAodInterruptParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); // WHEN it is cancelled mUdfpsController.cancelAodInterrupt(); @@ -754,21 +810,20 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptTimeout() { - runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor); + runWithAllParams(this::aodInterruptTimeoutParameterized); } - private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void aodInterruptTimeoutParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { when(mUdfpsView.isDisplayConfigured()).thenReturn(false); @@ -776,7 +831,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // WHEN it times out mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN the display is unconfigured. verify(mUdfpsView).unconfigureDisplay(); } else { @@ -787,23 +842,23 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptCancelTimeoutActionOnFingerUp() { - runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor); + runWithAllParams(this::aodInterruptCancelTimeoutActionOnFingerUpParameterized); } - private void aodInterruptCancelTimeoutActionOnFingerUpForSensor( - FingerprintSensorPropertiesInternal sensorProps) throws RemoteException { + private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams) + throws RemoteException { reset(mUdfpsView); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the ACTION_UP event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -833,7 +888,7 @@ public class UdfpsControllerTest extends SysuiTestCase { moveEvent.recycle(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the finger up event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -844,7 +899,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN the display should be unconfigured once. If the timeout action is not // cancelled, the display would be unconfigured twice which would cause two // FP attempts. @@ -856,23 +911,23 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptCancelTimeoutActionOnAcquired() { - runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor); + runWithAllParams(this::aodInterruptCancelTimeoutActionOnAcquiredParameterized); } - private void aodInterruptCancelTimeoutActionOnAcquiredForSensor( - FingerprintSensorPropertiesInternal sensorProps) throws RemoteException { + private void aodInterruptCancelTimeoutActionOnAcquiredParameterized(TestParams testParams) + throws RemoteException { reset(mUdfpsView); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the acquired event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -880,7 +935,7 @@ public class UdfpsControllerTest extends SysuiTestCase { } // WHEN acquired is received - mOverlayController.onAcquired(sensorProps.sensorId, + mOverlayController.onAcquired(testParams.sensorProps.sensorId, BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD); // Configure UdfpsView to accept the ACTION_DOWN event @@ -900,7 +955,7 @@ public class UdfpsControllerTest extends SysuiTestCase { moveEvent.recycle(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the finger up event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); } else { @@ -911,7 +966,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.advanceClockToNext(); mFgExecutor.runAllReady(); - if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // THEN the display should be unconfigured once. If the timeout action is not // cancelled, the display would be unconfigured twice which would cause two // FP attempts. @@ -923,15 +978,14 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterruptScreenOff() { - runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor); + runWithAllParams(this::aodInterruptScreenOffParameterized); } - private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps) - throws RemoteException { + private void aodInterruptScreenOffParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); // GIVEN screen off - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOff(); mFgExecutor.runAllReady(); @@ -945,17 +999,16 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt_fingerprintNotRunning() { - runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor); + runWithAllParams(this::aodInterrupt_fingerprintNotRunningParameterized); } - private void aodInterrupt_fingerprintNotRunningForSensor( - FingerprintSensorPropertiesInternal sensorProps) throws RemoteException { + private void aodInterrupt_fingerprintNotRunningParameterized(TestParams testParams) + throws RemoteException { reset(mUdfpsView); // GIVEN showing overlay - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, - mUdfpsOverlayControllerCallback); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt index db41d8d37a43..dedc7239bf85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt @@ -16,27 +16,42 @@ package com.android.systemui.controls.management +import android.Manifest import android.content.ComponentName import android.content.Context import android.content.ContextWrapper +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo +import android.os.Bundle import android.os.UserHandle +import android.service.controls.ControlsProviderService import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.settingslib.applications.ServiceListing import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsServiceInfo +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags.USE_APP_PANELS import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatcher import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock @@ -51,10 +66,8 @@ import java.util.concurrent.Executor class ControlsListingControllerImplTest : SysuiTestCase() { companion object { - private const val TEST_LABEL = "TEST_LABEL" - private const val TEST_PERMISSION = "permission" - fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() - fun <T> any(): T = Mockito.any<T>() + private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or + PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong() } @Mock @@ -63,15 +76,17 @@ class ControlsListingControllerImplTest : SysuiTestCase() { private lateinit var mockCallback: ControlsListingController.ControlsListingCallback @Mock private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback - @Mock - private lateinit var serviceInfo: ServiceInfo - @Mock - private lateinit var serviceInfo2: ServiceInfo @Mock(stubOnly = true) private lateinit var userTracker: UserTracker + @Mock(stubOnly = true) + private lateinit var dumpManager: DumpManager + @Mock + private lateinit var packageManager: PackageManager + @Mock + private lateinit var featureFlags: FeatureFlags - private var componentName = ComponentName("pkg1", "class1") - private var componentName2 = ComponentName("pkg2", "class2") + private var componentName = ComponentName("pkg", "class1") + private var activityName = ComponentName("pkg", "activity") private val executor = FakeExecutor(FakeSystemClock()) @@ -87,9 +102,15 @@ class ControlsListingControllerImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(serviceInfo.componentName).thenReturn(componentName) - `when`(serviceInfo2.componentName).thenReturn(componentName2) `when`(userTracker.userId).thenReturn(user) + `when`(userTracker.userContext).thenReturn(context) + // Return disabled by default + `when`(packageManager.getComponentEnabledSetting(any())) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED) + mContext.setMockPackageManager(packageManager) + + // Return true by default, we'll test the false path + `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true) val wrapper = object : ContextWrapper(mContext) { override fun createContextAsUser(user: UserHandle, flags: Int): Context { @@ -97,7 +118,14 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } } - controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker) + controller = ControlsListingControllerImpl( + wrapper, + executor, + { mockSL }, + userTracker, + dumpManager, + featureFlags + ) verify(mockSL).addCallback(capture(serviceListingCallbackCaptor)) } @@ -123,9 +151,16 @@ class ControlsListingControllerImplTest : SysuiTestCase() { Unit } `when`(mockServiceListing.reload()).then { - callback?.onServicesReloaded(listOf(serviceInfo)) + callback?.onServicesReloaded(listOf(ServiceInfo(componentName))) } - ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker) + ControlsListingControllerImpl( + mContext, + exec, + { mockServiceListing }, + userTracker, + dumpManager, + featureFlags + ) } @Test @@ -148,7 +183,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() { @Test fun testCallbackGetsList() { - val list = listOf(serviceInfo) + val list = listOf(ServiceInfo(componentName)) controller.addCallback(mockCallback) controller.addCallback(mockCallbackOther) @@ -188,6 +223,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() { @Test fun testChangeUserSendsCorrectServiceUpdate() { + val serviceInfo = ServiceInfo(componentName) + val list = listOf(serviceInfo) controller.addCallback(mockCallback) @@ -223,4 +260,284 @@ class ControlsListingControllerImplTest : SysuiTestCase() { verify(mockCallback).onServicesUpdated(capture(captor)) assertEquals(0, captor.value.size) } + + @Test + fun test_nullPanelActivity() { + val list = listOf(ServiceInfo(componentName)) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testNoActivity_nullPanel() { + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityWithoutPermission_nullPanel() { + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + setUpQueryResult(listOf(ActivityInfo(activityName))) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityPermissionNotExported_nullPanel() { + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + setUpQueryResult(listOf( + ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityDisabled_nullPanel() { + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityEnabled_correctPanel() { + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertEquals(activityName, controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityDefaultEnabled_correctPanel() { + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + enabled = true, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertEquals(activityName, controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityDefaultDisabled_nullPanel() { + val serviceInfo = ServiceInfo( + componentName, + activityName + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + enabled = false, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityDefaultEnabled_flagDisabled_nullPanel() { + `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false) + val serviceInfo = ServiceInfo( + componentName, + activityName, + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + enabled = true, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + @Test + fun testActivityDifferentPackage_nullPanel() { + val serviceInfo = ServiceInfo( + componentName, + ComponentName("other_package", "cls") + ) + + `when`(packageManager.getComponentEnabledSetting(eq(activityName))) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + + setUpQueryResult(listOf( + ActivityInfo( + activityName, + enabled = true, + exported = true, + permission = Manifest.permission.BIND_CONTROLS + ) + )) + + val list = listOf(serviceInfo) + serviceListingCallbackCaptor.value.onServicesReloaded(list) + + executor.runAllReady() + + assertNull(controller.getCurrentServices()[0].panelActivity) + } + + private fun ServiceInfo( + componentName: ComponentName, + panelActivityComponentName: ComponentName? = null + ): ServiceInfo { + return ServiceInfo().apply { + packageName = componentName.packageName + name = componentName.className + panelActivityComponentName?.let { + metaData = Bundle().apply { + putString( + ControlsProviderService.META_DATA_PANEL_ACTIVITY, + it.flattenToShortString() + ) + } + } + } + } + + private fun ActivityInfo( + componentName: ComponentName, + exported: Boolean = false, + enabled: Boolean = true, + permission: String? = null + ): ActivityInfo { + return ActivityInfo().apply { + packageName = componentName.packageName + name = componentName.className + this.permission = permission + this.exported = exported + this.enabled = enabled + } + } + + private fun setUpQueryResult(infos: List<ActivityInfo>) { + `when`( + packageManager.queryIntentActivitiesAsUser( + argThat(IntentMatcher(activityName)), + argThat(FlagsMatcher(FLAGS)), + eq(UserHandle.of(user)) + ) + ).thenReturn(infos.map { + ResolveInfo().apply { activityInfo = it } + }) + } + + private class IntentMatcher( + private val componentName: ComponentName + ) : ArgumentMatcher<Intent> { + override fun matches(argument: Intent?): Boolean { + return argument?.component == componentName + } + } + + private class FlagsMatcher( + private val flags: Long + ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> { + override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean { + return flags == argument?.value + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt deleted file mode 100644 index 4d66a168303c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ /dev/null @@ -1,302 +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.keyguard - -import android.content.pm.PackageManager -import android.content.pm.ProviderInfo -import androidx.test.filters.SmallTest -import com.android.internal.widget.LockPatternUtils -import com.android.systemui.SystemUIAppComponentFactoryBase -import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderClient as Client -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract -import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(JUnit4::class) -class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { - - @Mock private lateinit var lockPatternUtils: LockPatternUtils - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var userTracker: UserTracker - @Mock private lateinit var activityStarter: ActivityStarter - - private lateinit var underTest: KeyguardQuickAffordanceProvider - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - underTest = KeyguardQuickAffordanceProvider() - val quickAffordanceRepository = - KeyguardQuickAffordanceRepository( - scope = CoroutineScope(IMMEDIATE), - backgroundDispatcher = IMMEDIATE, - selectionManager = KeyguardQuickAffordanceSelectionManager(), - configs = - setOf( - FakeKeyguardQuickAffordanceConfig( - key = AFFORDANCE_1, - pickerIconResourceId = 1, - ), - FakeKeyguardQuickAffordanceConfig( - key = AFFORDANCE_2, - pickerIconResourceId = 2, - ), - ), - ) - underTest.interactor = - KeyguardQuickAffordanceInteractor( - keyguardInteractor = - KeyguardInteractor( - repository = FakeKeyguardRepository(), - ), - registry = mock(), - lockPatternUtils = lockPatternUtils, - keyguardStateController = keyguardStateController, - userTracker = userTracker, - activityStarter = activityStarter, - featureFlags = - FakeFeatureFlags().apply { - set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) - }, - repository = { quickAffordanceRepository }, - ) - - underTest.attachInfoForTesting( - context, - ProviderInfo().apply { authority = Contract.AUTHORITY }, - ) - context.contentResolver.addProvider(Contract.AUTHORITY, underTest) - context.testablePermissions.setPermission( - Contract.PERMISSION, - PackageManager.PERMISSION_GRANTED, - ) - } - - @Test - fun `onAttachInfo - reportsContext`() { - val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock() - underTest.setContextAvailableCallback(callback) - - underTest.attachInfo(context, null) - - verify(callback).onContextAvailable(context) - } - - @Test - fun getType() { - assertThat(underTest.getType(Contract.AffordanceTable.URI)) - .isEqualTo( - "vnd.android.cursor.dir/vnd." + - "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}" - ) - assertThat(underTest.getType(Contract.SlotTable.URI)) - .isEqualTo( - "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}" - ) - assertThat(underTest.getType(Contract.SelectionTable.URI)) - .isEqualTo( - "vnd.android.cursor.dir/vnd." + - "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}" - ) - } - - @Test - fun `insert and query selection`() = - runBlocking(IMMEDIATE) { - val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START - val affordanceId = AFFORDANCE_2 - - Client.insertSelection( - context = context, - slotId = slotId, - affordanceId = affordanceId, - dispatcher = IMMEDIATE, - ) - - assertThat( - Client.querySelections( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Selection( - slotId = slotId, - affordanceId = affordanceId, - ) - ) - ) - } - - @Test - fun `query slots`() = - runBlocking(IMMEDIATE) { - assertThat( - Client.querySlots( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - capacity = 1, - ), - Client.Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - capacity = 1, - ), - ) - ) - } - - @Test - fun `query affordances`() = - runBlocking(IMMEDIATE) { - assertThat( - Client.queryAffordances( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Affordance( - id = AFFORDANCE_1, - name = AFFORDANCE_1, - iconResourceId = 1, - ), - Client.Affordance( - id = AFFORDANCE_2, - name = AFFORDANCE_2, - iconResourceId = 2, - ), - ) - ) - } - - @Test - fun `delete and query selection`() = - runBlocking(IMMEDIATE) { - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - dispatcher = IMMEDIATE, - ) - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - dispatcher = IMMEDIATE, - ) - - Client.deleteSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - dispatcher = IMMEDIATE, - ) - - assertThat( - Client.querySelections( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Selection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - ) - ) - ) - } - - @Test - fun `delete all selections in a slot`() = - runBlocking(IMMEDIATE) { - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - dispatcher = IMMEDIATE, - ) - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - dispatcher = IMMEDIATE, - ) - - Client.deleteAllSelections( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - dispatcher = IMMEDIATE, - ) - - assertThat( - Client.querySelections( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Selection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - ) - ) - ) - } - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - private const val AFFORDANCE_1 = "affordance_1" - private const val AFFORDANCE_2 = "affordance_2" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index ade83cf58e6e..6ba06344314c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost @@ -60,12 +61,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { underTest = KeyguardRepositoryImpl( - statusBarStateController, - dozeHost, - wakefulnessLifecycle, - biometricUnlockController, - keyguardStateController, - keyguardUpdateMonitor, + statusBarStateController, + dozeHost, + wakefulnessLifecycle, + biometricUnlockController, + keyguardStateController, + keyguardUpdateMonitor, ) } @@ -257,6 +258,48 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun isKeyguardGoingAway() = runBlockingTest { + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardStateController.Callback>() + verify(keyguardStateController).addCallback(captor.capture()) + + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true) + captor.value.onKeyguardGoingAwayChanged() + assertThat(latest).isTrue() + + whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false) + captor.value.onKeyguardGoingAwayChanged() + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isDreaming() = runBlockingTest { + whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false) + var latest: Boolean? = null + val job = underTest.isDreaming.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + captor.value.onDreamingStateChanged(true) + assertThat(latest).isTrue() + + captor.value.onDreamingStateChanged(false) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test fun biometricUnlockState() = runBlockingTest { val values = mutableListOf<BiometricUnlockModel>() val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 27d5d0a98978..2b03722f9f31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -25,8 +25,8 @@ import android.view.Choreographer.FrameCallback import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState @@ -38,7 +38,6 @@ import java.util.UUID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn @@ -91,18 +90,51 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } } - assertSteps(steps, listWithStep(BigDecimal(.1))) + assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN) job.cancel() provider.stop() } @Test - fun `startTransition called during another transition fails`() { - underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null)) - underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null)) + fun `starting second transition will cancel the first transition`() { + runBlocking(IMMEDIATE) { + val (animator, provider) = setupAnimator(this) - assertThat(wtfHandler.failed).isTrue() + val steps = mutableListOf<TransitionStep>() + val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) + + underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) + // 3 yields(), alternating with the animator, results in a value 0.1, which can be + // canceled and tested against + yield() + yield() + yield() + + // Now start 2nd transition, which will interrupt the first + val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this) + val (animator2, provider2) = setupAnimator(this) + underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2)) + + val startTime = System.currentTimeMillis() + while (animator2.isRunning()) { + yield() + if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) { + fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION") + } + } + + val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1)) + assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN) + + val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9)) + assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD) + + job.cancel() + job2.cancel() + provider.stop() + provider2.stop() + } } @Test @@ -165,11 +197,15 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(wtfHandler.failed).isTrue() } - private fun listWithStep(step: BigDecimal): List<BigDecimal> { + private fun listWithStep( + step: BigDecimal, + start: BigDecimal = BigDecimal.ZERO, + stop: BigDecimal = BigDecimal.ONE, + ): List<BigDecimal> { val steps = mutableListOf<BigDecimal>() - var i = BigDecimal.ZERO - while (i.compareTo(BigDecimal.ONE) <= 0) { + var i = start + while (i.compareTo(stop) <= 0) { steps.add(i) i = (i + step).setScale(2, RoundingMode.HALF_UP) } @@ -177,23 +213,43 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { return steps } - private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) { + private fun assertSteps( + steps: List<TransitionStep>, + fractions: List<BigDecimal>, + from: KeyguardState, + to: KeyguardState, + ) { assertThat(steps[0]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME)) + .isEqualTo( + TransitionStep( + from, + to, + fractions[0].toFloat(), + TransitionState.STARTED, + OWNER_NAME + ) + ) fractions.forEachIndexed { index, fraction -> assertThat(steps[index + 1]) .isEqualTo( TransitionStep( - AOD, - LOCKSCREEN, + from, + to, fraction.toFloat(), TransitionState.RUNNING, OWNER_NAME ) ) } + val lastValue = fractions[fractions.size - 1].toFloat() + val status = + if (lastValue < 1f) { + TransitionState.CANCELED + } else { + TransitionState.FINISHED + } assertThat(steps[steps.size - 1]) - .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)) + .isEqualTo(TransitionStep(from, to, lastValue, status, OWNER_NAME)) assertThat(wtfHandler.failed).isFalse() } @@ -230,7 +286,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { scope.launch { frames.collect { // Delay is required for AnimationHandler to properly register a callback - delay(1) + yield() val (frameNumber, callback) = it callback?.doFrame(frameNumber) } @@ -243,7 +299,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } override fun postFrameCallback(cb: FrameCallback) { - frames.value = Pair(++frameCount, cb) + frames.value = Pair(frameCount++, cb) } override fun postCommitCallback(runnable: Runnable) {} override fun getFrameTime() = frameCount 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 c85f7b9e6885..21e506830d88 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 @@ -155,6 +155,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN) verify(repository).setPrimaryVisible(false) verify(repository).setPrimaryShow(null) + verify(repository).setPrimaryHide(true) verify(falsingCollector).onBouncerHidden() verify(mPrimaryBouncerCallbackInteractor).dispatchReset() verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index cd7a949443c9..72e022ed7bbb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -439,6 +439,17 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { verify(mQSPanelController).setExpanded(false); } + @Test + public void startsListeningAfterStateChangeToExpanded_inSplitShade() { + QSFragment fragment = resumeAndGetFragment(); + enableSplitShade(); + fragment.setQsVisible(true); + clearInvocations(mQSPanelController); + + fragment.setExpanded(true); + verify(mQSPanelController).setListening(true, true); + } + @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt new file mode 100644 index 000000000000..0aa36218b3a7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -0,0 +1,109 @@ +/* + * 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.screenrecord + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.widget.Spinner +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.settings.UserContextProvider +import com.android.systemui.util.mockito.mock +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.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class ScreenRecordPermissionDialogTest : SysuiTestCase() { + + @Mock private lateinit var starter: ActivityStarter + @Mock private lateinit var controller: RecordingController + @Mock private lateinit var userContextProvider: UserContextProvider + @Mock private lateinit var flags: FeatureFlags + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock private lateinit var onStartRecordingClicked: Runnable + + private lateinit var dialog: ScreenRecordPermissionDialog + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + dialog = + ScreenRecordPermissionDialog( + context, + controller, + starter, + dialogLaunchAnimator, + userContextProvider, + onStartRecordingClicked + ) + dialog.onCreate(null) + whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true) + } + + @After + fun teardown() { + if (::dialog.isInitialized) { + dialog.dismiss() + } + } + + @Test + fun testShowDialog_partialScreenSharingEnabled_optionsSpinnerIsVisible() { + dialog.show() + + val visibility = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner).visibility + assertThat(visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun testShowDialog_singleAppSelected_showTapsIsGone() { + dialog.show() + onSpinnerItemSelected(SINGLE_APP) + + val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility + assertThat(visibility).isEqualTo(View.GONE) + } + + @Test + fun testShowDialog_entireScreenSelected_showTapsIsVisible() { + dialog.show() + onSpinnerItemSelected(ENTIRE_SCREEN) + + val visibility = dialog.requireViewById<View>(R.id.show_taps).visibility + assertThat(visibility).isEqualTo(View.VISIBLE) + } + + private fun onSpinnerItemSelected(position: Int) { + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) + spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0) + } +} 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 1f71e3c64ec4..7d2251e20021 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -94,7 +94,6 @@ import com.android.systemui.DejankUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.camera.CameraGestureHelper; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.doze.DozeLog; @@ -492,7 +491,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mShadeTransitionController, systemClock, - mock(CameraGestureHelper.class), mKeyguardBottomAreaViewModel, mKeyguardBottomAreaInteractor, mDumpManager); 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 c6585931a035..77c690a924c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -1421,6 +1421,21 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test + public void onBiometricError_faceLockedOutSecondTimeOnBouncer_showsUnavailableMessage() { + createController(); + onFaceLockoutError("first lockout"); + clearInvocations(mRotateTextViewController); + when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); + + onFaceLockoutError("second lockout"); + + verify(mStatusBarKeyguardViewManager) + .setKeyguardMessage( + eq(mContext.getString(R.string.keyguard_face_unlock_unavailable)), + any()); + } + + @Test public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() { createController(); onFaceLockoutError("first lockout"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt new file mode 100644 index 000000000000..62b4e7b79f5e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileIconCarrierIdOverridesFake.kt @@ -0,0 +1,48 @@ +/* + * 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.statusbar.connectivity + +import android.content.res.Resources +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides + +typealias CarrierId = Int + +typealias NetworkType = String + +typealias ResId = Int + +class MobileIconCarrierIdOverridesFake : MobileIconCarrierIdOverrides { + /** Backing for [carrierIdEntryExists] */ + var overriddenIds = mutableSetOf<Int>() + + /** Backing for [getOverrideFor]. Map should be Map< CarrierId < NetworkType, ResId>> */ + var overridesByCarrierId = mutableMapOf<CarrierId, Map<NetworkType, ResId>>() + + override fun getOverrideFor( + carrierId: CarrierId, + networkType: NetworkType, + resources: Resources + ): ResId { + if (!overriddenIds.contains(carrierId)) return 0 + + return overridesByCarrierId[carrierId]?.get(networkType) ?: 0 + } + + override fun carrierIdEntryExists(carrierId: Int): Boolean { + return overriddenIds.contains(carrierId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java deleted file mode 100644 index 7ddfde370afa..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.java +++ /dev/null @@ -1,137 +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 com.android.systemui.statusbar.connectivity; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; - -import com.android.settingslib.mobile.TelephonyIcons; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class MobileStateTest extends SysuiTestCase { - - private final MobileState mState = new MobileState(); - - @Before - public void setUp() { - } - - @Test - public void testIsDataDisabledOrNotDefault_dataDisabled() { - mState.iconGroup = TelephonyIcons.DATA_DISABLED; - mState.userSetup = true; - - assertTrue(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testIsDataDisabledOrNotDefault_notDefaultData() { - mState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA; - mState.userSetup = true; - - assertTrue(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testIsDataDisabledOrNotDefault_notDisabled() { - mState.iconGroup = TelephonyIcons.G; - mState.userSetup = true; - - assertFalse(mState.isDataDisabledOrNotDefault()); - } - - @Test - public void testHasActivityIn_noData_noActivity() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityIn = false; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_noData_activityIn() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityIn = true; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_dataConnected_activityIn() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = false; - mState.activityIn = true; - - assertTrue(mState.hasActivityIn()); - } - - @Test - public void testHasActivityIn_carrierNetworkChange() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = true; - mState.activityIn = true; - - assertFalse(mState.hasActivityIn()); - } - - @Test - public void testHasActivityOut_noData_noActivity() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityOut = false; - - assertFalse(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_noData_activityOut() { - mState.dataConnected = false; - mState.carrierNetworkChangeMode = false; - mState.activityOut = true; - - assertFalse(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_dataConnected_activityOut() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = false; - mState.activityOut = true; - - assertTrue(mState.hasActivityOut()); - } - - @Test - public void testHasActivityOut_carrierNetworkChange() { - mState.dataConnected = true; - mState.carrierNetworkChangeMode = true; - mState.activityOut = true; - - assertFalse(mState.hasActivityOut()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt new file mode 100644 index 000000000000..a226ded06111 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt @@ -0,0 +1,119 @@ +/* + * 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 com.android.systemui.statusbar.connectivity + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class MobileStateTest : SysuiTestCase() { + + private val state = MobileState() + @Before fun setUp() {} + + @Test + fun testIsDataDisabledOrNotDefault_dataDisabled() { + state.iconGroup = TelephonyIcons.DATA_DISABLED + state.userSetup = true + assertTrue(state.isDataDisabledOrNotDefault) + } + + @Test + fun testIsDataDisabledOrNotDefault_notDefaultData() { + state.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA + state.userSetup = true + assertTrue(state.isDataDisabledOrNotDefault) + } + + @Test + fun testIsDataDisabledOrNotDefault_notDisabled() { + state.iconGroup = TelephonyIcons.G + state.userSetup = true + assertFalse(state.isDataDisabledOrNotDefault) + } + + @Test + fun testHasActivityIn_noData_noActivity() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityIn = false + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_noData_activityIn() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityIn = true + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_dataConnected_activityIn() { + state.dataConnected = true + state.carrierNetworkChangeMode = false + state.activityIn = true + assertTrue(state.hasActivityIn()) + } + + @Test + fun testHasActivityIn_carrierNetworkChange() { + state.dataConnected = true + state.carrierNetworkChangeMode = true + state.activityIn = true + assertFalse(state.hasActivityIn()) + } + + @Test + fun testHasActivityOut_noData_noActivity() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityOut = false + assertFalse(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_noData_activityOut() { + state.dataConnected = false + state.carrierNetworkChangeMode = false + state.activityOut = true + assertFalse(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_dataConnected_activityOut() { + state.dataConnected = true + state.carrierNetworkChangeMode = false + state.activityOut = true + assertTrue(state.hasActivityOut()) + } + + @Test + fun testHasActivityOut_carrierNetworkChange() { + state.dataConnected = true + state.carrierNetworkChangeMode = true + state.activityOut = true + assertFalse(state.hasActivityOut()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index 9c65fac1af45..9c870b5aa363 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -71,6 +71,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.telephony.TelephonyListenerManager; @@ -125,6 +126,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase { protected CarrierConfigTracker mCarrierConfigTracker; protected FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); protected Handler mMainHandler; + // Use a real mobile mappings object since lots of tests rely on it + protected FakeMobileMappingsProxy mMobileMappingsProxy = new FakeMobileMappingsProxy(); protected WifiStatusTrackerFactory mWifiStatusTrackerFactory; protected MobileSignalControllerFactory mMobileFactory; @@ -219,10 +222,13 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mWifiStatusTrackerFactory = new WifiStatusTrackerFactory( mContext, mMockWm, mMockNsm, mMockCm, mMainHandler); + // Most of these tests rely on the actual MobileMappings behavior + mMobileMappingsProxy.setUseRealImpl(true); mMobileFactory = new MobileSignalControllerFactory( mContext, mCallbackHandler, - mCarrierConfigTracker + mCarrierConfigTracker, + mMobileMappingsProxy ); mNetworkController = new NetworkControllerImpl(mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index 4bed4a19b3d9..1d112262765e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -18,12 +18,21 @@ package com.android.systemui.statusbar.connectivity; import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; +import static android.telephony.TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED; +import static android.telephony.TelephonyManager.EXTRA_CARRIER_ID; +import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID; +import static com.android.settingslib.mobile.TelephonyIcons.NR_5G_PLUS; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.Intent; import android.net.NetworkCapabilities; import android.os.Handler; import android.os.Looper; @@ -35,6 +44,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import com.android.settingslib.SignalIcon.MobileIconGroup; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; import com.android.systemui.dump.DumpManager; @@ -45,6 +55,8 @@ import com.android.systemui.util.CarrierConfigTracker; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.HashMap; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -329,6 +341,57 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { assertFalse(mNetworkController.isMobileDataNetworkInService()); } + @Test + public void mobileSignalController_getsCarrierId() { + when(mMockTm.getSimCarrierId()).thenReturn(1); + setupDefaultSignal(); + + assertEquals(1, mMobileSignalController.getState().getCarrierId()); + } + + @Test + public void mobileSignalController_updatesCarrierId_onChange() { + when(mMockTm.getSimCarrierId()).thenReturn(1); + setupDefaultSignal(); + + // Updates are sent down through this broadcast, we can send the intent directly + Intent intent = new Intent(ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); + intent.putExtra(EXTRA_SUBSCRIPTION_ID, mSubId); + intent.putExtra(EXTRA_CARRIER_ID, 2); + + mMobileSignalController.handleBroadcast(intent); + + assertEquals(2, mMobileSignalController.getState().getCarrierId()); + } + + @Test + public void networkTypeIcon_hasCarrierIdOverride() { + int fakeCarrier = 1; + int fakeIconOverride = 12345; + int testDataNetType = 100; + String testDataString = "100"; + HashMap<String, MobileIconGroup> testMap = new HashMap<>(); + testMap.put(testDataString, NR_5G_PLUS); + + // Pretend that there is an override for this icon, and this carrier ID + NetworkTypeResIdCache mockCache = mock(NetworkTypeResIdCache.class); + when(mockCache.get(eq(NR_5G_PLUS), eq(fakeCarrier), any())).thenReturn(fakeIconOverride); + + // Turn off the default mobile mapping, so we can override + mMobileMappingsProxy.setUseRealImpl(false); + mMobileMappingsProxy.setIconMap(testMap); + // Use the mocked cache + mMobileSignalController.mCurrentState.setNetworkTypeResIdCache(mockCache); + // Rebuild the network map + mMobileSignalController.setConfiguration(mConfig); + when(mMockTm.getSimCarrierId()).thenReturn(fakeCarrier); + + setupDefaultSignal(); + updateDataConnectionState(TelephonyManager.DATA_CONNECTED, testDataNetType); + + verifyDataIndicators(fakeIconOverride); + } + private void testDataActivity(int direction, boolean in, boolean out) { updateDataActivity(direction); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt new file mode 100644 index 000000000000..9e73487972e8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt @@ -0,0 +1,83 @@ +/* + * 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.statusbar.connectivity + +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class NetworkTypeResIdCacheTest : SysuiTestCase() { + private lateinit var cache: NetworkTypeResIdCache + private var overrides = MobileIconCarrierIdOverridesFake() + + @Before + fun setUp() { + cache = NetworkTypeResIdCache(overrides) + } + + @Test + fun carrier1_noOverride_usesDefault() { + assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconDefault1) + } + + @Test + fun carrier1_overridden_usesOverride() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group1, CARRIER_1, context)).isEqualTo(iconOverride1) + } + + @Test + fun carrier1_override_carrier2UsesDefault() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group1, CARRIER_2, context)).isEqualTo(iconDefault1) + } + + @Test + fun carrier1_overrideType1_type2UsesDefault() { + overrides.overriddenIds.add(CARRIER_1) + overrides.overridesByCarrierId[CARRIER_1] = mapOf(NET_TYPE_1 to iconOverride1) + + assertThat(cache.get(group2, CARRIER_1, context)).isEqualTo(iconDefault2) + } + + companion object { + // Simplified icon overrides here + const val CARRIER_1 = 1 + const val CARRIER_2 = 2 + + const val NET_TYPE_1 = "one" + const val iconDefault1 = 123 + const val iconOverride1 = 321 + val group1 = MobileIconGroup(NET_TYPE_1, /* dataContentDesc */ 0, iconDefault1) + + const val NET_TYPE_2 = "two" + const val iconDefault2 = 234 + + val group2 = MobileIconGroup(NET_TYPE_2, /* dataContentDesc*/ 0, iconDefault2) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index 5ebaf692ab44..d5bfe1f7c2ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -41,6 +41,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; @@ -60,6 +61,8 @@ import org.mockito.stubbing.Answer; import java.util.Optional; +import dagger.Lazy; + @SmallTest @RunWith(AndroidTestingRunner.class) public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @@ -84,6 +87,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private Vibrator mVibrator; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private SystemBarAttributesListener mSystemBarAttributesListener; + @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; CentralSurfacesCommandQueueCallbacks mSbcqCallbacks; @@ -115,7 +119,8 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { Optional.of(mVibrator), new DisableFlagsLogger(), DEFAULT_DISPLAY, - mSystemBarAttributesListener); + mSystemBarAttributesListener, + mCameraLauncherLazy); when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 5ad1431cc8d8..41912f51db56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -112,6 +112,7 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; @@ -288,6 +289,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private InteractionJankMonitor mJankMonitor; @Mock private DeviceStateManager mDeviceStateManager; @Mock private WiredChargingRippleController mWiredChargingRippleController; + @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; + @Mock private CameraLauncher mCameraLauncher; /** * The process of registering/unregistering a predictive back callback requires a * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test. @@ -380,6 +383,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper); when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController); + when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher); when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent); when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn( @@ -481,7 +485,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mActivityLaunchAnimator, mJankMonitor, mDeviceStateManager, - mWiredChargingRippleController, mDreamManager) { + mWiredChargingRippleController, + mDreamManager, + mCameraLauncherLazy) { @Override protected ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -893,7 +899,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.showKeyguardImpl(); // Starting a pulse should change the scrim controller to the pulsing state - when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true); + when(mCameraLauncher.isLaunchingAffordance()).thenReturn(true); mCentralSurfaces.updateScrimController(); verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any()); } @@ -929,7 +935,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.showKeyguardImpl(); // Starting a pulse should change the scrim controller to the pulsing state - when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false); + when(mCameraLauncher.isLaunchingAffordance()).thenReturn(false); mCentralSurfaces.updateScrimController(); verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index df48e1d43584..808abc8e9de5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -1437,6 +1437,17 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void behindTint_inKeyguardState_bouncerNotActive_usesKeyguardBehindTint() { + when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); + mScrimController.setClipsQsScrim(false); + + mScrimController.transitionTo(ScrimState.KEYGUARD); + finishAnimationsImmediately(); + assertThat(mScrimBehind.getTint()) + .isEqualTo(ScrimState.KEYGUARD.getBehindTint()); + } + + @Test public void testNotificationTransparency_followsTransitionToFullShade() { mScrimController.transitionTo(SHADE_LOCKED); mScrimController.setRawPanelExpansionFraction(1.0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt index 6d8d902615de..a052008d4832 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt @@ -16,31 +16,59 @@ package com.android.systemui.statusbar.pipeline.mobile.util +import android.telephony.TelephonyDisplayInfo import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings.Config import com.android.settingslib.mobile.TelephonyIcons class FakeMobileMappingsProxy : MobileMappingsProxy { + // The old [NetworkControllerDataTest] infra requires us to be able to use the real + // impl sometimes + var useRealImpl = false + + private var realImpl = MobileMappingsProxyImpl() private var iconMap = mapOf<String, MobileIconGroup>() private var defaultIcons = TelephonyIcons.THREE_G fun setIconMap(map: Map<String, MobileIconGroup>) { iconMap = map } - override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap + override fun mapIconSets(config: Config): Map<String, MobileIconGroup> { + if (useRealImpl) { + return realImpl.mapIconSets(config) + } + return iconMap + } fun getIconMap() = iconMap fun setDefaultIcons(group: MobileIconGroup) { defaultIcons = group } - override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons + override fun getDefaultIcons(config: Config): MobileIconGroup { + if (useRealImpl) { + return realImpl.getDefaultIcons(config) + } + return defaultIcons + } + + /** This is only used in the old pipeline, use the real impl always */ + override fun getIconKey(displayInfo: TelephonyDisplayInfo): String { + return realImpl.getIconKey(displayInfo) + } + fun getDefaultIcons(): MobileIconGroup = defaultIcons override fun toIconKey(networkType: Int): String { + if (useRealImpl) { + return realImpl.toIconKeyOverride(networkType) + } return networkType.toString() } override fun toIconKeyOverride(networkType: Int): String { + if (useRealImpl) { + return realImpl.toIconKeyOverride(networkType) + } return toIconKey(networkType) + "_override" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index 9dea48e3b47c..8572478589fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -123,23 +123,23 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } @Test - fun displayView_screenOff_wakeLockAcquired() { + fun displayView_wakeLockAcquired() { underTest.displayView(getState()) assertThat(fakeWakeLock.isHeld).isTrue() } @Test - fun displayView_screenAlreadyOn_wakeLockNotAcquired() { + fun displayView_screenAlreadyOn_wakeLockAcquired() { whenever(powerManager.isScreenOn).thenReturn(true) underTest.displayView(getState()) - assertThat(fakeWakeLock.isHeld).isFalse() + assertThat(fakeWakeLock.isHeld).isTrue() } @Test - fun displayView_screenOff_wakeLockCanBeReleasedAfterTimeOut() { + fun displayView_wakeLockCanBeReleasedAfterTimeOut() { underTest.displayView(getState()) assertThat(fakeWakeLock.isHeld).isTrue() @@ -149,6 +149,16 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } @Test + fun displayView_removeView_wakeLockCanBeReleased() { + underTest.displayView(getState()) + assertThat(fakeWakeLock.isHeld).isTrue() + + underTest.removeView("test reason") + + assertThat(fakeWakeLock.isHeld).isFalse() + } + + @Test fun displayView_twice_viewNotAddedTwice() { underTest.displayView(getState()) reset(windowManager) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 6f70f0ee0f2b..a798f403c73a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -44,6 +44,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: Flow<Boolean> = _isDozing + private val _isDreaming = MutableStateFlow(false) + override val isDreaming: Flow<Boolean> = _isDreaming + private val _dozeAmount = MutableStateFlow(0f) override val dozeAmount: Flow<Float> = _dozeAmount @@ -54,10 +57,13 @@ class FakeKeyguardRepository : KeyguardRepository { override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState private val _isUdfpsSupported = MutableStateFlow(false) - + private val _isBouncerShowing = MutableStateFlow(false) override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing + private val _isKeyguardGoingAway = MutableStateFlow(false) + override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway + private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index e40f001f27d5..933d2596aed8 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -473,18 +473,6 @@ public abstract class SystemService { } /** - * The {@link UserManager#isUserVisible() user visibility} changed. - * - * <p>This callback is called before the user starts or is switched to (or after it stops), when - * its visibility changed because of that action. - * - * @hide - */ - // NOTE: change visible to int if this method becomes a @SystemApi - public void onUserVisibilityChanged(@NonNull TargetUser user, boolean visible) { - } - - /** * Called when an existing user is stopping, for system services to finalize any per-user * state they maintain for running users. This is called prior to sending the SHUTDOWN * broadcast to the user; it is a good place to stop making use of any resources of that diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index 953e85014535..c3cd1359cd5f 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -82,10 +82,6 @@ public final class SystemServiceManager implements Dumpable { private static final String USER_STOPPING = "Stop"; // Logged as onUserStopping() private static final String USER_STOPPED = "Cleanup"; // Logged as onUserStopped() private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onUserCompletedEvent() - private static final String USER_VISIBLE = "Visible"; // Logged on onUserVisible() and - // onUserStarting() (when visible is true) - private static final String USER_INVISIBLE = "Invisible"; // Logged on onUserStopping() - // (when visibilityChanged is true) // The default number of threads to use if lifecycle thread pool is enabled. private static final int DEFAULT_MAX_USER_POOL_THREADS = 3; @@ -354,58 +350,17 @@ public final class SystemServiceManager implements Dumpable { /** * Starts the given user. */ - public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId, - boolean visible) { - EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId, visible ? 1 : 0); + public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) { + EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId); final TargetUser targetUser = newTargetUser(userId); synchronized (mTargetUsers) { mTargetUsers.put(userId, targetUser); } - - if (visible) { - // Must send the user visiiblity change first, for 2 reasons: - // 1. Automotive need to update the user-zone mapping ASAP and it's one of the few - // services listening to this event (OTOH, there are manyy listeners to USER_STARTING - // and some can take a while to process it) - // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is - // called onUserSwitching(), so calling it before onUserStarting() make it more - // consistent with that - EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1); - onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser); - } onUser(t, USER_STARTING, /* prevUser= */ null, targetUser); } /** - * Updates the user visibility. - * - * <p><b>NOTE: </b>this method should only be called when a user that is already running become - * visible; if the user is starting visible, callers should call - * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead. - */ - public void onUserVisible(@UserIdInt int userId) { - EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1); - onUser(USER_VISIBLE, userId); - } - - /** - * Updates the visibility of the system user. - * - * <p>Since the system user never stops, this method must be called when it's switched from / to - * foreground. - */ - public void onSystemUserVisibilityChanged(boolean visible) { - int userId = UserHandle.USER_SYSTEM; - EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0); - if (visible) { - onUser(USER_VISIBLE, userId); - } else { - onUser(USER_INVISIBLE, userId); - } - } - - /** * Unlocks the given user. */ public void onUserUnlocking(@UserIdInt int userId) { @@ -452,12 +407,9 @@ public final class SystemServiceManager implements Dumpable { /** * Stops the given user. */ - public void onUserStopping(@UserIdInt int userId, boolean visibilityChanged) { - EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId, visibilityChanged ? 1 : 0); + public void onUserStopping(@UserIdInt int userId) { + EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId); onUser(USER_STOPPING, userId); - if (visibilityChanged) { - onUser(USER_INVISIBLE, userId); - } } /** @@ -580,12 +532,6 @@ public final class SystemServiceManager implements Dumpable { threadPool.submit(getOnUserCompletedEventRunnable( t, service, serviceName, curUser, completedEventType)); break; - case USER_VISIBLE: - service.onUserVisibilityChanged(curUser, /* visible= */ true); - break; - case USER_INVISIBLE: - service.onUserVisibilityChanged(curUser, /* visible= */ false); - break; default: throw new IllegalArgumentException(onWhat + " what?"); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2761a864f22f..af6eaf4e94bd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19050,6 +19050,10 @@ public class ActivityManagerService extends IActivityManager.Stub return mOomAdjuster.mCachedAppOptimizer.useFreezer(); } + public boolean isAppFreezerExemptInstPkg() { + return mOomAdjuster.mCachedAppOptimizer.freezerExemptInstPkg(); + } + /** * Resets the state of the {@link com.android.server.am.AppErrors} instance. * This is intended for testing within the CTS only and is protected by diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index c3839a99ba52..3dee2627e45d 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -37,6 +37,7 @@ import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER; import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; @@ -239,7 +240,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } case MSG_DELIVERY_TIMEOUT_SOFT: { synchronized (mService) { - deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj); + final SomeArgs args = (SomeArgs) msg.obj; + final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1; + final long originalTimeout = args.argl1; + args.recycle(); + deliveryTimeoutSoftLocked(queue, originalTimeout); } return true; } @@ -599,6 +604,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // If nothing to dispatch, send any pending result immediately if (r.receivers.isEmpty()) { scheduleResultTo(r); + notifyFinishBroadcast(r); } traceEnd(cookie); @@ -746,8 +752,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.lastCpuDelayTime = queue.app.getCpuDelayTime(); final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT; + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = queue; + args.argl1 = timeout; mLocalHandler.sendMessageDelayed( - Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, queue), timeout); + Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_SOFT, args), timeout); } if (r.allowBackgroundActivityStarts) { @@ -834,15 +843,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultTo = null; } - private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue) { + private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue, + @DurationMillisLong long originalTimeout) { if (queue.app != null) { // Instead of immediately triggering an ANR, extend the timeout by // the amount of time the process was runnable-but-waiting; we're // only willing to do this once before triggering an hard ANR final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime; - final long timeout = MathUtils.constrain(cpuDelayTime, 0, mConstants.TIMEOUT); + final long hardTimeout = MathUtils.constrain(cpuDelayTime, 0, originalTimeout); mLocalHandler.sendMessageDelayed( - Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), timeout); + Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), hardTimeout); } else { deliveryTimeoutHardLocked(queue); } @@ -1402,30 +1412,34 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final boolean recordFinished = (r.terminalCount == r.receivers.size()); if (recordFinished) { - mService.notifyBroadcastFinishedLocked(r); - mHistory.addBroadcastToHistoryLocked(r); - - r.finishTime = SystemClock.uptimeMillis(); - r.nextReceiver = r.receivers.size(); - BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r); + notifyFinishBroadcast(r); + } + } - if (r.intent.getComponent() == null && r.intent.getPackage() == null - && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { - int manifestCount = 0; - int manifestSkipCount = 0; - for (int i = 0; i < r.receivers.size(); i++) { - if (r.receivers.get(i) instanceof ResolveInfo) { - manifestCount++; - if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) { - manifestSkipCount++; - } + private void notifyFinishBroadcast(@NonNull BroadcastRecord r) { + mService.notifyBroadcastFinishedLocked(r); + mHistory.addBroadcastToHistoryLocked(r); + + r.finishTime = SystemClock.uptimeMillis(); + r.nextReceiver = r.receivers.size(); + BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r); + + if (r.intent.getComponent() == null && r.intent.getPackage() == null + && (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { + int manifestCount = 0; + int manifestSkipCount = 0; + for (int i = 0; i < r.receivers.size(); i++) { + if (r.receivers.get(i) instanceof ResolveInfo) { + manifestCount++; + if (r.delivery[i] == BroadcastRecord.DELIVERY_SKIPPED) { + manifestSkipCount++; } } - - final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime; - mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage, - manifestCount, manifestSkipCount, dispatchTime); } + + final long dispatchTime = SystemClock.uptimeMillis() - r.enqueueTime; + mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage, + manifestCount, manifestSkipCount, dispatchTime); } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index cbf0aae50e87..2d7b0dc7b536 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -89,6 +89,8 @@ public final class CachedAppOptimizer { "compact_proc_state_throttle"; @VisibleForTesting static final String KEY_FREEZER_DEBOUNCE_TIMEOUT = "freeze_debounce_timeout"; + @VisibleForTesting static final String KEY_FREEZER_EXEMPT_INST_PKG = + "freeze_exempt_inst_pkg"; // RSS Indices private static final int RSS_TOTAL_INDEX = 0; @@ -137,6 +139,7 @@ public final class CachedAppOptimizer { @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE = String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER); @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 600_000L; + @VisibleForTesting static final Boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true; @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor( Settings.Global.CACHED_APPS_FREEZER_ENABLED); @@ -277,6 +280,8 @@ public final class CachedAppOptimizer { for (String name : properties.getKeyset()) { if (KEY_FREEZER_DEBOUNCE_TIMEOUT.equals(name)) { updateFreezerDebounceTimeout(); + } else if (KEY_FREEZER_EXEMPT_INST_PKG.equals(name)) { + updateFreezerExemptInstPkg(); } } } @@ -357,6 +362,7 @@ public final class CachedAppOptimizer { private boolean mFreezerOverride = false; @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT; + @VisibleForTesting volatile boolean mFreezerExemptInstPkg = DEFAULT_FREEZER_EXEMPT_INST_PKG; // Maps process ID to last compaction statistics for processes that we've fully compacted. Used // when evaluating throttles that we only consider for "full" compaction, so we don't store @@ -566,6 +572,15 @@ public final class CachedAppOptimizer { } } + /** + * Returns whether freezer exempts INSTALL_PACKAGES. + */ + public boolean freezerExemptInstPkg() { + synchronized (mPhenotypeFlagLock) { + return mUseFreezer && mFreezerExemptInstPkg; + } + } + @GuardedBy("mProcLock") void dump(PrintWriter pw) { pw.println("CachedAppOptimizer settings"); @@ -647,6 +662,7 @@ public final class CachedAppOptimizer { pw.println(" " + KEY_USE_FREEZER + "=" + mUseFreezer); pw.println(" " + KEY_FREEZER_STATSD_SAMPLE_RATE + "=" + mFreezerStatsdSampleRate); pw.println(" " + KEY_FREEZER_DEBOUNCE_TIMEOUT + "=" + mFreezerDebounceTimeout); + pw.println(" " + KEY_FREEZER_EXEMPT_INST_PKG + "=" + mFreezerExemptInstPkg); synchronized (mProcLock) { int size = mFrozenProcesses.size(); pw.println(" Apps frozen: " + size); @@ -1007,6 +1023,7 @@ public final class CachedAppOptimizer { KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) { mUseFreezer = isFreezerSupported(); updateFreezerDebounceTimeout(); + updateFreezerExemptInstPkg(); } else { mUseFreezer = false; } @@ -1194,6 +1211,15 @@ public final class CachedAppOptimizer { if (mFreezerDebounceTimeout < 0) { mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT; } + Slog.d(TAG_AM, "Freezer timeout set to " + mFreezerDebounceTimeout); + } + + @GuardedBy("mPhenotypeFlagLock") + private void updateFreezerExemptInstPkg() { + mFreezerExemptInstPkg = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT, + KEY_FREEZER_EXEMPT_INST_PKG, DEFAULT_FREEZER_EXEMPT_INST_PKG); + Slog.d(TAG_AM, "Freezer exemption set to " + mFreezerExemptInstPkg); } private boolean parseProcStateThrottle(String procStateThrottleString) { diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 60e6754f4b6f..ea3c8dc1fa90 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -107,21 +107,21 @@ option java_package com.android.server.am 30079 uc_dispatch_user_switch (oldUserId|1|5),(newUserId|1|5) 30080 uc_continue_user_switch (oldUserId|1|5),(newUserId|1|5) 30081 uc_send_user_broadcast (userId|1|5),(IntentAction|3) + # Tags below are used by SystemServiceManager - although it's technically part of am, these are # also user switch events and useful to be analyzed together with events above. -30082 ssm_user_starting (userId|1|5),(visible|1) +30082 ssm_user_starting (userId|1|5) 30083 ssm_user_switching (oldUserId|1|5),(newUserId|1|5) 30084 ssm_user_unlocking (userId|1|5) 30085 ssm_user_unlocked (userId|1|5) -30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1) +30086 ssm_user_stopping (userId|1|5) 30087 ssm_user_stopped (userId|1|5) 30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5) -30089 ssm_user_visibility_changed (userId|1|5),(visible|1) + +# Similarly, tags below are used by UserManagerService +30091 um_user_visibility_changed (userId|1|5),(visible|1) # Foreground service start/stop events. 30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3) 30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3) 30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3) - - - diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 42bfc4cc7130..ecea96e927e3 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1693,7 +1693,8 @@ public final class ProcessList { app.info.packageName); externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid, app.info.packageName); - if (pm.checkPermission(Manifest.permission.INSTALL_PACKAGES, + if (mService.isAppFreezerExemptInstPkg() + && pm.checkPermission(Manifest.permission.INSTALL_PACKAGES, app.info.packageName, userId) == PackageManager.PERMISSION_GRANTED) { Slog.i(TAG, app.info.packageName + " is exempt from freezer"); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 8049a3adeb5f..92133274c0cc 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -177,9 +177,7 @@ class UserController implements Handler.Callback { static final int START_USER_SWITCH_FG_MSG = 120; static final int COMPLETE_USER_SWITCH_MSG = 130; static final int USER_COMPLETED_EVENT_MSG = 140; - static final int USER_VISIBLE_MSG = 150; - - private static final int NO_ARG2 = 0; + static final int USER_VISIBILITY_CHANGED_MSG = 150; // Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if // the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not @@ -439,10 +437,13 @@ class UserController implements Handler.Callback { /** @see #getLastUserUnlockingUptime */ private volatile long mLastUserUnlockingUptime = 0; + // TODO(b/244333150) remove this array and let UserVisibilityMediator call the listeners + // directly, as that class should be responsible for all user visibility logic (for example, + // when the foreground user is switched out, its profiles also become invisible) /** * List of visible users (as defined by {@link UserManager#isUserVisible()}). * - * <p>It's only used to call {@link SystemServiceManager} when the visibility is changed upon + * <p>It's only used to call {@link UserManagerInternal} when the visibility is changed upon * the user starting or stopping. * * <p>Note: only the key is used, not the value. @@ -1096,10 +1097,7 @@ class UserController implements Handler.Callback { synchronized (mLock) { visibleBefore = mVisibleUsers.get(userId); if (visibleBefore) { - if (DEBUG_MU) { - Slogf.d(TAG, "Removing %d from mVisibleUsers", userId); - } - mVisibleUsers.delete(userId); + deleteVisibleUserLocked(userId); visibilityChanged = true; } else { visibilityChanged = false; @@ -1148,6 +1146,20 @@ class UserController implements Handler.Callback { } } + private void addVisibleUserLocked(@UserIdInt int userId) { + if (DEBUG_MU) { + Slogf.d(TAG, "adding %d to mVisibleUsers", userId); + } + mVisibleUsers.put(userId, true); + } + + private void deleteVisibleUserLocked(@UserIdInt int userId) { + if (DEBUG_MU) { + Slogf.d(TAG, "deleting %d from mVisibleUsers", userId); + } + mVisibleUsers.delete(userId); + } + private void finishUserStopping(final int userId, final UserState uss, final boolean allowDelayedLocking, final boolean visibilityChanged) { EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId); @@ -1166,7 +1178,10 @@ class UserController implements Handler.Callback { mInjector.batteryStatsServiceNoteEvent( BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH, Integer.toString(userId), userId); - mInjector.getSystemServiceManager().onUserStopping(userId, visibilityChanged); + mInjector.getSystemServiceManager().onUserStopping(userId); + if (visibilityChanged) { + mInjector.onUserVisibilityChanged(userId, /* visible= */ false); + } Runnable finishUserStoppedAsync = () -> mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking)); @@ -1635,7 +1650,12 @@ class UserController implements Handler.Callback { return false; } - mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId); + if (!userInfo.preCreated) { + // TODO(b/244644281): UMI should return whether the user is visible. And if fails, + // the user should not be in the mediator's started users structure + mInjector.getUserManagerInternal().assignUserToDisplay(userId, + userInfo.profileGroupId, foreground, displayId); + } // TODO(b/239982558): might need something similar for bg users on secondary display if (foreground && isUserSwitchUiEnabled()) { @@ -1687,12 +1707,23 @@ class UserController implements Handler.Callback { // Make sure the old user is no longer considering the display to be on. mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE); boolean userSwitchUiEnabled; + // TODO(b/244333150): temporary state until the callback logic is moved to + // UserVisibilityManager + int previousCurrentUserId; boolean notifyPreviousCurrentUserId; synchronized (mLock) { + previousCurrentUserId = mCurrentUserId; + notifyPreviousCurrentUserId = mVisibleUsers.get(previousCurrentUserId); + if (notifyPreviousCurrentUserId) { + deleteVisibleUserLocked(previousCurrentUserId); + } mCurrentUserId = userId; mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up userSwitchUiEnabled = mUserSwitchUiEnabled; } mInjector.updateUserConfiguration(); + // TODO(b/244644281): updateProfileRelatedCaches() is called on both if and else + // parts, ideally it should be moved outside, but for now it's not as there are many + // calls to external components here afterwards updateProfileRelatedCaches(); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); @@ -1705,6 +1736,11 @@ class UserController implements Handler.Callback { mInjector.getWindowManager().lockNow(null); } } + if (notifyPreviousCurrentUserId) { + mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, + previousCurrentUserId, 0)); + } + } else { final Integer currentUserIdInt = mCurrentUserId; updateProfileRelatedCaches(); @@ -1730,10 +1766,7 @@ class UserController implements Handler.Callback { && mInjector.getUserManagerInternal().isUserVisible(userId); if (visible) { synchronized (mLock) { - if (DEBUG_MU) { - Slogf.d(TAG, "Adding %d to mVisibleUsers", userId); - } - mVisibleUsers.put(userId, true); + addVisibleUserLocked(userId); } } @@ -1776,12 +1809,15 @@ class UserController implements Handler.Callback { mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, visible ? 1 : 0)); t.traceEnd(); - } else if (visible) { + } + + if (visible) { // User was already running and became visible (for example, when switching to a // user that was started in the background before), so it's necessary to explicitly // notify the services (while when the user starts from BOOTING, USER_START_MSG // takes care of that. - mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBLE_MSG, userId, NO_ARG2)); + mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, + visible ? 1 : 0)); } t.traceBegin("sendMessages"); @@ -2084,6 +2120,8 @@ class UserController implements Handler.Callback { } private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId); synchronized (mLock) { Slogf.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId); mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks; @@ -2093,6 +2131,7 @@ class UserController implements Handler.Callback { mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG, oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS); } + t.traceEnd(); } private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) { @@ -2150,6 +2189,8 @@ class UserController implements Handler.Callback { + " ms after dispatchUserSwitch."); } + TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(TAG); + t2.traceBegin("onUserSwitchingReply-" + name); curWaitingUserSwitchCallbacks.remove(name); // Continue switching if all callbacks have been notified and // user switching session is still valid @@ -2158,11 +2199,15 @@ class UserController implements Handler.Callback { == mCurWaitingUserSwitchCallbacks)) { sendContinueUserSwitchLU(uss, oldUserId, newUserId); } + t2.traceEnd(); } } }; + t.traceBegin("onUserSwitching-" + name); mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback); + t.traceEnd(); } catch (RemoteException e) { + // Ignore } } } else { @@ -2176,10 +2221,13 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("sendContinueUserSwitchLU-" + oldUserId + "-to-" + newUserId); mCurWaitingUserSwitchCallbacks = null; mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG); mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG, oldUserId, newUserId, uss)); + t.traceEnd(); } @VisibleForTesting @@ -2561,7 +2609,8 @@ class UserController implements Handler.Callback { if (!UserManager.isHeadlessSystemUserMode()) { // Don't need to call on HSUM because it will be called when the system user is // restarted on background - mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true); + mInjector.onUserStarting(UserHandle.USER_SYSTEM); + mInjector.onUserVisibilityChanged(UserHandle.USER_SYSTEM, /* visible= */ true); } } @@ -2573,12 +2622,12 @@ class UserController implements Handler.Callback { int userId = UserHandle.USER_SYSTEM; synchronized (mLock) { if (visible) { - mVisibleUsers.put(userId, true); + addVisibleUserLocked(userId); } else { - mVisibleUsers.delete(userId); + deleteVisibleUserLocked(userId); } } - mInjector.notifySystemUserVisibilityChanged(visible); + mInjector.onUserVisibilityChanged(userId, visible); t.traceEnd(); } @@ -3078,7 +3127,7 @@ class UserController implements Handler.Callback { logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER, USER_LIFECYCLE_EVENT_STATE_BEGIN); - mInjector.onUserStarting(/* userId= */ msg.arg1, /* visible= */ msg.arg2 == 1); + mInjector.onUserStarting(/* userId= */ msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_STARTING, USER_COMPLETED_EVENT_DELAY_MS); @@ -3159,8 +3208,9 @@ class UserController implements Handler.Callback { case COMPLETE_USER_SWITCH_MSG: completeUserSwitch(msg.arg1); break; - case USER_VISIBLE_MSG: - mInjector.getSystemServiceManager().onUserVisible(/* userId= */ msg.arg1); + case USER_VISIBILITY_CHANGED_MSG: + mInjector.onUserVisibilityChanged(/* userId= */ msg.arg1, + /* visible= */ msg.arg2 == 1); break; } return false; @@ -3692,12 +3742,12 @@ class UserController implements Handler.Callback { return UserManager.isUsersOnSecondaryDisplaysEnabled(); } - void onUserStarting(int userId, boolean visible) { - getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId, - visible); + void onUserStarting(@UserIdInt int userId) { + getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId); } - void notifySystemUserVisibilityChanged(boolean visible) { - getSystemServiceManager().onSystemUserVisibilityChanged(visible); + + void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) { + getUserManagerInternal().onUserVisibilityChanged(userId, visible); } } } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index efa2f25bf3e3..31d707da014f 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -883,6 +883,7 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onUserStarting(@NonNull TargetUser user) { + Slog.d(TAG, "Starting user " + user.getUserIdentifier()); mService.onUserStarting(user, Environment.getDataSystemDeDirectory(user.getUserIdentifier())); } @@ -1047,6 +1048,8 @@ public final class GameManagerService extends IGameManagerService.Stub { "com.android.server.app.GameManagerService"); if (!mSettings.containsKey(userId)) { + Slog.d(TAG, "Failed to set game mode for package " + packageName + + " as user " + userId + " is not started"); return; } GameManagerSettings userSettings = mSettings.get(userId); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index d35d193ebff2..78b697d13f7b 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1716,7 +1716,20 @@ public final class DisplayManagerService extends SystemService { final Point userPreferredResolution = mPersistentDataStore.getUserPreferredResolution(device); final float refreshRate = mPersistentDataStore.getUserPreferredRefreshRate(device); - if (userPreferredResolution == null && Float.isNaN(refreshRate)) { + // If value in persistentDataStore is null, preserving the mode from systemPreferredMode. + // This is required because in some devices, user-preferred mode was not stored in + // persistentDataStore, but was stored in a config which is returned through + // systemPreferredMode. + if ((userPreferredResolution == null && Float.isNaN(refreshRate)) + || (userPreferredResolution.equals(0, 0) && refreshRate == 0.0f)) { + Display.Mode systemPreferredMode = device.getSystemPreferredDisplayModeLocked(); + if (systemPreferredMode == null) { + return; + } + storeModeInPersistentDataStoreLocked( + display.getDisplayIdLocked(), systemPreferredMode.getPhysicalWidth(), + systemPreferredMode.getPhysicalHeight(), systemPreferredMode.getRefreshRate()); + device.setUserPreferredDisplayModeLocked(systemPreferredMode); return; } Display.Mode.Builder modeBuilder = new Display.Mode.Builder(); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index dcec0aaf9358..2669d217120f 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -140,7 +140,9 @@ import com.android.server.location.provider.StationaryThrottlingLocationProvider import com.android.server.location.provider.proxy.ProxyLocationProvider; import com.android.server.location.settings.LocationSettings; import com.android.server.location.settings.LocationUserSettings; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.LegacyPermissionManagerInternal; +import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -308,6 +310,10 @@ public class LocationManagerService extends ILocationManager.Stub implements permissionManagerInternal.setLocationExtraPackagesProvider( userId -> mContext.getResources().getStringArray( com.android.internal.R.array.config_locationExtraPackageNames)); + + // TODO(b/241604546): properly handle this callback + LocalServices.getService(UserManagerInternal.class).addUserVisibilityListener( + (u, v) -> Slogf.i(TAG, "onUserVisibilityChanged(): %d -> %b", u, v)); } @Nullable diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 56ec8e46291d..91558308e305 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -77,6 +77,23 @@ public abstract class UserManagerInternal { } /** + * Listener for {@link UserManager#isUserVisible() user visibility} changes. + */ + public interface UserVisibilityListener { + + /** + * Called when the {@link UserManager#isUserVisible() user visibility} changed. + * + * <p><b>Note:</b> this method is called independently of + * {@link com.android.server.SystemService} callbacks; for example, the call with + * {@code visible} {@code true} might be called before the + * {@link com.android.server.SystemService#onUserStarting(com.android.server.SystemService.TargetUser)} + * call. + */ + void onUserVisibilityChanged(@UserIdInt int userId, boolean visible); + } + + /** * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set * restrictions enforced by the user. * @@ -331,13 +348,18 @@ public abstract class UserManagerInternal { * <p>On most devices this call will be a no-op, but it will be used on devices that support * multiple users on multiple displays (like automotives with passenger displays). * + * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user + * is started) + * * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to * check it. In fact, one of the intended clients for this method is * {@code DisplayManagerService}, which will call it when a virtual display is created (another * client is {@code UserController}, which will call it when a user is started). - * */ - public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId); + // TODO(b/244644281): rename to assignUserToDisplayOnStart() and make sure it's called on boot + // as well + public abstract void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId, + boolean foreground, int displayId); /** * Unassigns a user from its current display. @@ -346,7 +368,7 @@ public abstract class UserManagerInternal { * multiple users on multiple displays (like automotives with passenger displays). * * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user - * is stopped) and {@code DisplayManagerService} (when a virtual display is destroyed). + * is stopped). */ public abstract void unassignUserFromDisplay(@UserIdInt int userId); @@ -390,4 +412,13 @@ public abstract class UserManagerInternal { * would make such call). */ public abstract @UserIdInt int getUserAssignedToDisplay(int displayId); + + /** Adds a {@link UserVisibilityListener}. */ + public abstract void addUserVisibilityListener(UserVisibilityListener listener); + + /** Removes a {@link UserVisibilityListener}. */ + public abstract void removeUserVisibilityListener(UserVisibilityListener listener); + + /** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */ + public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 0eff9e9a8e4d..d25566980fbb 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -96,6 +96,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.Slog; @@ -124,9 +125,11 @@ import com.android.server.BundleUtils; import com.android.server.LocalServices; import com.android.server.LockGuard; import com.android.server.SystemService; +import com.android.server.am.EventLogTags; import com.android.server.am.UserState; import com.android.server.pm.UserManagerInternal.UserLifecycleListener; import com.android.server.pm.UserManagerInternal.UserRestrictionsListener; +import com.android.server.pm.UserManagerInternal.UserVisibilityListener; import com.android.server.storage.DeviceStorageMonitorInternal; import com.android.server.utils.Slogf; import com.android.server.utils.TimingsTraceAndSlog; @@ -504,6 +507,10 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mUserLifecycleListeners") private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>(); + // TODO(b/244333150): temporary array, should belong to UserVisibilityMediator + @GuardedBy("mUserVisibilityListeners") + private final ArrayList<UserVisibilityListener> mUserVisibilityListeners = new ArrayList<>(); + private final LockPatternUtils mLockPatternUtils; private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK = @@ -626,7 +633,7 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mUserStates") private final WatchedUserStates mUserStates = new WatchedUserStates(); - private final UserVisibilityMediator mUserVisibilityMediator; + private final UserVisibilityMediator mUserVisibilityMediator = new UserVisibilityMediator(); private static UserManagerService sInstance; @@ -749,7 +756,6 @@ public class UserManagerService extends IUserManager.Stub { mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING); mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null; emulateSystemUserModeIfNeeded(); - mUserVisibilityMediator = new UserVisibilityMediator(this); } void systemReady() { @@ -1776,7 +1782,7 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public List<UserHandle> getVisibleUsers() { + public int[] getVisibleUsers() { if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) { throw new SecurityException("Caller needs MANAGE_USERS or INTERACT_ACROSS_USERS " + "permission to get list of visible users"); @@ -1784,18 +1790,19 @@ public class UserManagerService extends IUserManager.Stub { final long ident = Binder.clearCallingIdentity(); try { // TODO(b/2399825580): refactor into UserDisplayAssigner + IntArray visibleUsers; synchronized (mUsersLock) { int usersSize = mUsers.size(); - ArrayList<UserHandle> visibleUsers = new ArrayList<>(usersSize); + visibleUsers = new IntArray(); for (int i = 0; i < usersSize; i++) { UserInfo ui = mUsers.valueAt(i).info; if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id) && mUserVisibilityMediator.isUserVisible(ui.id)) { - visibleUsers.add(UserHandle.of(ui.id)); + visibleUsers.add(ui.id); } } - return visibleUsers; } + return visibleUsers.toArray(); } finally { Binder.restoreCallingIdentity(ident); } @@ -6147,7 +6154,7 @@ public class UserManagerService extends IUserManager.Stub { dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime); return; case "--visibility-mediator": - mUserVisibilityMediator.dump(pw); + mUserVisibilityMediator.dump(pw, args); return; } } @@ -6213,7 +6220,7 @@ public class UserManagerService extends IUserManager.Stub { } // synchronized (mPackagesLock) pw.println(); - mUserVisibilityMediator.dump(pw); + mUserVisibilityMediator.dump(pw, args); pw.println(); // Dump some capabilities @@ -6250,6 +6257,9 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mUserLifecycleListeners) { pw.println(" user lifecycle events: " + mUserLifecycleListeners.size()); } + synchronized (mUserVisibilityListeners) { + pw.println(" user visibility events: " + mUserVisibilityListeners.size()); + } // Dump UserTypes pw.println(); @@ -6789,13 +6799,16 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public void assignUserToDisplay(@UserIdInt int userId, int displayId) { - mUserVisibilityMediator.assignUserToDisplay(userId, displayId); + public void assignUserToDisplay(@UserIdInt int userId, @UserIdInt int profileGroupId, + boolean foreground, int displayId) { + mUserVisibilityMediator.startUser(userId, profileGroupId, foreground, displayId); + mUserVisibilityMediator.assignUserToDisplay(userId, profileGroupId, displayId); } @Override public void unassignUserFromDisplay(@UserIdInt int userId) { mUserVisibilityMediator.unassignUserFromDisplay(userId); + mUserVisibilityMediator.stopUser(userId); } @Override @@ -6817,8 +6830,39 @@ public class UserManagerService extends IUserManager.Stub { public @UserIdInt int getUserAssignedToDisplay(int displayId) { return mUserVisibilityMediator.getUserAssignedToDisplay(displayId); } + + @Override + public void addUserVisibilityListener(UserVisibilityListener listener) { + synchronized (mUserVisibilityListeners) { + mUserVisibilityListeners.add(listener); + } + } + + @Override + public void removeUserVisibilityListener(UserVisibilityListener listener) { + synchronized (mUserVisibilityListeners) { + mUserVisibilityListeners.remove(listener); + } + } + + @Override + public void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) { + EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0); + mHandler.post(() -> { + UserVisibilityListener[] listeners; + synchronized (mUserVisibilityListeners) { + listeners = new UserVisibilityListener[mUserVisibilityListeners.size()]; + mUserVisibilityListeners.toArray(listeners); + } + for (UserVisibilityListener listener : listeners) { + listener.onUserVisibilityChanged(userId, visible); + } + }); + } } // class LocalService + + /** * Check if user has restrictions * @param restriction restrictions to check diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index f725c486ec1f..bd81062b0ff1 100644 --- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java +++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java @@ -15,10 +15,18 @@ */ package com.android.server.pm; +import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID; +import static android.os.UserHandle.USER_NULL; +import static android.os.UserHandle.USER_SYSTEM; +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.os.UserHandle; import android.os.UserManager; +import android.util.DebugUtils; +import android.util.Dumpable; import android.util.IndentingPrintWriter; import android.util.SparseIntArray; import android.view.Display; @@ -29,6 +37,8 @@ import com.android.internal.util.Preconditions; import com.android.server.utils.Slogf; import java.io.PrintWriter; +import java.util.LinkedHashMap; +import java.util.Map; /** * Class responsible for deciding whether a user is visible (or visible for a given display). @@ -36,72 +46,146 @@ import java.io.PrintWriter; * <p>This class is thread safe. */ // TODO(b/244644281): improve javadoc (for example, explain all cases / modes) -public final class UserVisibilityMediator { +public final class UserVisibilityMediator implements Dumpable { private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE private static final String TAG = UserVisibilityMediator.class.getSimpleName(); - private final Object mLock = new Object(); + private static final String PREFIX_START_USER_RESULT = "START_USER_"; + + // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices + @VisibleForTesting + static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM; + + public static final int START_USER_RESULT_SUCCESS_VISIBLE = 1; + public static final int START_USER_RESULT_SUCCESS_INVISIBLE = 2; + public static final int START_USER_RESULT_FAILURE = -1; - // TODO(b/244644281): should not depend on service, but keep its own internal state (like - // current user and profile groups), but it is initially as the code was just moved from UMS - // "as is". Similarly, it shouldn't need to pass the SparseIntArray on constructor (which was - // added to UMS for testing purposes) - private final UserManagerService mService; + @IntDef(flag = false, prefix = {PREFIX_START_USER_RESULT}, value = { + START_USER_RESULT_SUCCESS_VISIBLE, + START_USER_RESULT_SUCCESS_INVISIBLE, + START_USER_RESULT_FAILURE + }) + public @interface StartUserResult {} + + private final Object mLock = new Object(); private final boolean mUsersOnSecondaryDisplaysEnabled; + @UserIdInt + @GuardedBy("mLock") + private int mCurrentUserId = INITIAL_CURRENT_USER_ID; + @Nullable @GuardedBy("mLock") - private final SparseIntArray mUsersOnSecondaryDisplays; + private final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray(); - UserVisibilityMediator(UserManagerService service) { - this(service, UserManager.isUsersOnSecondaryDisplaysEnabled(), - /* usersOnSecondaryDisplays= */ null); + /** + * Mapping from each started user to its profile group. + */ + @GuardedBy("mLock") + private final SparseIntArray mStartedProfileGroupIds = new SparseIntArray(); + + UserVisibilityMediator() { + this(UserManager.isUsersOnSecondaryDisplaysEnabled()); } @VisibleForTesting - UserVisibilityMediator(UserManagerService service, boolean usersOnSecondaryDisplaysEnabled, - @Nullable SparseIntArray usersOnSecondaryDisplays) { - mService = service; + UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled) { mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled; - if (mUsersOnSecondaryDisplaysEnabled) { - mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null - ? new SparseIntArray() // default behavior - : usersOnSecondaryDisplays; // passed by unit test - } else { - mUsersOnSecondaryDisplays = null; + } + + /** + * TODO(b/244644281): merge with assignUserToDisplay() or add javadoc. + */ + public @StartUserResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId, + boolean foreground, int displayId) { + int actualProfileGroupId = profileGroupId == NO_PROFILE_GROUP_ID + ? userId + : profileGroupId; + if (DBG) { + Slogf.d(TAG, "startUser(%d, %d, %b, %d): actualProfileGroupId=%d", + userId, profileGroupId, foreground, displayId, actualProfileGroupId); + } + if (foreground && displayId != DEFAULT_DISPLAY) { + Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start foreground user on " + + "secondary display", userId, actualProfileGroupId, foreground, displayId); + return START_USER_RESULT_FAILURE; + } + + int visibility; + synchronized (mLock) { + if (isProfile(userId, actualProfileGroupId)) { + if (displayId != DEFAULT_DISPLAY) { + Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user on " + + "secondary display", userId, actualProfileGroupId, foreground, + displayId); + return START_USER_RESULT_FAILURE; + } + if (foreground) { + Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in " + + "foreground", userId, actualProfileGroupId, foreground, + displayId); + return START_USER_RESULT_FAILURE; + } else { + boolean isParentRunning = mStartedProfileGroupIds + .get(actualProfileGroupId) == actualProfileGroupId; + if (DBG) { + Slogf.d(TAG, "profile parent running: %b", isParentRunning); + } + visibility = isParentRunning + ? START_USER_RESULT_SUCCESS_VISIBLE + : START_USER_RESULT_SUCCESS_INVISIBLE; + } + } else if (foreground) { + mCurrentUserId = userId; + visibility = START_USER_RESULT_SUCCESS_VISIBLE; + } else { + visibility = START_USER_RESULT_SUCCESS_INVISIBLE; + } + if (DBG) { + Slogf.d(TAG, "adding user / profile mapping (%d -> %d) and returning %s", + userId, actualProfileGroupId, startUserResultToString(visibility)); + } + mStartedProfileGroupIds.put(userId, actualProfileGroupId); + } + return visibility; + } + + /** + * TODO(b/244644281): merge with unassignUserFromDisplay() or add javadoc (and unit tests) + */ + public void stopUser(@UserIdInt int userId) { + if (DBG) { + Slogf.d(TAG, "stopUser(%d)", userId); + } + synchronized (mLock) { + mStartedProfileGroupIds.delete(userId); } } /** * See {@link UserManagerInternal#assignUserToDisplay(int, int)}. */ - public void assignUserToDisplay(int userId, int displayId) { + public void assignUserToDisplay(int userId, int profileGroupId, int displayId) { if (DBG) { - Slogf.d(TAG, "assignUserToDisplay(%d, %d)", userId, displayId); + Slogf.d(TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplaysEnabled=%b", + userId, displayId, mUsersOnSecondaryDisplaysEnabled); } - // NOTE: Using Boolean instead of boolean as it will be re-used below - Boolean isProfile = null; - if (displayId == Display.DEFAULT_DISPLAY) { - if (mUsersOnSecondaryDisplaysEnabled) { - // Profiles are only supported in the default display, but it cannot return yet - // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY - // (this is done indirectly below when it checks that the profile parent is the - // current user, as the current user is always assigned to the DEFAULT_DISPLAY). - isProfile = isProfileUnchecked(userId); - } - if (isProfile == null || !isProfile) { - // Don't need to do anything because methods (such as isUserVisible()) already - // know that the current user (and their profiles) is assigned to the default - // display. - if (DBG) { - Slogf.d(TAG, "ignoring on default display"); - } - return; + if (displayId == DEFAULT_DISPLAY + && (!mUsersOnSecondaryDisplaysEnabled || !isProfile(userId, profileGroupId))) { + // Don't need to do anything because methods (such as isUserVisible()) already + // know that the current user (and their profiles) is assigned to the default display. + // But on MUMD devices, it profiles are only supported in the default display, so it + // cannot return yet as it needs to check if the parent is also assigned to the + // DEFAULT_DISPLAY (this is done indirectly below when it checks that the profile parent + // is the current user, as the current user is always assigned to the DEFAULT_DISPLAY). + if (DBG) { + Slogf.d(TAG, "ignoring on default display"); } + return; } if (!mUsersOnSecondaryDisplaysEnabled) { @@ -119,24 +203,21 @@ public final class UserVisibilityMediator { Preconditions.checkArgument(userId != currentUserId, "Cannot assign current user (%d) to other displays", currentUserId); - if (isProfile == null) { - isProfile = isProfileUnchecked(userId); - } - synchronized (mLock) { - if (isProfile) { - // Profile can only start in the same display as parent. And for simplicity, - // that display must be the DEFAULT_DISPLAY. - Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY, - "Profile user can only be started in the default display"); - int parentUserId = getProfileParentId(userId); - Preconditions.checkArgument(parentUserId == currentUserId, - "Only profile of current user can be assigned to a display"); - if (DBG) { - Slogf.d(TAG, "Ignoring profile user %d on default display", userId); - } - return; + if (isProfile(userId, profileGroupId)) { + // Profile can only start in the same display as parent. And for simplicity, + // that display must be the DEFAULT_DISPLAY. + Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY, + "Profile user can only be started in the default display"); + int parentUserId = getStartedProfileGroupId(userId); + Preconditions.checkArgument(parentUserId == currentUserId, + "Only profile of current user can be assigned to a display"); + if (DBG) { + Slogf.d(TAG, "Ignoring profile user %d on default display", userId); } + return; + } + synchronized (mLock) { // Check if display is available for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) { int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i); @@ -289,7 +370,7 @@ public final class UserVisibilityMediator { continue; } int userId = mUsersOnSecondaryDisplays.keyAt(i); - if (!isProfileUnchecked(userId)) { + if (!isStartedProfile(userId)) { return userId; } else if (DBG) { Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's " @@ -307,23 +388,42 @@ public final class UserVisibilityMediator { } private void dump(IndentingPrintWriter ipw) { - ipw.println("UserVisibilityManager"); + ipw.println("UserVisibilityMediator"); ipw.increaseIndent(); - ipw.print("Supports users on secondary displays: "); - ipw.println(mUsersOnSecondaryDisplaysEnabled); + synchronized (mLock) { + ipw.print("Current user id: "); + ipw.println(mCurrentUserId); + + ipw.print("Number of started user / profile group mappings: "); + ipw.println(mStartedProfileGroupIds.size()); + if (mStartedProfileGroupIds.size() > 0) { + ipw.increaseIndent(); + for (int i = 0; i < mStartedProfileGroupIds.size(); i++) { + ipw.print("User #"); + ipw.print(mStartedProfileGroupIds.keyAt(i)); + ipw.print(" -> profile #"); + ipw.println(mStartedProfileGroupIds.valueAt(i)); + } + ipw.decreaseIndent(); + } - if (mUsersOnSecondaryDisplaysEnabled) { - ipw.print("Users on secondary displays: "); - synchronized (mLock) { - ipw.println(mUsersOnSecondaryDisplays); + ipw.print("Supports users on secondary displays: "); + ipw.println(mUsersOnSecondaryDisplaysEnabled); + + if (mUsersOnSecondaryDisplaysEnabled) { + ipw.print("Users on secondary displays: "); + synchronized (mLock) { + ipw.println(mUsersOnSecondaryDisplays); + } } } ipw.decreaseIndent(); } - void dump(PrintWriter pw) { + @Override + public void dump(PrintWriter pw, String[] args) { if (pw instanceof IndentingPrintWriter) { dump((IndentingPrintWriter) pw); return; @@ -331,20 +431,70 @@ public final class UserVisibilityMediator { dump(new IndentingPrintWriter(pw)); } - // TODO(b/244644281): remove methods below once this class caches that state - private @UserIdInt int getCurrentUserId() { - return mService.getCurrentUserId(); + @VisibleForTesting + Map<Integer, Integer> getUsersOnSecondaryDisplays() { + Map<Integer, Integer> map; + synchronized (mLock) { + int size = mUsersOnSecondaryDisplays.size(); + map = new LinkedHashMap<>(size); + for (int i = 0; i < size; i++) { + map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i)); + } + } + Slogf.v(TAG, "getUsersOnSecondaryDisplays(): returning %s", map); + return map; + } + + /** + * Gets the user-friendly representation of the {@code result}. + */ + public static String startUserResultToString(@StartUserResult int result) { + return DebugUtils.constantToString(UserVisibilityMediator.class, PREFIX_START_USER_RESULT, + result); } - private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) { - return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId); + // TODO(b/244644281): methods below are needed because some APIs use the current users (full and + // profiles) state to decide whether a user is visible or not. If we decide to always store that + // info into intermediate maps, we should remove them. + + @VisibleForTesting + @UserIdInt int getCurrentUserId() { + synchronized (mLock) { + return mCurrentUserId; + } } - private boolean isProfileUnchecked(@UserIdInt int userId) { - return mService.isProfileUnchecked(userId); + @VisibleForTesting + boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) { + synchronized (mLock) { + // Special case as NO_PROFILE_GROUP_ID == USER_NULL + if (userId == USER_NULL || mCurrentUserId == USER_NULL) { + return false; + } + if (mCurrentUserId == userId) { + return true; + } + return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID) == mCurrentUserId; + } } - private @UserIdInt int getProfileParentId(@UserIdInt int userId) { - return mService.getProfileParentId(userId); + private static boolean isProfile(@UserIdInt int userId, @UserIdInt int profileGroupId) { + return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId; + } + + @VisibleForTesting + boolean isStartedProfile(@UserIdInt int userId) { + int profileGroupId; + synchronized (mLock) { + profileGroupId = mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); + } + return isProfile(userId, profileGroupId); + } + + @VisibleForTesting + @UserIdInt int getStartedProfileGroupId(@UserIdInt int userId) { + synchronized (mLock) { + return mStartedProfileGroupIds.get(userId, NO_PROFILE_GROUP_ID); + } } } diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java index 8582f5424321..2d76c5092b92 100644 --- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java +++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java @@ -127,7 +127,7 @@ public class SideFpsEventHandler implements View.OnClickListener { */ public void notifyPowerPressed() { Log.i(TAG, "notifyPowerPressed"); - if (mFingerprintManager == null) { + if (mFingerprintManager == null && mSideFpsEventHandlerReady.get()) { mFingerprintManager = mContext.getSystemService(FingerprintManager.class); } if (mFingerprintManager == null) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 7dc4f9782e70..b8cd8d9f3d71 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1157,6 +1157,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "WallpaperService is not connected yet"); return; } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent); if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken); mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId, null /* options */); @@ -1173,6 +1175,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub false /* fromUser */, wallpaper, null /* reply */); } } + t.traceEnd(); } void disconnectLocked() { @@ -1322,6 +1325,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @Override public void onServiceConnected(ComponentName name, IBinder service) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("WPMS.onServiceConnected-" + name); synchronized (mLock) { if (mWallpaper.connection == this) { mService = IWallpaperService.Stub.asInterface(service); @@ -1338,6 +1343,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mContext.getMainThreadHandler().removeCallbacks(mDisconnectRunnable); } } + t.traceEnd(); } @Override @@ -1545,6 +1551,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void engineShown(IWallpaperEngine engine) { synchronized (mLock) { if (mReply != null) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("WPMS.mReply.sendResult"); final long ident = Binder.clearCallingIdentity(); try { mReply.sendResult(null); @@ -1553,6 +1561,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } finally { Binder.restoreCallingIdentity(ident); } + t.traceEnd(); mReply = null; } } @@ -3058,6 +3067,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return true; } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("WPMS.bindWallpaperComponentLocked-" + componentName); try { if (componentName == null) { componentName = mDefaultWallpaperComponent; @@ -3190,6 +3201,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } Slog.w(TAG, msg); return false; + } finally { + t.traceEnd(); } return true; } @@ -3234,7 +3247,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("WPMS.attachServiceLocked"); conn.forEachDisplayConnector(connector-> connector.connectLocked(conn, wallpaper)); + t.traceEnd(); } private void notifyCallbacksLocked(WallpaperData wallpaper) { @@ -3360,6 +3376,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } void saveSettingsLocked(int userId) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); + t.traceBegin("WPMS.saveSettingsLocked-" + userId); JournaledFile journal = makeJournaledFile(userId); FileOutputStream fstream = null; try { @@ -3388,6 +3406,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub IoUtils.closeQuietly(fstream); journal.rollback(); } + t.traceEnd(); } diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index d0c381e9debc..21b241a0d117 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -56,8 +56,11 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { private static final int SURFACE_FLINGER_CALLBACK_WINDOWS_STABLE_TIMES_MS = 35; // To avoid the surface flinger callbacks always comes within in 2 frames, then no windows // are reported to the A11y framework, and the animation duration time is 500ms, so setting - // this value as the max timeout value to force computing changed windows. - private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 500; + // this value as the max timeout value to force computing changed windows. However, since + // UiAutomator waits 500ms to determine that things are idle. Since we aren't actually idle, + // we need to reduce the timeout here a little so that we can deliver an updated state before + // UiAutomator reports idle based-on stale information. + private static final int WINDOWS_CHANGED_NOTIFICATION_MAX_DURATION_TIMES_MS = 450; private static final float[] sTempFloats = new float[9]; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 59f37c27f637..7386a19009e0 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -86,6 +86,7 @@ import android.window.TransitionInfo; import com.android.internal.app.AssistUtils; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.pm.KnownPackages; @@ -454,6 +455,39 @@ class ActivityClientController extends IActivityClientController.Stub { finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY; if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY || (finishWithRootActivity && r == rootR)) { + ActivityRecord topActivity = + r.getTask().getTopNonFinishingActivity(); + boolean passesAsmChecks = topActivity != null + && topActivity.getUid() == r.getUid(); + if (!passesAsmChecks) { + Slog.i(TAG, "Finishing task from background. r: " + r); + FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED, + /* caller_uid */ + r.getUid(), + /* caller_activity_class_name */ + r.info.name, + /* target_task_top_activity_uid */ + topActivity == null ? -1 : topActivity.getUid(), + /* target_task_top_activity_class_name */ + topActivity == null ? null : topActivity.info.name, + /* target_task_is_different */ + false, + /* target_activity_uid */ + -1, + /* target_activity_class_name */ + null, + /* target_intent_action */ + null, + /* target_intent_flags */ + 0, + /* action */ + FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK, + /* version */ + 1, + /* multi_window */ + false + ); + } // If requested, remove the task that is associated to this activity only if it // was the root activity in the task. The result code and data is ignored // because we don't support returning them across task boundaries. Also, to diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0eee8a329a1e..2a3946f77847 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -9204,10 +9204,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + " preserveWindow=" + preserveWindow); if (andResume) { EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this), - task.mTaskId, shortComponentName); + task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags)); } else { EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this), - task.mTaskId, shortComponentName); + task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags)); } startFreezingScreenLocked(0); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index dc047aaab31c..e6d94920c00e 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1663,7 +1663,8 @@ class ActivityStarter { } final Task startedTask = mStartActivity.getTask(); if (newTask) { - EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId); + EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId, + startedTask.getRootTaskId(), startedTask.getDisplayId()); } mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask); @@ -1856,6 +1857,11 @@ class ActivityStarter { + " from background: " + mSourceRecord + ". New task: " + newTask); boolean newOrEmptyTask = newTask || (targetTopActivity == null); + int action = newTask + ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK + : (mSourceRecord.getTask().equals(targetTask) + ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK + : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK); FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED, /* caller_uid */ callerUid, @@ -1874,7 +1880,14 @@ class ActivityStarter { /* target_intent_action */ r.intent.getAction(), /* target_intent_flags */ - r.intent.getFlags() + r.intent.getFlags(), + /* action */ + action, + /* version */ + 1, + /* multi_window */ + targetTask != null && !targetTask.equals(mSourceRecord.getTask()) + && targetTask.isVisible() ); } } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 901153300169..bd22b32742a1 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -215,9 +215,20 @@ public class AppTransitionController { mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded( mDisplayContent.mOpeningApps); + ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps; + ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps; + if (mDisplayContent.mAtmService.mBackNavigationController.isWaitBackTransition()) { + tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps); + tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps); + if (mDisplayContent.mAtmService.mBackNavigationController + .removeIfContainsBackAnimationTargets(tmpOpenApps, tmpCloseApps)) { + mDisplayContent.mAtmService.mBackNavigationController.clearBackAnimations(null); + } + } + @TransitionOldType final int transit = getTransitCompatType( - mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps, - mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers, + mDisplayContent.mAppTransition, tmpOpenApps, + tmpCloseApps, mDisplayContent.mChangingContainers, mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(), mDisplayContent.mSkipAppTransitionAnimation); mDisplayContent.mSkipAppTransitionAnimation = false; @@ -225,22 +236,21 @@ public class AppTransitionController { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "handleAppTransitionReady: displayId=%d appTransition={%s}" + " openingApps=[%s] closingApps=[%s] transit=%s", - mDisplayContent.mDisplayId, appTransition.toString(), mDisplayContent.mOpeningApps, - mDisplayContent.mClosingApps, AppTransition.appTransitionOldToString(transit)); + mDisplayContent.mDisplayId, appTransition.toString(), tmpOpenApps, + tmpCloseApps, AppTransition.appTransitionOldToString(transit)); // Find the layout params of the top-most application window in the tokens, which is // what will control the animation theme. If all closing windows are obscured, then there is // no need to do an animation. This is the case, for example, when this transition is being // done behind a dream window. - final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps, - mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers); + final ArraySet<Integer> activityTypes = collectActivityTypes(tmpOpenApps, + tmpCloseApps, mDisplayContent.mChangingContainers); final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes, - mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, - mDisplayContent.mChangingContainers); + tmpOpenApps, tmpCloseApps, mDisplayContent.mChangingContainers); final ActivityRecord topOpeningApp = - getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */); + getTopApp(tmpOpenApps, false /* ignoreHidden */); final ActivityRecord topClosingApp = - getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */); + getTopApp(tmpCloseApps, false /* ignoreHidden */); final ActivityRecord topChangingApp = getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */); final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity); @@ -258,8 +268,7 @@ public class AppTransitionController { final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); try { - applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, - animLp, voiceInteraction); + applyAnimations(tmpOpenApps, tmpCloseApps, transit, animLp, voiceInteraction); handleClosingApps(); handleOpeningApps(); handleChangingApps(transit); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 6ff2bb30cc0c..9398bbec4a63 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; @@ -32,6 +34,7 @@ import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemProperties; +import android.util.ArraySet; import android.util.Slog; import android.view.IWindowFocusObserver; import android.view.RemoteAnimationTarget; @@ -41,7 +44,6 @@ import android.window.BackNavigationInfo; import android.window.IBackAnimationFinishedCallback; import android.window.OnBackInvokedCallbackInfo; import android.window.TaskSnapshot; -import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -60,9 +62,9 @@ class BackNavigationController { private boolean mShowWallpaper; private Runnable mPendingAnimation; - // TODO (b/241808055) Find a appropriate time to remove during refactor - // Execute back animation with legacy transition system. Temporary flag for easier debugging. - static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions; + private final AnimationTargets mAnimationTargets = new AnimationTargets(); + private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>(); + private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>(); /** * true if the back predictability feature is enabled @@ -284,7 +286,6 @@ class BackNavigationController { } if (prepareAnimation) { - infoBuilder.setDepartingWCT(toWindowContainerToken(currentTask)); prepareAnimationIfNeeded(currentTask, prevTask, prevActivity, removedWindowContainer, backType, adapter); } @@ -303,11 +304,233 @@ class BackNavigationController { return infoBuilder.build(); } - private static WindowContainerToken toWindowContainerToken(WindowContainer<?> windowContainer) { - if (windowContainer == null || windowContainer.mRemoteToken == null) { - return null; + boolean isWaitBackTransition() { + return mAnimationTargets.mComposed && mAnimationTargets.mWaitTransition; + } + + // For legacy transition. + /** + * Once we find the transition targets match back animation targets, remove the target from + * list, so that transition won't count them in since the close animation was finished. + * + * @return {@code true} if the participants of this transition was animated by back gesture + * animations, and shouldn't join next transition. + */ + boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps, + ArraySet<ActivityRecord> closeApps) { + if (!isWaitBackTransition()) { + return false; + } + mTmpCloseApps.addAll(closeApps); + boolean result = false; + // Note: TmpOpenApps is empty. Unlike shell transition, the open apps will be removed from + // mOpeningApps if there is no visibility change. + if (mAnimationTargets.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps)) { + // remove close target from close list, open target from open list; + // but the open target can be in close list. + for (int i = openApps.size() - 1; i >= 0; --i) { + final ActivityRecord ar = openApps.valueAt(i); + if (mAnimationTargets.isTarget(ar, true /* open */)) { + openApps.removeAt(i); + } + } + for (int i = closeApps.size() - 1; i >= 0; --i) { + final ActivityRecord ar = closeApps.valueAt(i); + if (mAnimationTargets.isTarget(ar, false /* open */)) { + closeApps.removeAt(i); + } + } + result = true; + } + mTmpCloseApps.clear(); + return result; + } + + // For shell transition + /** + * Check whether the transition targets was animated by back gesture animation. + * Because the opening target could request to do other stuff at onResume, so it could become + * close target for a transition. So the condition here is + * The closing target should only exist in close list, but the opening target can be either in + * open or close list. + * @return {@code true} if the participants of this transition was animated by back gesture + * animations, and shouldn't join next transition. + */ + boolean containsBackAnimationTargets(Transition transition) { + if (!mAnimationTargets.mComposed + || (transition.mType != TRANSIT_CLOSE && transition.mType != TRANSIT_TO_BACK)) { + return false; + } + final ArraySet<WindowContainer> targets = transition.mParticipants; + for (int i = targets.size() - 1; i >= 0; --i) { + final WindowContainer wc = targets.valueAt(i); + if (wc.asActivityRecord() == null && wc.asTask() == null) { + continue; + } + // WC can be visible due to setLaunchBehind + if (wc.isVisibleRequested()) { + mTmpOpenApps.add(wc); + } else { + mTmpCloseApps.add(wc); + } + } + final boolean result = mAnimationTargets.containsBackAnimationTargets( + mTmpOpenApps, mTmpCloseApps); + mTmpOpenApps.clear(); + mTmpCloseApps.clear(); + return result; + } + + boolean isMonitorTransitionTarget(WindowContainer wc) { + if (!mAnimationTargets.mComposed || !mAnimationTargets.mWaitTransition) { + return false; + } + return mAnimationTargets.isTarget(wc, wc.isVisibleRequested() /* open */); + } + + /** + * Cleanup animation, this can either happen when transition ready or finish. + * @param cleanupTransaction The transaction which the caller want to apply the internal + * cleanup together. + */ + void clearBackAnimations(SurfaceControl.Transaction cleanupTransaction) { + mAnimationTargets.clearBackAnimateTarget(cleanupTransaction); + } + + /** + * TODO: Animation composer + * prepareAnimationIfNeeded will become too complicated in order to support + * ActivityRecord/WindowState, using a factory class to create the RemoteAnimationTargets for + * different scenario. + */ + private static class AnimationTargets { + ActivityRecord mCloseTarget; // Must be activity + WindowContainer mOpenTarget; // Can be activity or task if activity was removed + private boolean mComposed; + private boolean mWaitTransition; + private int mSwitchType = UNKNOWN; + private SurfaceControl.Transaction mFinishedTransaction; + + private static final int UNKNOWN = 0; + private static final int TASK_SWITCH = 1; + private static final int ACTIVITY_SWITCH = 2; + + void reset(@NonNull WindowContainer close, @NonNull WindowContainer open) { + clearBackAnimateTarget(null); + if (close.asActivityRecord() != null && open.asActivityRecord() != null + && (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) { + mSwitchType = ACTIVITY_SWITCH; + mCloseTarget = close.asActivityRecord(); + } else if (close.asTask() != null && open.asTask() != null + && close.asTask() != open.asTask()) { + mSwitchType = TASK_SWITCH; + mCloseTarget = close.asTask().getTopNonFinishingActivity(); + } else { + mSwitchType = UNKNOWN; + return; + } + + mOpenTarget = open; + mComposed = false; + mWaitTransition = false; + } + + void composeNewAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open) { + reset(close, open); + if (mSwitchType == UNKNOWN || mComposed || mCloseTarget == mOpenTarget + || mCloseTarget == null || mOpenTarget == null) { + return; + } + mComposed = true; + mWaitTransition = false; + } + + boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) { + for (int i = wcs.size() - 1; i >= 0; --i) { + if (isTarget(wcs.get(i), open)) { + return true; + } + } + return wcs.isEmpty(); + } + + boolean isTarget(WindowContainer wc, boolean open) { + if (open) { + return wc == mOpenTarget || mOpenTarget.hasChild(wc); + } + if (mSwitchType == TASK_SWITCH) { + return wc == mCloseTarget + || (wc.asTask() != null && wc.hasChild(mCloseTarget)); + } else if (mSwitchType == ACTIVITY_SWITCH) { + return wc == mCloseTarget; + } + return false; + } + + boolean setFinishTransaction(SurfaceControl.Transaction finishTransaction) { + if (!mComposed) { + return false; + } + mFinishedTransaction = finishTransaction; + return true; + } + + void finishPresentAnimations(SurfaceControl.Transaction t) { + if (!mComposed) { + return; + } + final SurfaceControl.Transaction pt = t != null ? t + : mOpenTarget.getPendingTransaction(); + if (mFinishedTransaction != null) { + pt.merge(mFinishedTransaction); + mFinishedTransaction = null; + } + } + + void clearBackAnimateTarget(SurfaceControl.Transaction cleanupTransaction) { + finishPresentAnimations(cleanupTransaction); + mCloseTarget = null; + mOpenTarget = null; + mComposed = false; + mWaitTransition = false; + mSwitchType = UNKNOWN; + if (mFinishedTransaction != null) { + Slog.w(TAG, "Clear back animation, found un-processed finished transaction"); + if (cleanupTransaction != null) { + cleanupTransaction.merge(mFinishedTransaction); + } else { + mFinishedTransaction.apply(); + } + mFinishedTransaction = null; + } + } + + // The close target must in close list + // The open target can either in close or open list + boolean containsBackAnimationTargets(ArrayList<WindowContainer> openApps, + ArrayList<WindowContainer> closeApps) { + return containTarget(closeApps, false /* open */) + && (containTarget(openApps, true /* open */) + || containTarget(openApps, false /* open */)); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(128); + sb.append("AnimationTargets{"); + sb.append(" mOpenTarget= "); + sb.append(mOpenTarget); + sb.append(" mCloseTarget= "); + sb.append(mCloseTarget); + sb.append(" mSwitchType= "); + sb.append(mSwitchType); + sb.append(" mComposed= "); + sb.append(mComposed); + sb.append(" mWaitTransition= "); + sb.append(mWaitTransition); + sb.append('}'); + return sb.toString(); } - return windowContainer.mRemoteToken.toWindowContainerToken(); } private void prepareAnimationIfNeeded(Task currentTask, @@ -373,10 +596,6 @@ class BackNavigationController { leashes.add(screenshotSurface); } } else if (prevTask != null) { - if (!ENABLE_SHELL_TRANSITIONS) { - // Special handling for preventing next transition. - currentTask.mBackGestureStarted = true; - } prevActivity = prevTask.getTopNonFinishingActivity(); if (prevActivity != null) { // Make previous task show from behind by marking its top activity as visible @@ -417,35 +636,36 @@ class BackNavigationController { for (SurfaceControl sc: leashes) { finishedTransaction.remove(sc); } - synchronized (mWindowManagerService.mGlobalLock) { - if (ENABLE_SHELL_TRANSITIONS) { - if (!triggerBack) { - if (!needsScreenshot(backType)) { - restoreLaunchBehind(finalPrevActivity); - } + if (triggerBack) { + final SurfaceControl surfaceControl = + removedWindowContainer.getSurfaceControl(); + if (surfaceControl != null && surfaceControl.isValid()) { + // The animation is finish and start waiting for transition, + // hide the task surface before it re-parented to avoid flicker. + finishedTransaction.hide(surfaceControl); } + } else if (!needsScreenshot(backType)) { + restoreLaunchBehind(finalPrevActivity); + } + if (!mAnimationTargets.setFinishTransaction(finishedTransaction)) { + finishedTransaction.apply(); + } + if (!triggerBack) { + mAnimationTargets.clearBackAnimateTarget(null); } else { - if (triggerBack) { - final SurfaceControl surfaceControl = - removedWindowContainer.getSurfaceControl(); - if (surfaceControl != null && surfaceControl.isValid()) { - // When going back to home, hide the task surface before it - // re-parented to avoid flicker. - finishedTransaction.hide(surfaceControl); - } - } else { - currentTask.mBackGestureStarted = false; - if (!needsScreenshot(backType)) { - restoreLaunchBehind(finalPrevActivity); - } - } + mAnimationTargets.mWaitTransition = true; } } - finishedTransaction.apply(); + // TODO Add timeout monitor if transition didn't happen } }; - + if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) { + mAnimationTargets.composeNewAnimations(removedWindowContainer, prevActivity); + } else if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME + || backType == BackNavigationInfo.TYPE_CROSS_TASK) { + mAnimationTargets.composeNewAnimations(removedWindowContainer, prevTask); + } scheduleAnimationLocked(backType, targets, adapter, callback); } diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags index 1e5a219e5e52..d94bf4bbbfa4 100644 --- a/services/core/java/com/android/server/wm/EventLogTags.logtags +++ b/services/core/java/com/android/server/wm/EventLogTags.logtags @@ -8,11 +8,11 @@ option java_package com.android.server.wm # An activity is being finished: 30001 wm_finish_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3) # A task is being brought to the front of the screen: -30002 wm_task_to_front (User|1|5),(Task|1|5) +30002 wm_task_to_front (User|1|5),(Task|1|5),(Display Id|1|5) # An existing activity is being given a new intent: 30003 wm_new_intent (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5) # A new task is being created: -30004 wm_create_task (User|1|5),(Task ID|1|5) +30004 wm_create_task (User|1|5),(Task ID|1|5),(Root Task ID|1|5),(Display Id|1|5) # A new activity is being created in an existing task: 30005 wm_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5) # An activity has been resumed into the foreground but was not already running: @@ -32,9 +32,9 @@ option java_package com.android.server.wm # An activity is being destroyed: 30018 wm_destroy_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3) # An activity has been relaunched, resumed, and is now in the foreground: -30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3) +30019 wm_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3) # An activity has been relaunched: -30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3) +30020 wm_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(config mask|3) # Activity set to resumed 30043 wm_set_resumed_activity (User|1|5),(Component Name|3),(Reason|3) @@ -45,9 +45,6 @@ option java_package com.android.server.wm # Attempting to stop an activity 30048 wm_stop_activity (User|1|5),(Token|1|5),(Component Name|3) -# The task is being removed from its parent task -30061 wm_remove_task (Task ID|1|5), (Root Task ID|1|5) - # An activity been add into stopping list 30066 wm_add_to_stopping (User|1|5),(Token|1|5),(Component Name|3),(Reason|3) @@ -57,11 +54,11 @@ option java_package com.android.server.wm # Out of memory for surfaces. 31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3) # Task created. -31001 wm_task_created (TaskId|1|5),(RootTaskId|1|5) +31001 wm_task_created (TaskId|1|5) # Task moved to top (1) or bottom (0). -31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1) +31002 wm_task_moved (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(ToTop|1),(Index|1) # Task removed with source explanation. -31003 wm_task_removed (TaskId|1|5),(Reason|3) +31003 wm_task_removed (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(Reason|3) # bootanim finished: 31007 wm_boot_animation_done (time|2|3) diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index ea82417a2389..74a236bd862c 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -501,12 +501,16 @@ final class LetterboxUiController { if (hasVisibleTaskbar(mainWindow)) { cropBounds = new Rect(mActivityRecord.getBounds()); + + // Rounded corners should be displayed above the taskbar. + // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo + // because taskbar bounds are in screen coordinates + adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds); + // Activity bounds are in screen coordinates while (0,0) for activity's surface // control is at the top left corner of an app window so offsetting bounds // accordingly. cropBounds.offsetTo(0, 0); - // Rounded corners should be displayed above the taskbar. - adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds); } transaction diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d06f2716b84a..cdb332123fe2 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -67,7 +67,6 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; @@ -607,12 +606,6 @@ class Task extends TaskFragment { boolean mLastSurfaceShowing = true; - /** - * Tracks if a back gesture is in progress. - * Skips any system transition animations if this is set to {@code true}. - */ - boolean mBackGestureStarted = false; - private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, @@ -680,7 +673,7 @@ class Task extends TaskFragment { mLaunchCookie = _launchCookie; mDeferTaskAppear = _deferTaskAppear; mRemoveWithTaskOrganizer = _removeWithTaskOrganizer; - EventLogTags.writeWmTaskCreated(mTaskId, isRootTask() ? INVALID_TASK_ID : getRootTaskId()); + EventLogTags.writeWmTaskCreated(mTaskId); } static Task fromWindowContainerToken(WindowContainerToken token) { @@ -1297,7 +1290,8 @@ class Task extends TaskFragment { } void updateTaskMovement(boolean toTop, int position) { - EventLogTags.writeWmTaskMoved(mTaskId, toTop ? 1 : 0, position); + EventLogTags.writeWmTaskMoved(mTaskId, getRootTaskId(), getDisplayId(), toTop ? 1 : 0, + position); final TaskDisplayArea taskDisplayArea = getDisplayArea(); if (taskDisplayArea != null && isLeafTask()) { taskDisplayArea.onLeafTaskMoved(this, toTop); @@ -2560,7 +2554,7 @@ class Task extends TaskFragment { } mRemoving = true; - EventLogTags.writeWmTaskRemoved(mTaskId, reason); + EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(), reason); clearPinnedTaskIfNeed(); // If applicable let the TaskOrganizer know the Task is vanishing. setTaskOrganizer(null); @@ -2573,7 +2567,8 @@ class Task extends TaskFragment { void reparent(Task rootTask, int position, boolean moveParents, String reason) { if (DEBUG_ROOT_TASK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId + " from rootTask=" + getRootTask()); - EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason); + EventLogTags.writeWmTaskRemoved(mTaskId, getRootTaskId(), getDisplayId(), + "reParentTask:" + reason); reparent(rootTask, position); @@ -3331,14 +3326,6 @@ class Task extends TaskFragment { } }); } - } else if (mBackGestureStarted) { - // Cancel playing transitions if a back navigation animation is in progress. - // This bit is set by {@link BackNavigationController} when a back gesture is started. - // It is used as a one-off transition overwrite that is cleared when the back gesture - // is committed and triggers a transition, or when the gesture is cancelled. - mBackGestureStarted = false; - mDisplayContent.mSkipAppTransitionAnimation = true; - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Skipping app transition animation. task=%s", this); } else { super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index b6c14bbfd8ed..6ff91af527ed 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -455,7 +455,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } mLastLeafTaskToFrontId = t.mTaskId; - EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId); + EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId, getDisplayId()); // Notifying only when a leaf task moved to front. Or the listeners would be notified // couple times from the leaf task all the way up to the root task. mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(t.getTaskInfo()); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index a64bd694605c..ef6859092689 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -928,10 +928,16 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; } + // Check whether the participants were animated from back navigation. + final boolean markBackAnimated = mController.mAtm.mBackNavigationController + .containsBackAnimationTargets(this); // Resolve the animating targets from the participants mTargets = calculateTargets(mParticipants, mChanges); final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges, transaction); + if (markBackAnimated) { + mController.mAtm.mBackNavigationController.clearBackAnimations(mStartTransaction); + } if (mOverrideOptions != null) { info.setAnimationOptions(mOverrideOptions); if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) { @@ -1935,9 +1941,20 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe final Task task = wc.asTask(); if (task != null) { final ActivityRecord topActivity = task.getTopNonFinishingActivity(); - if (topActivity != null && topActivity.mStartingData != null - && topActivity.mStartingData.hasImeSurface()) { - flags |= FLAG_WILL_IME_SHOWN; + if (topActivity != null) { + if (topActivity.mStartingData != null + && topActivity.mStartingData.hasImeSurface()) { + flags |= FLAG_WILL_IME_SHOWN; + } + if (topActivity.mAtmService.mBackNavigationController + .isMonitorTransitionTarget(topActivity)) { + flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; + } + } else { + if (task.mAtmService.mBackNavigationController + .isMonitorTransitionTarget(task)) { + flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; + } } if (task.voiceSession != null) { flags |= FLAG_IS_VOICE_INTERACTION; @@ -1951,6 +1968,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe flags |= FLAG_IS_VOICE_INTERACTION; } flags |= record.mTransitionChangeFlags; + if (record.mAtmService.mBackNavigationController + .isMonitorTransitionTarget(record)) { + flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; + } } final TaskFragment taskFragment = wc.asTaskFragment(); if (taskFragment != null && task == null) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 99a0e9032add..c56a22ef2bbe 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1842,7 +1842,7 @@ public class WindowManagerService extends IWindowManager.Stub // Make this invalid which indicates a null attached frame. outAttachedFrame.set(0, 0, -1, -1); } - outSizeCompatScale[0] = win.getSizeCompatScale(); + outSizeCompatScale[0] = win.getSizeCompatScaleForClient(); } Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index f06a8c9ffe82..17e64995fe8c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1267,8 +1267,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mGlobalScale = mInvGlobalScale = mSizeCompatScale = 1f; } - float getSizeCompatScale() { - return mSizeCompatScale; + float getSizeCompatScaleForClient() { + // If the size compat scale is because of the size compat bounds, we only scale down its + // coordinates at the server side without letting the client know. + return mToken.hasSizeCompatBounds() ? 1f : mSizeCompatScale; } /** @@ -3865,7 +3867,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outFrames.attachedFrame.scale(mInvGlobalScale); } } - outFrames.sizeCompatScale = mSizeCompatScale; + + outFrames.sizeCompatScale = getSizeCompatScaleForClient(); // Note: in the cases where the window is tied to an activity, we should not send a // configuration update when the window has requested to be hidden. Doing so can lead to diff --git a/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java new file mode 100644 index 000000000000..33275bd471f6 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java @@ -0,0 +1,91 @@ +/* + * 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; + +import android.util.Dumpable; +import android.util.Log; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * {@code JUnit} rule that logs (using tag {@value #TAG} the contents of + * {@link Dumpable dumpables} in case of failure. + */ +public final class DumpableDumperRule implements TestRule { + + private static final String TAG = DumpableDumperRule.class.getSimpleName(); + + private static final String[] NO_ARGS = {}; + + private final List<Dumpable> mDumpables = new ArrayList<>(); + + /** + * Adds a {@link Dumpable} to be logged if the test case fails. + */ + public void addDumpable(Dumpable dumpable) { + mDumpables.add(dumpable); + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + base.evaluate(); + } catch (Throwable t) { + dumpOnFailure(description.getMethodName()); + throw t; + } + } + }; + } + + private void dumpOnFailure(String testName) throws IOException { + if (mDumpables.isEmpty()) { + return; + } + Log.w(TAG, "Dumping " + mDumpables.size() + " dumpables on failure of " + testName); + mDumpables.forEach(d -> logDumpable(d)); + } + + private void logDumpable(Dumpable dumpable) { + try { + try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { + dumpable.dump(pw, NO_ARGS); + String[] dump = sw.toString().split(System.lineSeparator()); + Log.w(TAG, "Dumping " + dumpable.getDumpableName() + " (" + dump.length + + " lines):"); + for (String line : dump) { + Log.w(TAG, line); + } + + } catch (RuntimeException e) { + Log.e(TAG, "RuntimeException dumping " + dumpable.getDumpableName(), e); + } + } catch (IOException e) { + Log.e(TAG, "IOException dumping " + dumpable.getDumpableName(), e); + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java index 9aa28ce741aa..c0b507068358 100644 --- a/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/ExtendedMockitoTestCase.java @@ -23,6 +23,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -38,6 +39,9 @@ public abstract class ExtendedMockitoTestCase { private MockitoSession mSession; + @Rule + public final DumpableDumperRule mDumpableDumperRule = new DumpableDumperRule(); + @Before public void startSession() { if (DEBUG) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java index 21f541f54790..923c3e385b5e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java @@ -44,14 +44,14 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_systemUser() { - assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID)); + assertThrows(IllegalArgumentException.class, () -> mMediator + .assignUserToDisplay(USER_SYSTEM, USER_SYSTEM, SECONDARY_DISPLAY_ID)); } @Test public void testAssignUserToDisplay_invalidDisplay() { assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(USER_ID, INVALID_DISPLAY)); + () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, INVALID_DISPLAY)); } @Test @@ -59,7 +59,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator mockCurrentUser(USER_ID); assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID)); + () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID)); assertNoUserAssignedToDisplay(); } @@ -67,11 +67,10 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_startedProfileOfCurrentUser() { mockCurrentUser(PARENT_USER_ID); - addDefaultProfileAndParent(); startDefaultProfile(); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator + .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); Log.v(TAG, "Exception: " + e); assertNoUserAssignedToDisplay(); @@ -80,11 +79,10 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() { mockCurrentUser(PARENT_USER_ID); - addDefaultProfileAndParent(); stopDefaultProfile(); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator + .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); Log.v(TAG, "Exception: " + e); assertNoUserAssignedToDisplay(); @@ -92,17 +90,17 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_displayAvailable() { - mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID); assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID); } @Test public void testAssignUserToDisplay_displayAlreadyAssigned() { - mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID); - IllegalStateException e = assertThrows(IllegalStateException.class, - () -> mMediator.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID)); + IllegalStateException e = assertThrows(IllegalStateException.class, () -> mMediator + .assignUserToDisplay(OTHER_USER_ID, OTHER_USER_ID, SECONDARY_DISPLAY_ID)); Log.v(TAG, "Exception: " + e); assertWithMessage("exception (%s) message", e).that(e).hasMessageThat() @@ -112,10 +110,10 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_userAlreadyAssigned() { - mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID); IllegalStateException e = assertThrows(IllegalStateException.class, - () -> mMediator.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID)); + () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, OTHER_SECONDARY_DISPLAY_ID)); Log.v(TAG, "Exception: " + e); assertWithMessage("exception (%s) message", e).that(e).hasMessageThat() @@ -127,11 +125,9 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_profileOnSameDisplayAsParent() { - addDefaultProfileAndParent(); - - mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)); + mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator + .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); Log.v(TAG, "Exception: " + e); assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); @@ -139,11 +135,9 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() { - addDefaultProfileAndParent(); - - mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID)); + mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator + .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID)); Log.v(TAG, "Exception: " + e); assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); @@ -151,11 +145,9 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() { - addDefaultProfileAndParent(); - - mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)); + mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator + .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, DEFAULT_DISPLAY)); Log.v(TAG, "Exception: " + e); assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); @@ -201,7 +193,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() { - addDefaultProfileAndParent(); startDefaultProfile(); mockCurrentUser(PARENT_USER_ID); assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); @@ -212,7 +203,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() { - addDefaultProfileAndParent(); stopDefaultProfile(); mockCurrentUser(PARENT_USER_ID); assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); @@ -223,7 +213,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() { - addDefaultProfileAndParent(); startDefaultProfile(); mockCurrentUser(PARENT_USER_ID); @@ -285,19 +274,6 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID); } - // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be - // started on secondary display if its parent isn't, so we might need to remove (or refactor - // this test) if/when the underlying logic changes - @Test - public void testGetUserAssignedToDisplay_profileOnSecondaryDisplay() { - addDefaultProfileAndParent(); - mockCurrentUser(USER_ID); - assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID); - - assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID) - .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID); - } - // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as // getUserAssignedToDisplay() for bg users relies only on the user / display assignments } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java index 7ae811793949..7af5f5d6b2fe 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java @@ -39,27 +39,25 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator mockCurrentUser(USER_ID); assertThrows(UnsupportedOperationException.class, - () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID)); + () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID)); } @Test public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() { mockCurrentUser(PARENT_USER_ID); - addDefaultProfileAndParent(); startDefaultProfile(); - assertThrows(UnsupportedOperationException.class, - () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)); + assertThrows(UnsupportedOperationException.class, () -> mMediator + .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); } @Test public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() { mockCurrentUser(PARENT_USER_ID); - addDefaultProfileAndParent(); stopDefaultProfile(); - assertThrows(UnsupportedOperationException.class, - () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)); + assertThrows(UnsupportedOperationException.class, () -> mMediator + .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); } @Test 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 22e6e0dcae1c..7b20092b503a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java @@ -15,23 +15,27 @@ */ package com.android.server.pm; +import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID; import static android.os.UserHandle.USER_NULL; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED; +import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID; +import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_FAILURE; +import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_INVISIBLE; +import static com.android.server.pm.UserVisibilityMediator.START_USER_RESULT_SUCCESS_VISIBLE; +import static com.android.server.pm.UserVisibilityMediator.startUserResultToString; import static com.google.common.truth.Truth.assertWithMessage; import android.annotation.UserIdInt; -import android.util.SparseIntArray; +import android.util.Log; + +import com.android.server.ExtendedMockitoTestCase; import org.junit.Before; import org.junit.Test; -import java.util.LinkedHashMap; -import java.util.Map; - /** * Base class for {@link UserVisibilityMediator} tests. * @@ -39,7 +43,33 @@ import java.util.Map; * device mode (for example, whether the device supports concurrent multiple users on multiple * displays or not). */ -abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrInternalTestCase { +abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { + + private static final String TAG = UserVisibilityMediatorTestCase.class.getSimpleName(); + + /** + * Id for a simple user (that doesn't have profiles). + */ + protected static final int USER_ID = 600; + + /** + * Id for another simple user. + */ + protected static final int OTHER_USER_ID = 666; + + /** + * Id for a user that has one profile (whose id is {@link #PROFILE_USER_ID}. + * + * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service. + */ + protected static final int PARENT_USER_ID = 642; + + /** + * Id for a profile whose parent is {@link #PARENTUSER_ID}. + * + * <p>You can use {@link #addDefaultProfileAndParent()} to add both of this user to the service. + */ + protected static final int PROFILE_USER_ID = 643; /** * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}). @@ -51,12 +81,10 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern */ protected static final int OTHER_SECONDARY_DISPLAY_ID = 108; - private final boolean mUsersOnSecondaryDisplaysEnabled; + private static final boolean FG = true; + private static final boolean BG = false; - // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation - // details into the unit test, but it's fine for now as the tests were copied "as is" - it - // would be better to use a geter() instead - protected final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray(); + private final boolean mUsersOnSecondaryDisplaysEnabled; protected UserVisibilityMediator mMediator; @@ -66,13 +94,93 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Before public final void setMediator() { - mMediator = new UserVisibilityMediator(mUms, mUsersOnSecondaryDisplaysEnabled, - mUsersOnSecondaryDisplays); + mMediator = new UserVisibilityMediator(mUsersOnSecondaryDisplaysEnabled); + mDumpableDumperRule.addDumpable(mMediator); + } + + @Test + public final void testStartUser_currentUser() { + int result = mMediator.startUser(USER_ID, USER_ID, FG, DEFAULT_DISPLAY); + assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE); + + assertCurrentUser(USER_ID); + assertIsCurrentUserOrRunningProfileOfCurrentUser(USER_ID); + assertStartedProfileGroupIdOf(USER_ID, USER_ID); + } + + @Test + public final void testStartUser_currentUserSecondaryDisplay() { + int result = mMediator.startUser(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID); + assertStartUserResult(result, START_USER_RESULT_FAILURE); + + assertCurrentUser(INITIAL_CURRENT_USER_ID); + assertIsNotCurrentUserOrRunningProfileOfCurrentUser(USER_ID); + assertStartedProfileGroupIdOf(USER_ID, NO_PROFILE_GROUP_ID); + } + + @Test + public final void testStartUser_profileBg_parentStarted() { + mockCurrentUser(PARENT_USER_ID); + + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); + assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE); + + assertCurrentUser(PARENT_USER_ID); + assertIsCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID); + assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID); + assertIsStartedProfile(PROFILE_USER_ID); + } + + @Test + public final void testStartUser_profileBg_parentNotStarted() { + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); + assertStartUserResult(result, START_USER_RESULT_SUCCESS_INVISIBLE); + + assertCurrentUser(INITIAL_CURRENT_USER_ID); + assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID); + assertStartedProfileGroupIdOf(PROFILE_USER_ID, PARENT_USER_ID); + assertIsStartedProfile(PROFILE_USER_ID); + } + + @Test + public final void testStartUser_profileBg_secondaryDisplay() { + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID); + assertStartUserResult(result, START_USER_RESULT_FAILURE); + + assertCurrentUser(INITIAL_CURRENT_USER_ID); + assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID); + } + + @Test + public final void testStartUser_profileFg() { + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY); + assertStartUserResult(result, START_USER_RESULT_FAILURE); + + assertCurrentUser(INITIAL_CURRENT_USER_ID); + assertIsNotCurrentUserOrRunningProfileOfCurrentUser(PROFILE_USER_ID); + assertStartedProfileGroupIdOf(PROFILE_USER_ID, NO_PROFILE_GROUP_ID); + } + + @Test + public final void testStartUser_profileFgSecondaryDisplay() { + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID); + + assertStartUserResult(result, START_USER_RESULT_FAILURE); + assertCurrentUser(INITIAL_CURRENT_USER_ID); + } + + @Test + public final void testGetStartedProfileGroupId_whenStartedWithNoProfileGroupId() { + int result = mMediator.startUser(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY); + assertStartUserResult(result, START_USER_RESULT_SUCCESS_VISIBLE); + + assertWithMessage("shit").that(mMediator.getStartedProfileGroupId(USER_ID)) + .isEqualTo(USER_ID); } @Test public final void testAssignUserToDisplay_defaultDisplayIgnored() { - mMediator.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY); + mMediator.assignUserToDisplay(USER_ID, USER_ID, DEFAULT_DISPLAY); assertNoUserAssignedToDisplay(); } @@ -103,18 +211,14 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Test public final void testIsUserVisible_startedProfileOfcurrentUser() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); startDefaultProfile(); - setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED); - assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID)).isTrue(); } @Test public final void testIsUserVisible_stoppedProfileOfcurrentUser() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); stopDefaultProfile(); @@ -164,7 +268,6 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Test public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); startDefaultProfile(); @@ -174,7 +277,6 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Test public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); stopDefaultProfile(); @@ -184,18 +286,14 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Test public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); startDefaultProfile(); - setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED); - assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY) .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue(); } @Test public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); stopDefaultProfile(); @@ -205,18 +303,14 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Test public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); startDefaultProfile(); - setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED); - assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue(); } @Test public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); stopDefaultProfile(); @@ -250,11 +344,8 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Test public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); startDefaultProfile(); - setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED); - assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID) .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID)) .isEqualTo(DEFAULT_DISPLAY); @@ -262,7 +353,6 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern @Test public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() { - addDefaultProfileAndParent(); mockCurrentUser(PARENT_USER_ID); stopDefaultProfile(); @@ -296,28 +386,96 @@ abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrIntern .isEqualTo(USER_ID); } - // NOTE: should only called by tests that indirectly needs to check user assignments (like - // isUserVisible), not by tests for the user assignment methods per se. + // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining + // it's not meant to be used to test startUser() itself. + protected void mockCurrentUser(@UserIdInt int userId) { + Log.d(TAG, "mockCurrentUser(" + userId + ")"); + int result = mMediator.startUser(userId, userId, FG, DEFAULT_DISPLAY); + if (result != START_USER_RESULT_SUCCESS_VISIBLE) { + throw new IllegalStateException("Failed to mock current user " + userId + + ": mediator returned " + startUserResultToString(result)); + } + } + + // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining + // it's not meant to be used to test startUser() itself. + protected void startDefaultProfile() { + mockCurrentUser(PARENT_USER_ID); + Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting" + + " its parent (" + PARENT_USER_ID + ") on foreground"); + + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); + if (result != START_USER_RESULT_SUCCESS_VISIBLE) { + throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID + + ": mediator returned " + startUserResultToString(result)); + } + } + + // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining + // it's not meant to be used to test stopUser() itself. + protected void stopDefaultProfile() { + Log.d(TAG, "stopping default profile"); + mMediator.stopUser(PROFILE_USER_ID); + } + + // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining + // it's not meant to be used to test assignUserToDisplay() itself. protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) { - mUsersOnSecondaryDisplays.put(userId, displayId); + Log.d(TAG, "assignUserToDisplay(" + userId + ", " + displayId + ")"); + int result = mMediator.startUser(userId, userId, BG, displayId); + if (result != START_USER_RESULT_SUCCESS_INVISIBLE) { + throw new IllegalStateException("Failed to startuser " + userId + + " on background: mediator returned " + startUserResultToString(result)); + } + mMediator.assignUserToDisplay(userId, userId, displayId); + } protected final void assertNoUserAssignedToDisplay() { - assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap()) + assertWithMessage("uses on secondary displays") + .that(mMediator.getUsersOnSecondaryDisplays()) .isEmpty(); } protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) { - assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap()) + assertWithMessage("uses on secondary displays") + .that(mMediator.getUsersOnSecondaryDisplays()) .containsExactly(userId, displayId); } - private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() { - int size = mUsersOnSecondaryDisplays.size(); - Map<Integer, Integer> map = new LinkedHashMap<>(size); - for (int i = 0; i < size; i++) { - map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i)); - } - return map; + private void assertCurrentUser(@UserIdInt int userId) { + assertWithMessage("mediator.getCurrentUserId()").that(mMediator.getCurrentUserId()) + .isEqualTo(userId); + } + + private void assertIsStartedProfile(@UserIdInt int userId) { + assertWithMessage("mediator.isStartedProfile(%s)", userId) + .that(mMediator.isStartedProfile(userId)) + .isTrue(); + } + + private void assertStartedProfileGroupIdOf(@UserIdInt int profileId, @UserIdInt int parentId) { + assertWithMessage("mediator.getStartedProfileGroupId(%s)", profileId) + .that(mMediator.getStartedProfileGroupId(profileId)) + .isEqualTo(parentId); + } + + private void assertIsCurrentUserOrRunningProfileOfCurrentUser(int userId) { + assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId) + .that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId)) + .isTrue(); + } + + private void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) { + assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId) + .that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId)) + .isFalse(); + } + + private void assertStartUserResult(int actualResult, int expectedResult) { + assertWithMessage("startUser() result (where %s=%s and %s=%s)", + actualResult, startUserResultToString(actualResult), + expectedResult, startUserResultToString(expectedResult)) + .that(actualResult).isEqualTo(expectedResult); } } diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 935d1d880a2a..80cee50cef79 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -38,6 +38,7 @@ import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG; import static com.android.server.am.UserController.USER_CURRENT_MSG; import static com.android.server.am.UserController.USER_START_MSG; import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG; +import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG; import static com.google.android.collect.Lists.newArrayList; import static com.google.android.collect.Sets.newHashSet; @@ -158,6 +159,7 @@ public class UserControllerTest { REPORT_USER_SWITCH_MSG, USER_SWITCH_TIMEOUT_MSG, USER_START_MSG, + USER_VISIBILITY_CHANGED_MSG, USER_CURRENT_MSG); private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet( @@ -283,7 +285,7 @@ public class UserControllerTest { assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes()) .containsExactly(USER_START_MSG); - verifyUserAssignedToDisplay(TEST_PRE_CREATED_USER_ID, Display.DEFAULT_DISPLAY); + verifyUserNeverAssignedToDisplay(); } private void startUserAssertions( @@ -948,11 +950,13 @@ public class UserControllerTest { } private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) { - verify(mInjector.getUserManagerInternal()).assignUserToDisplay(userId, displayId); + verify(mInjector.getUserManagerInternal()).assignUserToDisplay(eq(userId), anyInt(), + anyBoolean(), eq(displayId)); } private void verifyUserNeverAssignedToDisplay() { - verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt()); + verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplay(anyInt(), anyInt(), + anyBoolean(), anyInt()); } private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) { @@ -964,7 +968,7 @@ public class UserControllerTest { } private void verifySystemUserVisibilityChangedNotified(boolean visible) { - verify(mInjector).notifySystemUserVisibilityChanged(visible); + verify(mInjector).onUserVisibilityChanged(UserHandle.USER_SYSTEM, visible); } // Should be public to allow mocking @@ -1104,13 +1108,13 @@ public class UserControllerTest { } @Override - void onUserStarting(@UserIdInt int userId, boolean visible) { - Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")"); + void onUserStarting(@UserIdInt int userId) { + Log.i(TAG, "onUserStarting(" + userId + ")"); } @Override - void notifySystemUserVisibilityChanged(boolean visible) { - Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")"); + void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) { + Log.i(TAG, "onUserVisibilityChanged(" + userId + ", " + visible + ")"); } } diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index f1383119605e..808130aa9568 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -155,7 +155,18 @@ public class JobStoreTest { } @Test - public void testWritingTwoFilesToDisk() throws Exception { + public void testWritingTwoJobsToDisk_singleFile() throws Exception { + mTaskStoreUnderTest.setUseSplitFiles(false); + runWritingTwoJobsToDisk(); + } + + @Test + public void testWritingTwoJobsToDisk_splitFiles() throws Exception { + mTaskStoreUnderTest.setUseSplitFiles(true); + runWritingTwoJobsToDisk(); + } + + private void runWritingTwoJobsToDisk() throws Exception { final JobInfo task1 = new Builder(8, mComponent) .setRequiresDeviceIdle(true) .setPeriodic(10000L) @@ -169,8 +180,10 @@ public class JobStoreTest { .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); - final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, SOME_UID, null, -1, null); - final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1, null); + final int uid1 = SOME_UID; + final int uid2 = uid1 + 1; + final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null); + final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null); mTaskStoreUnderTest.add(taskStatus1); mTaskStoreUnderTest.add(taskStatus2); waitForPendingIo(); 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 c906abcdc986..e5842b400ba3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT; @@ -71,6 +72,7 @@ import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.times; import android.annotation.Nullable; import android.app.ActivityManager; @@ -87,6 +89,8 @@ import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.view.InsetsFrameProvider; +import android.view.InsetsSource; +import android.view.InsetsVisibilities; import android.view.WindowManager; import androidx.test.filters.MediumTest; @@ -105,6 +109,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.List; /** * Tests for Size Compatibility mode. @@ -2368,6 +2375,48 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testLetterboxDetailsForTaskBar_letterboxNotOverlappingTaskBar() { + mAtm.mDevEnableNonResizableMultiWindow = true; + final int screenHeight = 2200; + final int screenWidth = 1400; + final int taskbarHeight = 200; + setUpDisplaySizeWithApp(screenWidth, screenHeight); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); + + // Move first activity to split screen which takes half of the screen. + organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight); + organizer.putTaskToPrimary(mTask, true); + + final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR); + navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight)); + + mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15); + + final WindowState w1 = addWindowToActivity(mActivity); + w1.mAboveInsetsState.addSource(navSource); + + // Prepare unresizable activity with max aspect ratio + prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + + // Refresh the letterboxes + mActivity.mRootWindowContainer.performSurfacePlacement(); + + final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class); + verify(mTransaction, times(2)).setWindowCrop( + eq(w1.getSurfaceControl()), + cropCapturer.capture() + ); + final List<Rect> capturedCrops = cropCapturer.getAllValues(); + + final int expectedHeight = screenHeight / 2 - taskbarHeight; + assertEquals(2, capturedCrops.size()); + assertEquals(expectedHeight, capturedCrops.get(0).bottom); + assertEquals(expectedHeight, capturedCrops.get(1).bottom); + } + + @Test public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() { mAtm.mDevEnableNonResizableMultiWindow = true; setUpDisplaySizeWithApp(2800, 1000); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 7f5beb1f6cf0..4fd2b78b6f66 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2392,6 +2392,8 @@ public class UsageStatsService extends SystemService implements @Override public void setAppStandbyBucket(String packageName, int bucket, int userId) { + super.setAppStandbyBucket_enforcePermission(); + final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); @@ -2442,6 +2444,8 @@ public class UsageStatsService extends SystemService implements @Override public void setAppStandbyBuckets(ParceledListSlice appBuckets, int userId) { + super.setAppStandbyBuckets_enforcePermission(); + final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); @@ -2493,6 +2497,8 @@ public class UsageStatsService extends SystemService implements public void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime, int userId) { + super.setEstimatedLaunchTime_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { UsageStatsService.this @@ -2506,6 +2512,8 @@ public class UsageStatsService extends SystemService implements @Override public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) { + super.setEstimatedLaunchTimes_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { UsageStatsService.this diff --git a/telecomm/OWNERS b/telecomm/OWNERS index eb0c4327ec46..dcaf858a0a0b 100644 --- a/telecomm/OWNERS +++ b/telecomm/OWNERS @@ -4,3 +4,7 @@ breadley@google.com tgunn@google.com xiaotonj@google.com rgreenwalt@google.com +chinmayd@google.com +grantmenke@google.com +pmadapurmath@google.com +tjstuart@google.com
\ No newline at end of file diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index b22e2b76a477..936fad51f095 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8785,7 +8785,7 @@ public class CarrierConfigManager { * The default value is 30 minutes. * * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR - * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED + * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED */ public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index bfa60ba960ca..6be2f770efe9 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -138,13 +138,6 @@ public class ServiceState implements Parcelable { */ public static final int FREQUENCY_RANGE_MMWAVE = 4; - private static final List<Integer> FREQUENCY_RANGE_ORDER = Arrays.asList( - FREQUENCY_RANGE_UNKNOWN, - FREQUENCY_RANGE_LOW, - FREQUENCY_RANGE_MID, - FREQUENCY_RANGE_HIGH, - FREQUENCY_RANGE_MMWAVE); - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "DUPLEX_MODE_", @@ -2108,15 +2101,6 @@ public class ServiceState implements Parcelable { } /** - * @hide - */ - public static final int getBetterNRFrequencyRange(int range1, int range2) { - return FREQUENCY_RANGE_ORDER.indexOf(range1) > FREQUENCY_RANGE_ORDER.indexOf(range2) - ? range1 - : range2; - } - - /** * Returns a copy of self with location-identifying information removed. * Always clears the NetworkRegistrationInfo's CellIdentity fields, but if removeCoarseLocation * is true, clears other info as well. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 51a7840a9fbe..e099e69e3edd 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -17324,14 +17324,14 @@ public class TelephonyManager { public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; /** - * Purchase premium capability failed because the network is congested. + * Purchase premium capability failed because the entitlement check failed. * Subsequent attempts will be throttled for the amount of time specified by * {@link CarrierConfigManager * #KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}. * Throttling will be reevaluated when the network is no longer congested. */ - public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED = 13; + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED = 13; /** * Purchase premium capability failed because the request was not made on the default data @@ -17368,7 +17368,7 @@ public class TelephonyManager { PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT, PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED, PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE, - PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED, + PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED, PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB, PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}) public @interface PurchasePremiumCapabilityResult {} @@ -17407,8 +17407,8 @@ public class TelephonyManager { return "REQUEST_FAILED"; case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE: return "NETWORK_NOT_AVAILABLE"; - case PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED: - return "NETWORK_CONGESTED"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED: + return "ENTITLEMENT_CHECK_FAILED"; case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUB: return "NOT_DEFAULT_DATA_SUB"; case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP: diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index 73aff4351785..a834e2bbd0d1 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -468,14 +468,14 @@ public final class DataCallResponse implements Parcelable { final boolean isQosBearerSessionsSame = (mQosBearerSessions == null || other.mQosBearerSessions == null) ? mQosBearerSessions == other.mQosBearerSessions - : mQosBearerSessions.size() == other.mQosBearerSessions.size() - && mQosBearerSessions.containsAll(other.mQosBearerSessions); + : (mQosBearerSessions.size() == other.mQosBearerSessions.size() + && mQosBearerSessions.containsAll(other.mQosBearerSessions)); final boolean isTrafficDescriptorsSame = (mTrafficDescriptors == null || other.mTrafficDescriptors == null) ? mTrafficDescriptors == other.mTrafficDescriptors - : mTrafficDescriptors.size() == other.mTrafficDescriptors.size() - && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); + : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size() + && mTrafficDescriptors.containsAll(other.mTrafficDescriptors)); return mCause == other.mCause && mSuggestedRetryTime == other.mSuggestedRetryTime @@ -504,10 +504,35 @@ public final class DataCallResponse implements Parcelable { @Override public int hashCode() { + // Generate order-independent hashes for lists + int addressesHash = mAddresses.stream() + .map(LinkAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int dnsAddressesHash = mDnsAddresses.stream() + .map(InetAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int gatewayAddressesHash = mGatewayAddresses.stream() + .map(InetAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int pcscfAddressesHash = mPcscfAddresses.stream() + .map(InetAddress::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int qosBearerSessionsHash = mQosBearerSessions.stream() + .map(QosBearerSession::hashCode) + .mapToInt(Integer::intValue) + .sum(); + int trafficDescriptorsHash = mTrafficDescriptors.stream() + .map(TrafficDescriptor::hashCode) + .mapToInt(Integer::intValue) + .sum(); return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, - mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, - mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos, - mQosBearerSessions, mSliceInfo, mTrafficDescriptors); + mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash, + pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, + mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash); } @Override @@ -816,8 +841,8 @@ public final class DataCallResponse implements Parcelable { /** * Set pdu session id. * <p/> - * The id must be between 1 and 15 when linked to a pdu session. If no pdu session - * exists for the current data call, the id must be set to {@link PDU_SESSION_ID_NOT_SET}. + * The id must be between 1 and 15 when linked to a pdu session. If no pdu session + * exists for the current data call, the id must be set to {@link #PDU_SESSION_ID_NOT_SET}. * * @param pduSessionId Pdu Session Id of the data call. * @return The same instance of the builder. @@ -858,6 +883,7 @@ public final class DataCallResponse implements Parcelable { */ public @NonNull Builder setQosBearerSessions( @NonNull List<QosBearerSession> qosBearerSessions) { + Objects.requireNonNull(qosBearerSessions); mQosBearerSessions = qosBearerSessions; return this; } @@ -891,6 +917,7 @@ public final class DataCallResponse implements Parcelable { */ public @NonNull Builder setTrafficDescriptors( @NonNull List<TrafficDescriptor> trafficDescriptors) { + Objects.requireNonNull(trafficDescriptors); mTrafficDescriptors = trafficDescriptors; return this; } diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index d9d5c14735ea..e78a1e107afe 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -660,9 +660,6 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu if (!first.getEmergencyUrns().equals(second.getEmergencyUrns())) { return false; } - if (first.getEmergencyCallRouting() != second.getEmergencyCallRouting()) { - return false; - } // Never merge two numbers if one of them is from test mode but the other one is not; // This supports to remove a number from the test mode. if (first.isFromSources(EMERGENCY_NUMBER_SOURCE_TEST) @@ -685,12 +682,18 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu public static EmergencyNumber mergeSameEmergencyNumbers(@NonNull EmergencyNumber first, @NonNull EmergencyNumber second) { if (areSameEmergencyNumbers(first, second)) { + int routing = first.getEmergencyCallRouting(); + + if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) { + routing = second.getEmergencyCallRouting(); + } + return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(), first.getEmergencyServiceCategoryBitmask(), first.getEmergencyUrns(), first.getEmergencyNumberSourceBitmask() | second.getEmergencyNumberSourceBitmask(), - first.getEmergencyCallRouting()); + routing); } return null; } diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java index dd9b294a9596..afaeca1f76ae 100644 --- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java +++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java @@ -15,7 +15,6 @@ */ package com.android.frameworks.perftests.job; - import android.app.job.JobInfo; import android.content.ComponentName; import android.content.Context; @@ -46,7 +45,8 @@ import java.util.List; public class JobStorePerfTests { private static final String SOURCE_PACKAGE = "com.android.frameworks.perftests.job"; private static final int SOURCE_USER_ID = 0; - private static final int CALLING_UID = 10079; + private static final int BASE_CALLING_UID = 10079; + private static final int MAX_UID_COUNT = 10; private static Context sContext; private static File sTestDir; @@ -65,10 +65,10 @@ public class JobStorePerfTests { sJobStore = JobStore.initAndGetForTesting(sContext, sTestDir); for (int i = 0; i < 50; i++) { - sFewJobs.add(createJobStatus("fewJobs", i)); + sFewJobs.add(createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT))); } for (int i = 0; i < 500; i++) { - sManyJobs.add(createJobStatus("manyJobs", i)); + sManyJobs.add(createJobStatus("manyJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT))); } } @@ -104,6 +104,64 @@ public class JobStorePerfTests { runPersistedJobWriting(sManyJobs); } + private void runPersistedJobWriting_delta(List<JobStatus> jobList, + List<JobStatus> jobAdditions, List<JobStatus> jobRemovals) { + final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); + + long elapsedTimeNs = 0; + while (benchmarkState.keepRunning(elapsedTimeNs)) { + sJobStore.clearForTesting(); + for (JobStatus job : jobList) { + sJobStore.addForTesting(job); + } + sJobStore.writeStatusToDiskForTesting(); + + for (JobStatus job : jobAdditions) { + sJobStore.addForTesting(job); + } + for (JobStatus job : jobRemovals) { + sJobStore.removeForTesting(job); + } + + final long startTime = SystemClock.elapsedRealtimeNanos(); + sJobStore.writeStatusToDiskForTesting(); + final long endTime = SystemClock.elapsedRealtimeNanos(); + elapsedTimeNs = endTime - startTime; + } + } + + @Test + public void testPersistedJobWriting_delta_fewJobs() { + List<JobStatus> additions = new ArrayList<>(); + List<JobStatus> removals = new ArrayList<>(); + final int numModifiedUids = MAX_UID_COUNT / 2; + for (int i = 0; i < sFewJobs.size() / 3; ++i) { + JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids)); + if (i % 2 == 0) { + additions.add(job); + } else { + removals.add(job); + } + } + runPersistedJobWriting_delta(sFewJobs, additions, removals); + } + + @Test + public void testPersistedJobWriting_delta_manyJobs() { + List<JobStatus> additions = new ArrayList<>(); + List<JobStatus> removals = new ArrayList<>(); + final int numModifiedUids = MAX_UID_COUNT / 2; + for (int i = 0; i < sManyJobs.size() / 3; ++i) { + JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids)); + if (i % 2 == 0) { + additions.add(job); + } else { + removals.add(job); + } + } + runPersistedJobWriting_delta(sManyJobs, additions, removals); + } + private void runPersistedJobReading(List<JobStatus> jobList, boolean rtcIsGood) { final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); @@ -144,12 +202,12 @@ public class JobStorePerfTests { runPersistedJobReading(sManyJobs, false); } - private static JobStatus createJobStatus(String testTag, int jobId) { + private static JobStatus createJobStatus(String testTag, int jobId, int callingUid) { JobInfo jobInfo = new JobInfo.Builder(jobId, new ComponentName(sContext, "JobStorePerfTestJobService")) .setPersisted(true) .build(); return JobStatus.createFromJobInfo( - jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag); + jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, testTag); } } diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index 3da8b460df13..133c1767c9b4 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java @@ -147,12 +147,39 @@ public class BroadcastInterceptingContext extends ContextWrapper { @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { - return registerReceiver(receiver, filter, null, null); + return registerReceiver(receiver, filter, null, null, 0); + } + + /** + * Registers the specified {@code receiver} to listen for broadcasts that match the {@code + * filter} in the current process. + * + * <p>Since this method only listens for broadcasts in the current process, the provided {@code + * flags} are ignored; this method is primarily intended to allow receivers that register with + * flags to register in the current process during tests. + */ + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + return registerReceiver(receiver, filter, null, null, flags); } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { + return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0); + } + + /** + * Registers the specified {@code receiver} to listen for broadcasts that match the {@code + * filter} to run in the context of the specified {@code scheduler} in the current process. + * + * <p>Since this method only listens for broadcasts in the current process, the provided {@code + * flags} are ignored; this method is primarily intended to allow receivers that register with + * flags to register in the current process during tests. + */ + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, int flags) { synchronized (mInterceptors) { mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler)); } |