Merge "Rename EntryProvider as SpaSearchProvider"
diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index 63e5983..5befa1f 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 d9fb318..358f009 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 @@
@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 @@
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 @@
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 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 @@
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 @@
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 @@
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 68cb049..2f94705f 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.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 @@
/** 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 @@
@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 @@
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 @@
// 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 void add(JobStatus jobStatus) {
mJobSet.add(jobStatus);
if (jobStatus.isPersisted()) {
+ mPendingJobWriteUids.put(jobStatus.getUid(), true);
maybeWriteStatusToDiskAsync();
}
if (DEBUG) {
@@ -224,6 +244,9 @@
@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 @@
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 @@
@VisibleForTesting
public void clear() {
mJobSet.clear();
+ mPendingJobWriteUids.put(ALL_UIDS, true);
maybeWriteStatusToDiskAsync();
}
@@ -282,6 +318,36 @@
@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 @@
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 @@
* 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 @@
}
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();
}
- writeJobsMapImpl(storeCopy);
+ 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));
+ }
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 @@
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 @@
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 @@
@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);
+ 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++;
+ 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 05c9012..3766a0b9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32542,7 +32542,7 @@
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();
@@ -41722,7 +41722,7 @@
field public static final String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
field public static final String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
- field public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
+ field @Deprecated public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
@@ -44095,8 +44095,8 @@
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 2012948..8ec313ec 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -30,10 +30,12 @@
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
per-file IGameManager* = file:/GAME_MANAGER_OWNERS
+per-file IGameMode* = file:/GAME_MANAGER_OWNERS
# ActivityThread
per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 30a6c31..ee14708 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -176,7 +176,6 @@
/** 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 @@
/** 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 6330661..1929a4d 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -536,8 +536,8 @@
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 8eaa5ad..a887f2a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -130,7 +130,7 @@
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 3d20d63..a38d9da 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2904,12 +2904,19 @@
*/
@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/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 47b16a3..d2a4ae2 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -55,6 +55,20 @@
"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 37fc9f2..104570d0 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -575,6 +575,7 @@
*/
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 @@
mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5));
}
}
+ Trace.endSection();
}
/**
@@ -1259,7 +1261,9 @@
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 @@
+ ", " + 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 @@
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 @@
// 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 @@
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 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 @@
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 @@
}
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 @@
}
} 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 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 @@
@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/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d9ca16e..0798110 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 @@
// 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 06d7cdd..c3623a2 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 @@
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 @@
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 392e140..ad1b872 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.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 @@
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 @@
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 0000000..9b62dc7
--- /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 b292d7d..a0ed026 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 @@
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 @@
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 @@
// 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 @@
}
};
}
-
- /** 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/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
index 98485c0..ee73f00 100644
--- a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
+++ b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
@@ -29,10 +29,11 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
/**
- * Test class for {@link ConstrainDisplayApisConfig}.
+ * Test for {@link ConstrainDisplayApisConfig}.
*
* Build/Install/Run:
* atest FrameworksCoreTests:ConstrainDisplayApisConfigTest
@@ -72,6 +73,7 @@
testNeverConstrainDisplayApis("com.android.test", /* version= */ 1, /* expected= */ false);
}
+ @Ignore("b/257375674")
@Test
public void neverConstrainDisplayApis_flagsHasSingleEntry_returnsTrueForPackageWithinRange() {
setNeverConstrainDisplayApisFlag("com.android.test:1:1");
@@ -107,6 +109,7 @@
testNeverConstrainDisplayApis("com.android.test4", /* version= */ 9, /* expected= */ false);
}
+ @Ignore("b/257375674")
@Test
public void neverConstrainDisplayApis_flagHasInvalidEntries_ignoresInvalidEntries() {
// We add a valid entry before and after the invalid ones to make sure they are applied.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index a0e176c..ff6f2b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -430,7 +430,8 @@
}
@Override
- public @Nullable SplashScreenView get() {
+ @Nullable
+ public SplashScreenView get() {
synchronized (this) {
while (!mIsViewSet) {
try {
@@ -691,7 +692,7 @@
private final TaskSnapshotWindow mTaskSnapshotWindow;
private SplashScreenView mContentView;
private boolean mSetSplashScreen;
- private @StartingWindowType int mSuggestType;
+ @StartingWindowType private int mSuggestType;
private int mBGColor;
private final long mCreateTime;
private int mSystemBarAppearance;
@@ -732,7 +733,7 @@
// Reset the system bar color which set by splash screen, make it align to the app.
private void clearSystemBarColor() {
- if (mDecorView == null) {
+ if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
return;
}
if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 6764890..ad6acea 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -438,6 +438,18 @@
return "AUDIO_FORMAT_APTX_TWSP";
case /* AUDIO_FORMAT_LC3 */ 0x2B000000:
return "AUDIO_FORMAT_LC3";
+ case /* AUDIO_FORMAT_MPEGH */ 0x2C000000:
+ return "AUDIO_FORMAT_MPEGH";
+ case /* AUDIO_FORMAT_IEC60958 */ 0x2D000000:
+ return "AUDIO_FORMAT_IEC60958";
+ case /* AUDIO_FORMAT_DTS_UHD */ 0x2E000000:
+ return "AUDIO_FORMAT_DTS_UHD";
+ case /* AUDIO_FORMAT_DRA */ 0x2F000000:
+ return "AUDIO_FORMAT_DRA";
+ case /* AUDIO_FORMAT_APTX_ADAPTIVE_QLEA */ 0x30000000:
+ return "AUDIO_FORMAT_APTX_ADAPTIVE_QLEA";
+ case /* AUDIO_FORMAT_APTX_ADAPTIVE_R4 */ 0x31000000:
+ return "AUDIO_FORMAT_APTX_ADAPTIVE_R4";
/* Aliases */
case /* AUDIO_FORMAT_PCM_16_BIT */ 0x1:
@@ -510,10 +522,14 @@
return "AUDIO_FORMAT_MAT_2_0"; // (MAT | MAT_SUB_2_0)
case /* AUDIO_FORMAT_MAT_2_1 */ 0x24000003:
return "AUDIO_FORMAT_MAT_2_1"; // (MAT | MAT_SUB_2_1)
- case /* AUDIO_FORMAT_DTS_UHD */ 0x2E000000:
- return "AUDIO_FORMAT_DTS_UHD";
- case /* AUDIO_FORMAT_DRA */ 0x2F000000:
- return "AUDIO_FORMAT_DRA";
+ case /* AUDIO_FORMAT_MPEGH_SUB_BL_L3 */ 0x2C000013:
+ return "AUDIO_FORMAT_MPEGH_SUB_BL_L3";
+ case /* AUDIO_FORMAT_MPEGH_SUB_BL_L4 */ 0x2C000014:
+ return "AUDIO_FORMAT_MPEGH_SUB_BL_L4";
+ case /* AUDIO_FORMAT_MPEGH_SUB_LC_L3 */ 0x2C000023:
+ return "AUDIO_FORMAT_MPEGH_SUB_LC_L3";
+ case /* AUDIO_FORMAT_MPEGH_SUB_LC_L4 */ 0x2C000024:
+ return "AUDIO_FORMAT_MPEGH_SUB_LC_L4";
default:
return "AUDIO_FORMAT_(" + audioFormat + ")";
}
@@ -2407,4 +2423,3 @@
*/
final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
}
-
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 25fa34b..1a852c5 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -28,6 +28,7 @@
<string name="passkeys">passkeys</string>
<string name="passwords">passwords</string>
<string name="sign_ins">sign-ins</string>
+ <string name="other_password_manager">Other password manager</string>
<string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
<!-- Spoken content description of an element which will close the sheet when clicked. -->
<string name="close_sheet">"Close sheet"</string>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 2099a23..6e4bfd8 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.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 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 @@
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 @@
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 @@
}
}
+ 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 @@
}
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
- providerList as List<CreateCredentialProviderData>, context)
+ providerEnabledList as List<CreateCredentialProviderData>, context)
+ val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
+ // Handle runtime cast error
+ 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 @@
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,7 +169,7 @@
}
// 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")
@@ -185,6 +197,13 @@
)
}
+ private fun testDisabledProviderList(): List<DisabledProviderData> {
+ return listOf(
+ DisabledProviderData("LastPass"),
+ DisabledProviderData("Xyzinstalledbutdisabled"),
+ )
+ }
+
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 5c79564..e4fab07 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -20,6 +20,7 @@
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,12 +104,12 @@
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> {
return providerDataList.map {
- com.android.credentialmanager.createflow.ProviderInfo(
+ com.android.credentialmanager.createflow.EnabledProviderInfo(
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
@@ -119,6 +120,20 @@
}
}
+ fun toDisabledProviderList(
+ providerDataList: List<DisabledProviderData>,
+ context: Context,
+ ): List<com.android.credentialmanager.createflow.DisabledProviderInfo> {
+ return providerDataList.map {
+ com.android.credentialmanager.createflow.DisabledProviderInfo(
+ // TODO: replace to extract from the service data structure when available
+ icon = context.getDrawable(R.drawable.ic_passkey)!!,
+ name = it.providerFlattenedComponentName,
+ displayName = it.providerFlattenedComponentName,
+ )
+ }
+ }
+
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 21190e7..0c3447f 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 @@
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 @@
* 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 437e7b21..06e437c 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.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 @@
onCancel = viewModel::onCancel,
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- providerList = uiState.providers,
+ enabledProviderList = uiState.enabledProviders,
onCancel = viewModel::onCancel,
onProviderSelected = viewModel::onProviderSelected
)
@@ -70,14 +71,16 @@
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 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderSelectionCard(
- providerList: List<ProviderInfo>,
+ enabledProviderList: List<EnabledProviderInfo>,
onProviderSelected: (String) -> Unit,
onCancel: () -> Unit
) {
@@ -182,7 +185,7 @@
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
- providerList.forEach {
+ enabledProviderList.forEach {
item {
ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
}
@@ -212,9 +215,11 @@
@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 @@
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 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsRowIntroCard(
- providerInfo: ProviderInfo,
+ providerInfo: EnabledProviderInfo,
onDefaultOrNotSelected: () -> Unit,
) {
Card() {
@@ -483,7 +494,7 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsInfoRow(
- providerInfo: ProviderInfo,
+ providerInfo: EnabledProviderInfo,
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
@@ -546,4 +557,37 @@
}
}
)
+}
+
+@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 2e9758a..8b94201 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.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 @@
}
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 @@
)
}
- 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 @@
)
}
+ fun onDisabledPasswordManagerSelected() {
+ // TODO: Complete this function
+ }
+
fun onCancel() {
CredentialManagerRepo.getInstance().onCancel()
dialogResult.value = DialogResult(ResultState.CANCELED)
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 0000000..46abff8
--- /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 0000000..d2ae52d
--- /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/mobile/MobileIconCarrierIdOverrides.kt b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileIconCarrierIdOverrides.kt
new file mode 100644
index 0000000..a0395b5
--- /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/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 0000000..740261d
--- /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 11237dc..47771aa 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 a4e7a5f..f1aa544 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 0a55cf7..3861d98 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 a936914..d6c9e98 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 d9df337..707bc9e 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 97ead01..b98165f 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 17f82b5..8b41a44 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 0e48760..6824d7f 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 8612b3a..0000000
--- 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 c2658a9..0000000
--- 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 40c8774..a790d89 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 @@
* 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 73229c3..faaba63 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.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 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 2bd3ca5..db986e0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -103,6 +103,11 @@
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 2bb3a5f..ffcf42f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1097,11 +1097,19 @@
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/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index f74c721..815ac68 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -141,8 +141,7 @@
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
private final @Background DelayableExecutor mBackgroundExecutor;
- private int mOrientation;
- private boolean mSkipFirstLostFocus = false;
+ private boolean mIsOrientationChanged = false;
// Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
@Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
@@ -491,6 +490,7 @@
@Override
public void onOrientationChanged() {
maybeUpdatePositionForUdfps(true /* invalidate */);
+ mIsOrientationChanged = true;
}
@Override
@@ -499,8 +499,8 @@
if (!hasWindowFocus) {
//it's a workaround to avoid closing BP incorrectly
//BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true)
- if (mSkipFirstLostFocus) {
- mSkipFirstLostFocus = false;
+ if (mIsOrientationChanged) {
+ mIsOrientationChanged = false;
return;
}
Log.v(TAG, "Lost window focus, dismissing the dialog");
@@ -512,9 +512,6 @@
public void onAttachedToWindow() {
super.onAttachedToWindow();
- //save the first orientation
- mOrientation = getResources().getConfiguration().orientation;
-
mWakefulnessLifecycle.addObserver(this);
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
@@ -670,7 +667,7 @@
}
if (savedState != null) {
- mSkipFirstLostFocus = savedState.getBoolean(
+ mIsOrientationChanged = savedState.getBoolean(
AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED);
}
@@ -764,9 +761,7 @@
mBiometricView != null && mCredentialView == null);
outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
- if (mOrientation != getResources().getConfiguration().orientation) {
- outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, true);
- }
+ outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, mIsOrientationChanged);
if (mBiometricView != null) {
mBiometricView.onSaveState(outState);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 588ef5c..4dfcd63 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 2d76ff2..115edd11 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 @@
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 @@
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 @@
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 @@
init {
Log.d(TAG, "Initializing")
+ dumpManager.registerDumpable(TAG, this)
serviceListing.addCallback(serviceListingCallback)
serviceListing.setListening(true)
serviceListing.reload()
@@ -165,7 +173,7 @@
* [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 @@
* @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 d3555ee..7ab36e8 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 @@
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 99dfefa..002ffdb 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 @@
// 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 @@
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 0f4581c..0000000
--- 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 d4514c5..783f752 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 @@
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 ca25282..9d5d8bb 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 @@
*/
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 @@
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 @@
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 @@
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 @@
}
.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 e3d1a27..bce7d92 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 @@
*/
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 @@
*/
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 @@
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 0aeff7f..e5521c7 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 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 @@
@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 @@
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 0000000..dd29673
--- /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 0000000..c44cda4
--- /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 0000000..9e2b724
--- /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 614ff8d..5a1c702 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 @@
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 57fb4a1..58a8093 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 @@
}
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 a7c6d44..43dd358e 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 @@
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 749183e..54a4f49 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 @@
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 @@
/* 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 fd4814d..cca2d56 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 @@
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 @@
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 @@
)
} 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 6c1adbd..4100f7a 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.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 @@
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 c22f4e7a..fcd653b 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 @@
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 37f33af..dbffeab 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 @@
@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 7958033..dd908c4 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 @@
/** List of all possible states to transition to/from */
enum class KeyguardState {
- /**
- * For initialization as well as when the security method is set to NONE, indicating that
- * the keyguard should never be shown.
+ /*
+ * The display is completely off, as well as any sensors that would trigger the device to wake
+ * up.
*/
- NONE,
- /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
+ OFF,
+ /**
+ * 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.
+ */
AOD,
/*
* The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
@@ -34,7 +51,6 @@
* 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 0e0465b..38a93b5 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 @@
/** 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 732a6f7..767fd58 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 @@
/** 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 64f834d..92040f4 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 @@
/** 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 a22958b..7739a45 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 @@
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 @@
}
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 0781600..526ae74 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 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 @@
/** 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 b682bd1..d4991f9 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 @@
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 @@
measuredWidth,
measuredHeight,
displayWidthPx,
+ displayHeightPx,
taskbarSize,
isTablet,
currentRotation,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index cffd28f..19bb15a 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.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 @@
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 @@
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 @@
*/
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/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index c41e2bc..4cb91e1 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 @@
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 @@
}
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 0000000..fc61e90
--- /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 8c1b574..ceef8c8 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.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 @@
private boolean mCollapsedOnDown;
private boolean mClosingWithAlphaFadeOut;
private boolean mHeadsUpAnimatingAway;
- private boolean mLaunchingAffordance;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
@@ -575,7 +573,7 @@
/** 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 @@
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 @@
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
- CameraGestureHelper cameraGestureHelper,
KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
DumpManager dumpManager) {
@@ -924,7 +920,6 @@
unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
- mCameraGestureHelper = cameraGestureHelper;
mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
dumpManager.registerDumpable(this);
}
@@ -2814,7 +2809,7 @@
*/
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 @@
}
} 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 @@
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 @@
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 int getBarState() {
+ return mBarState;
+ }
+
private boolean isOnKeyguard() {
return mBarState == KEYGUARD;
}
@@ -3993,35 +3990,6 @@
&& 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 @@
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 3670d09..0deb47d 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 @@
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 ec221b7..c523d22 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.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 @@
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 @@
TelephonyManager phone,
CallbackHandler callbackHandler,
NetworkControllerImpl networkController,
+ MobileMappingsProxy mobileMappingsProxy,
SubscriptionInfo info,
SubscriptionDefaults defaults,
Looper receiverLooper,
@@ -135,13 +136,14 @@
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 @@
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 @@
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 @@
} 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 @@
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 7938179..a323454 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 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 @@
val context: Context,
val callbackHandler: CallbackHandler,
val carrierConfigTracker: CarrierConfigTracker,
+ val mobileMappings: MobileMappingsProxy,
) {
fun createMobileSignalController(
config: MobileMappings.Config,
@@ -56,6 +58,7 @@
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 f20d206..1fb6a98 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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
override fun tableColumns(): List<String> {
val columns = listOf("dataSim",
+ "carrierId",
"networkName",
"networkNameData",
"dataConnected",
@@ -178,6 +206,7 @@
"showQuickSettingsRatIcon",
"voiceServiceState",
"isInService",
+ "networkTypeIconCache",
"serviceState",
"signalStrength",
"displayInfo")
@@ -187,6 +216,7 @@
override fun tableData(): List<String> {
val columns = listOf(dataSim,
+ carrierId,
networkName,
networkNameData,
dataConnected,
@@ -201,6 +231,7 @@
showQuickSettingsRatIcon(),
getVoiceServiceState(),
isInService(),
+ networkTypeResIdCache,
serviceState?.minLog() ?: "(null)",
signalStrength?.minLog() ?: "(null)",
telephonyDisplayInfo).map { it.toString() }
@@ -217,6 +248,7 @@
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 @@
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 450b757..73d6483 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_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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
// 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 0000000..9be7ee9
--- /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 a02dd34..42b874f 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 @@
* 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/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index de158c4..3021414 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1511,7 +1511,7 @@
l.setAlpha(alpha);
}
if (mChildrenContainer != null) {
- mChildrenContainer.setAlpha(alpha);
+ mChildrenContainer.setContentAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 0554fb5..645a02d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -495,6 +495,20 @@
}
/**
+ * Sets the alpha on the content, while leaving the background of the container itself as is.
+ *
+ * @param alpha alpha value to apply to the content
+ */
+ public void setContentAlpha(float alpha) {
+ for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+ mNotificationHeader.getChildAt(i).setAlpha(alpha);
+ }
+ for (ExpandableNotificationRow child : getAttachedChildren()) {
+ child.setContentAlpha(alpha);
+ }
+ }
+
+ /**
* To be called any time the rows have been updated
*/
public void updateExpansionStates() {
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 41f0520..f3482f4 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.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 javax.inject.Inject;
+import dagger.Lazy;
+
/** */
@CentralSurfacesComponent.CentralSurfacesScope
public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
@@ -99,6 +102,7 @@
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 @@
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 @@
mVibratorOptional = vibratorOptional;
mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
+ mCameraLauncherLazy = cameraLauncherLazy;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -346,7 +351,8 @@
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 @@
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 71609f8..334f1af 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.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 @@
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 @@
private Runnable mLaunchTransitionEndRunnable;
private Runnable mLaunchTransitionCancelRunnable;
+ private boolean mLaunchingAffordance;
private boolean mLaunchCameraWhenFinishedWaking;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private boolean mLaunchEmergencyActionWhenFinishedWaking;
@@ -744,7 +747,8 @@
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
- IDreamManager dreamManager) {
+ IDreamManager dreamManager,
+ Lazy<CameraLauncher> cameraLauncherLazy) {
mContext = context;
mNotificationsController = notificationsController;
mFragmentService = fragmentService;
@@ -821,6 +825,7 @@
mMessageRouter = messageRouter;
mWallpaperManager = wallpaperManager;
mJankMonitor = jankMonitor;
+ mCameraLauncherLazy = cameraLauncherLazy;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -2949,7 +2954,7 @@
private void onLaunchTransitionFadingEnded() {
mNotificationPanelViewController.resetAlpha();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mKeyguardStateController.setLaunchTransitionFadingAway(false);
@@ -3019,7 +3024,7 @@
private void onLaunchTransitionTimeout() {
Log.w(TAG, "Launch transition: Timeout!");
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
releaseGestureWakeLock();
mNotificationPanelViewController.resetViews(false /* animate */);
}
@@ -3072,7 +3077,7 @@
}
mMessageRouter.cancelMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
releaseGestureWakeLock();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
mNotificationPanelViewController.resetAlpha();
mNotificationPanelViewController.resetTranslation();
mNotificationPanelViewController.resetViewGroupFade();
@@ -3230,7 +3235,7 @@
@Override
public void endAffordanceLaunch() {
releaseGestureWakeLock();
- mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mCameraLauncherLazy.get().setLaunchingAffordance(false);
}
/**
@@ -3503,7 +3508,7 @@
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 @@
.updateSensitivenessForOccludedWakeup();
}
if (mLaunchCameraWhenFinishedWaking) {
- mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource);
+ mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
+ mNotificationPanelViewController.isFullyCollapsed());
mLaunchCameraWhenFinishedWaking = false;
}
if (mLaunchEmergencyActionWhenFinishedWaking) {
@@ -3795,8 +3801,7 @@
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 d328472..aa0757e 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 @@
* @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 @@
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 86e27ab..d54a863 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 @@
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 @@
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 e9d4e55..681d21b 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 @@
@Override
public void onFullyHidden() {
mPrimaryBouncerAnimating = false;
+ updateStates();
}
@Override
@@ -1183,12 +1184,16 @@
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 @@
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 60bd038..501467f 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 @@
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 @@
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 4cb41f3..8270336 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 @@
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 @@
// 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/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 903aba1..2c64fe1 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 @@
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 @@
&& 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 5d2b0ca..8290084 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 @@
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 b369098..ffd95f4 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.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 @@
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 9eff704..b3d1c8f 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.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 @@
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 d9efdea..8bcfe6f 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 @@
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 1bd14e5..1d2b09c 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 @@
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/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 45b8ce1..12c2bbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -175,6 +175,25 @@
}
@Test
+ fun testFocusLossAfterRotating() {
+ val container = initializeFingerprintContainer()
+ waitForIdleSync()
+
+ val requestID = authContainer?.requestId ?: 0L
+
+ verify(callback).onDialogAnimatedIn(requestID)
+ container.onOrientationChanged()
+ container.onWindowFocusChanged(false)
+ waitForIdleSync()
+
+ verify(callback, never()).onDismissed(
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(requestID)
+ )
+ }
+
+ @Test
fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
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 db41d8d..dedc723 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 @@
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 @@
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 @@
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 @@
}
}
- controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
+ controller = ControlsListingControllerImpl(
+ wrapper,
+ executor,
+ { mockSL },
+ userTracker,
+ dumpManager,
+ featureFlags
+ )
verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
}
@@ -123,9 +151,16 @@
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 @@
@Test
fun testCallbackGetsList() {
- val list = listOf(serviceInfo)
+ val list = listOf(ServiceInfo(componentName))
controller.addCallback(mockCallback)
controller.addCallback(mockCallbackOther)
@@ -188,6 +223,8 @@
@Test
fun testChangeUserSendsCorrectServiceUpdate() {
+ val serviceInfo = ServiceInfo(componentName)
+
val list = listOf(serviceInfo)
controller.addCallback(mockCallback)
@@ -223,4 +260,284 @@
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 4d66a16..0000000
--- 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 ade83cf..6ba0634 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 @@
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 @@
underTest =
KeyguardRepositoryImpl(
- statusBarStateController,
- dozeHost,
- wakefulnessLifecycle,
- biometricUnlockController,
- keyguardStateController,
- keyguardUpdateMonitor,
+ statusBarStateController,
+ dozeHost,
+ wakefulnessLifecycle,
+ biometricUnlockController,
+ keyguardStateController,
+ keyguardUpdateMonitor,
)
}
@@ -257,6 +258,48 @@
}
@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 27d5d0a..2b03722 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 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 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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
}
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 c85f7b9..21e5068 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 @@
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/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
new file mode 100644
index 0000000..0aa3621
--- /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 1f71e3c..7d2251e 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.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 @@
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 c658593..77c690a 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 @@
}
@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 0000000..62b4e7b
--- /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 7ddfde3..0000000
--- 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 0000000..a226ded
--- /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 9c65fac..9c870b5 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.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 @@
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 @@
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 4bed4a1..1d11226 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 @@
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.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 org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -329,6 +341,57 @@
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 0000000..9e73487
--- /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 5ebaf69..d5bfe1f 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.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 java.util.Optional;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@@ -84,6 +87,7 @@
@Mock private Vibrator mVibrator;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private SystemBarAttributesListener mSystemBarAttributesListener;
+ @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -115,7 +119,8 @@
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 5ad1431..41912f5 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.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 @@
@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 @@
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 @@
mActivityLaunchAnimator,
mJankMonitor,
mDeviceStateManager,
- mWiredChargingRippleController, mDreamManager) {
+ mWiredChargingRippleController,
+ mDreamManager,
+ mCameraLauncherLazy) {
@Override
protected ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -893,7 +899,7 @@
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 @@
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 df48e1d..808abc8 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 @@
}
@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 6d8d902..a052008 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 9dea48e..8572478 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -123,27 +123,37 @@
}
@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).isTrue()
+ }
+
+ @Test
+ fun displayView_wakeLockCanBeReleasedAfterTimeOut() {
+ underTest.displayView(getState())
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
assertThat(fakeWakeLock.isHeld).isFalse()
}
@Test
- fun displayView_screenOff_wakeLockCanBeReleasedAfterTimeOut() {
+ fun displayView_removeView_wakeLockCanBeReleased() {
underTest.displayView(getState())
assertThat(fakeWakeLock.isHeld).isTrue()
- fakeClock.advanceTime(TIMEOUT_MS + 1)
+ underTest.removeView("test reason")
assertThat(fakeWakeLock.isHeld).isFalse()
}
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 6f70f0e..a798f40 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 @@
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 @@
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 e40f001..933d259 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -473,18 +473,6 @@
}
/**
- * 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 953e850..c3cd135 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -82,10 +82,6 @@
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 @@
/**
* 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 @@
/**
* 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 @@
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 2761a86..af6eaf4 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 @@
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/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 9d96008..dfac82c 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -141,7 +141,8 @@
*/
public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES;
private static final String KEY_MAX_RUNNING_PROCESS_QUEUES = "bcast_max_running_process_queues";
- private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES = 4;
+ private static final int DEFAULT_MAX_RUNNING_PROCESS_QUEUES =
+ ActivityManager.isLowRamDeviceStatic() ? 2 : 4;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
@@ -150,7 +151,8 @@
*/
public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts";
- private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
+ private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS =
+ ActivityManager.isLowRamDeviceStatic() ? 8 : 16;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of pending
@@ -159,7 +161,8 @@
*/
public int MAX_PENDING_BROADCASTS = DEFAULT_MAX_PENDING_BROADCASTS;
private static final String KEY_MAX_PENDING_BROADCASTS = "bcast_max_pending_broadcasts";
- private static final int DEFAULT_MAX_PENDING_BROADCASTS = 256;
+ private static final int DEFAULT_MAX_PENDING_BROADCASTS =
+ ActivityManager.isLowRamDeviceStatic() ? 128 : 256;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to normal
@@ -167,7 +170,7 @@
*/
public long DELAY_NORMAL_MILLIS = DEFAULT_DELAY_NORMAL_MILLIS;
private static final String KEY_DELAY_NORMAL_MILLIS = "bcast_delay_normal_millis";
- private static final long DEFAULT_DELAY_NORMAL_MILLIS = 0;
+ private static final long DEFAULT_DELAY_NORMAL_MILLIS = +500;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts
@@ -175,7 +178,7 @@
*/
public long DELAY_CACHED_MILLIS = DEFAULT_DELAY_CACHED_MILLIS;
private static final String KEY_DELAY_CACHED_MILLIS = "bcast_delay_cached_millis";
- private static final long DEFAULT_DELAY_CACHED_MILLIS = +30_000;
+ private static final long DEFAULT_DELAY_CACHED_MILLIS = +120_000;
/**
* For {@link BroadcastQueueModernImpl}: Delay to apply to urgent
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c3839a9..3dee262 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.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 @@
}
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 @@
// If nothing to dispatch, send any pending result immediately
if (r.receivers.isEmpty()) {
scheduleResultTo(r);
+ notifyFinishBroadcast(r);
}
traceEnd(cookie);
@@ -746,8 +752,11 @@
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 @@
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 @@
final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
- mService.notifyBroadcastFinishedLocked(r);
- mHistory.addBroadcastToHistoryLocked(r);
+ notifyFinishBroadcast(r);
+ }
+ }
- r.finishTime = SystemClock.uptimeMillis();
- r.nextReceiver = r.receivers.size();
- BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
+ mService.notifyBroadcastFinishedLocked(r);
+ mHistory.addBroadcastToHistoryLocked(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++;
- }
+ 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 cbf0aae..2d7b0dc 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 @@
"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 @@
@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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 @@
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 @@
KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
mUseFreezer = isFreezerSupported();
updateFreezerDebounceTimeout();
+ updateFreezerExemptInstPkg();
} else {
mUseFreezer = false;
}
@@ -1194,6 +1211,15 @@
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 60e6754..ea3c8dc 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 @@
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 42bfc4c..ecea96e 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 @@
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 8049a3a..9213327 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 @@
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 @@
/** @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 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
// 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 @@
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 @@
&& 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 @@
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 @@
}
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 @@
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 @@
+ " 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 @@
== 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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d35d193..78b697d 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 @@
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 dcec0aa..2669d21 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.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 @@
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 56ec8e4..9155830 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 @@
}
/**
+ * 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 @@
* <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 @@
* 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 @@
* 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 0eff9e9..d255669 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.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.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 @@
@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 @@
@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 @@
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 @@
}
@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 @@
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 @@
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 @@
} // synchronized (mPackagesLock)
pw.println();
- mUserVisibilityMediator.dump(pw);
+ mUserVisibilityMediator.dump(pw, args);
pw.println();
// Dump some capabilities
@@ -6250,6 +6257,9 @@
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 @@
}
@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 @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 f725c48..0015fad 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_CURRENT;
+import static android.os.UserHandle.USER_NULL;
+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.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 @@
* <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/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;
+ // NOTE: it's set as USER_CURRENT instead of USER_NULL because NO_PROFILE_GROUP_ID has the same
+ // falue of USER_NULL, which would complicate some checks (especially on unit tests)
+ @VisibleForTesting
+ static final int INITIAL_CURRENT_USER_ID = USER_CURRENT;
+
+ 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;
+
+ @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");
+ 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 (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");
}
- 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;
- }
+ return;
}
if (!mUsersOnSecondaryDisplaysEnabled) {
@@ -119,24 +203,21 @@
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 @@
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 @@
}
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);
- if (mUsersOnSecondaryDisplaysEnabled) {
- ipw.print("Users on secondary displays: ");
- synchronized (mLock) {
- ipw.println(mUsersOnSecondaryDisplays);
+ 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();
+ }
+
+ 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 @@
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;
}
- private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
- return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ /**
+ * 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 isProfileUnchecked(@UserIdInt int userId) {
- return mService.isProfileUnchecked(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 @UserIdInt int getProfileParentId(@UserIdInt int userId) {
- return mService.getProfileParentId(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 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 8582f54..2d76c50 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 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 7dc4f97..b8cd8d9 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 @@
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 @@
false /* fromUser */, wallpaper, null /* reply */);
}
}
+ t.traceEnd();
}
void disconnectLocked() {
@@ -1322,6 +1325,8 @@
@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 @@
mContext.getMainThreadHandler().removeCallbacks(mDisconnectRunnable);
}
}
+ t.traceEnd();
}
@Override
@@ -1545,6 +1551,8 @@
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 @@
} finally {
Binder.restoreCallingIdentity(ident);
}
+ t.traceEnd();
mReply = null;
}
}
@@ -3058,6 +3067,8 @@
return true;
}
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.bindWallpaperComponentLocked-" + componentName);
try {
if (componentName == null) {
componentName = mDefaultWallpaperComponent;
@@ -3190,6 +3201,8 @@
}
Slog.w(TAG, msg);
return false;
+ } finally {
+ t.traceEnd();
}
return true;
}
@@ -3234,7 +3247,10 @@
}
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 @@
}
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 @@
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 d0c381e..21b241a 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 @@
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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ea82417..74a236b 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 @@
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/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java b/services/tests/mockingservicestests/src/com/android/server/DumpableDumperRule.java
new file mode 100644
index 0000000..33275bd
--- /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 9aa28ce..c0b5070 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 org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -38,6 +39,9 @@
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 21f541f..923c3e3 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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@Test
public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- addDefaultProfileAndParent();
startDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
@@ -212,7 +203,6 @@
@Test
public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- addDefaultProfileAndParent();
stopDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
@@ -223,7 +213,6 @@
@Test
public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() {
- addDefaultProfileAndParent();
startDefaultProfile();
mockCurrentUser(PARENT_USER_ID);
@@ -285,19 +274,6 @@
.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 7ae8117..7af5f5d 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 @@
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 22e6e0d..7b20092 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 @@
* 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 @@
*/
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 @@
@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 @@
@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 @@
@Test
public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
startDefaultProfile();
@@ -174,7 +277,6 @@
@Test
public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
stopDefaultProfile();
@@ -184,18 +286,14 @@
@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 @@
@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 @@
@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 @@
@Test
public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
mockCurrentUser(PARENT_USER_ID);
stopDefaultProfile();
@@ -296,28 +386,96 @@
.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/Android.bp b/services/tests/servicestests/Android.bp
index 61bb57e..9386a23 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -204,10 +204,10 @@
":FrameworksServicesTests_install_uses_sdk_q0",
":FrameworksServicesTests_install_uses_sdk_q0_r0",
":FrameworksServicesTests_install_uses_sdk_r0",
- ":FrameworksServicesTests_install_uses_sdk_r5",
+ ":FrameworksServicesTests_install_uses_sdk_r1000",
":FrameworksServicesTests_install_uses_sdk_r_none",
":FrameworksServicesTests_install_uses_sdk_r0_s0",
- ":FrameworksServicesTests_install_uses_sdk_r0_s5",
+ ":FrameworksServicesTests_install_uses_sdk_r0_s1000",
":FrameworksServicesTests_keyset_permdef_sa_unone",
":FrameworksServicesTests_keyset_permuse_sa_ua_ub",
":FrameworksServicesTests_keyset_permuse_sb_ua_ub",
diff --git a/services/tests/servicestests/apks/install_uses_sdk/Android.bp b/services/tests/servicestests/apks/install_uses_sdk/Android.bp
index a51293d..2894395 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/Android.bp
+++ b/services/tests/servicestests/apks/install_uses_sdk/Android.bp
@@ -32,9 +32,9 @@
}
android_test_helper_app {
- name: "FrameworksServicesTests_install_uses_sdk_r5",
+ name: "FrameworksServicesTests_install_uses_sdk_r1000",
defaults: ["FrameworksServicesTests_apks_defaults"],
- manifest: "AndroidManifest-r5.xml",
+ manifest: "AndroidManifest-r1000.xml",
}
android_test_helper_app {
@@ -44,9 +44,9 @@
}
android_test_helper_app {
- name: "FrameworksServicesTests_install_uses_sdk_r0_s5",
+ name: "FrameworksServicesTests_install_uses_sdk_r0_s1000",
defaults: ["FrameworksServicesTests_apks_defaults"],
- manifest: "AndroidManifest-r0-s5.xml",
+ manifest: "AndroidManifest-r0-s1000.xml",
}
android_test_helper_app {
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
similarity index 97%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
rename to services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
index bafe4c4..25743b8 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s5.xml
+++ b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r0-s1000.xml
@@ -19,7 +19,7 @@
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This fails because 31 is not version 5 -->
<extension-sdk android:sdkVersion="30" android:minExtensionVersion="0" />
- <extension-sdk android:sdkVersion="31" android:minExtensionVersion="5" />
+ <extension-sdk android:sdkVersion="31" android:minExtensionVersion="1000" />
</uses-sdk>
<application>
diff --git a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r1000.xml
similarity index 97%
rename from services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
rename to services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r1000.xml
index 7723d05..9bf9254 100644
--- a/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r5.xml
+++ b/services/tests/servicestests/apks/install_uses_sdk/AndroidManifest-r1000.xml
@@ -18,7 +18,7 @@
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
<!-- This will fail to install, because minExtensionVersion is not met -->
- <extension-sdk android:sdkVersion="30" android:minExtensionVersion="5" />
+ <extension-sdk android:sdkVersion="30" android:minExtensionVersion="1000" />
</uses-sdk>
<application>
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 935d1d8..80cee50 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_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 @@
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 @@
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 @@
}
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 @@
}
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 @@
}
@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 f138311..808130a 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 @@
}
@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 @@
.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/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 699601b..8e6c014 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -575,10 +575,11 @@
assertEquals(0, minExtVers.get(31, -1));
Map<Pair<String, Integer>, Integer> appToError = new HashMap<>();
- appToError.put(Pair.create("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5),
+ appToError.put(Pair.create("install_uses_sdk.apk_r1000", R.raw.install_uses_sdk_r1000),
PackageManager.INSTALL_FAILED_OLDER_SDK);
- appToError.put(Pair.create("install_uses_sdk.apk_r0_s5", R.raw.install_uses_sdk_r0_s5),
- PackageManager.INSTALL_FAILED_OLDER_SDK);
+ appToError.put(
+ Pair.create("install_uses_sdk.apk_r0_s1000", R.raw.install_uses_sdk_r0_s1000),
+ PackageManager.INSTALL_FAILED_OLDER_SDK);
appToError.put(Pair.create("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0),
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED);
@@ -595,7 +596,7 @@
int result = entry.getValue();
try {
parsePackage(filename, resId, x -> x);
- expect.withMessage("Expected parsing error %d from %s", result, filename).fail();
+ expect.withMessage("Expected parsing error %s from %s", result, filename).fail();
} catch (PackageManagerException expected) {
expect.that(expected.error).isEqualTo(result);
}
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 c906abc..e5842b4 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.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.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.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.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 @@
}
@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 7f5beb1..4fd2b78 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 @@
@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 @@
@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 void setEstimatedLaunchTime(String packageName, long estimatedLaunchTime,
int userId) {
+ super.setEstimatedLaunchTime_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
@@ -2506,6 +2512,8 @@
@Override
public void setEstimatedLaunchTimes(ParceledListSlice estimatedLaunchTimes, int userId) {
+ super.setEstimatedLaunchTimes_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
UsageStatsService.this
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 453c656..936fad5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8615,7 +8615,12 @@
*
* Used to trade privacy/security against potentially reduced carrier coverage for some
* carriers.
+ *
+ * @deprecated Future versions of Android will disallow carriers from hiding this toggle
+ * because disabling 2g is a security feature that users should always have access to at
+ * their discretion.
*/
+ @Deprecated
public static final String KEY_HIDE_ENABLE_2G = "hide_enable_2g_bool";
/**
@@ -8780,7 +8785,7 @@
* 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 bfa60ba..6be2f77 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,13 +138,6 @@
*/
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 @@
}
/**
- * @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 51a7840..e099e69 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17324,14 +17324,14 @@
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 @@
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 @@
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 73aff43..a834e2bb 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -468,14 +468,14 @@
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 @@
@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 @@
/**
* 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 @NonNull Builder setQosBearerSessions(
@NonNull List<QosBearerSession> qosBearerSessions) {
+ Objects.requireNonNull(qosBearerSessions);
mQosBearerSessions = qosBearerSessions;
return this;
}
@@ -891,6 +917,7 @@
*/
public @NonNull Builder setTrafficDescriptors(
@NonNull List<TrafficDescriptor> trafficDescriptors) {
+ Objects.requireNonNull(trafficDescriptors);
mTrafficDescriptors = trafficDescriptors;
return this;
}
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 dd9b294..afaeca1 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 @@
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 @@
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 @@
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 @@
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);
}
}