Merge "Add a helper script to apply lint fixes"
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index 3a23b54..bc8fc53 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -59,9 +59,8 @@
Typeface.loadPreinstalledSystemFontMap();
}
- @ManualBenchmarkState.ManualBenchmarkTest(
- warmupDurationNs = WARMUP_DURATION_NS,
- targetTestDurationNs = TARGET_TEST_DURATION_NS)
+ // testSerializeFontMap uses the default targetTestDurationNs, which is much longer than
+ // TARGET_TEST_DURATION_NS, in order to stabilize test results.
@Test
public void testSerializeFontMap() throws Exception {
Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
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 794362b..4d5eef2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -310,11 +310,25 @@
*/
boolean mReportedActive;
+ /**
+ * Track the most recently completed jobs (that had been executing and were stopped for any
+ * reason, including successful completion).
+ */
private int mLastCompletedJobIndex = 0;
private final JobStatus[] mLastCompletedJobs = new JobStatus[NUM_COMPLETED_JOB_HISTORY];
private final long[] mLastCompletedJobTimeElapsed = new long[NUM_COMPLETED_JOB_HISTORY];
/**
+ * Track the most recently cancelled jobs (that had internal reason
+ * {@link JobParameters#INTERNAL_STOP_REASON_CANCELED}.
+ */
+ private int mLastCancelledJobIndex = 0;
+ private final JobStatus[] mLastCancelledJobs =
+ new JobStatus[DEBUG ? NUM_COMPLETED_JOB_HISTORY : 0];
+ private final long[] mLastCancelledJobTimeElapsed =
+ new long[DEBUG ? NUM_COMPLETED_JOB_HISTORY : 0];
+
+ /**
* A mapping of which uids are currently in the foreground to their effective bias.
*/
final SparseIntArray mUidBiasOverride = new SparseIntArray();
@@ -1398,6 +1412,12 @@
startTrackingJobLocked(incomingJob, cancelled);
}
reportActiveLocked();
+ if (mLastCancelledJobs.length > 0
+ && internalReasonCode == JobParameters.INTERNAL_STOP_REASON_CANCELED) {
+ mLastCancelledJobs[mLastCancelledJobIndex] = cancelled;
+ mLastCancelledJobTimeElapsed[mLastCancelledJobIndex] = sElapsedRealtimeClock.millis();
+ mLastCancelledJobIndex = (mLastCancelledJobIndex + 1) % mLastCancelledJobs.length;
+ }
}
void updateUidState(int uid, int procState) {
@@ -2555,9 +2575,9 @@
}
private boolean isComponentUsable(@NonNull JobStatus job) {
- final ServiceInfo service = job.serviceInfo;
+ final String processName = job.serviceProcessName;
- if (service == null) {
+ if (processName == null) {
if (DEBUG) {
Slog.v(TAG, "isComponentUsable: " + job.toShortString()
+ " component not present");
@@ -2566,8 +2586,7 @@
}
// Everything else checked out so far, so this is the final yes/no check
- final boolean appIsBad = mActivityManagerInternal.isAppBad(
- service.processName, service.applicationInfo.uid);
+ final boolean appIsBad = mActivityManagerInternal.isAppBad(processName, job.getUid());
if (DEBUG && appIsBad) {
Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable");
}
@@ -3848,6 +3867,38 @@
pw.decreaseIndent();
pw.println();
+ boolean recentCancellationsPrinted = false;
+ for (int r = 1; r <= mLastCancelledJobs.length; ++r) {
+ // Print most recent first
+ final int idx = (mLastCancelledJobIndex + mLastCancelledJobs.length - r)
+ % mLastCancelledJobs.length;
+ job = mLastCancelledJobs[idx];
+ if (job != null) {
+ if (!predicate.test(job)) {
+ continue;
+ }
+ if (!recentCancellationsPrinted) {
+ pw.println();
+ pw.println("Recently cancelled jobs:");
+ pw.increaseIndent();
+ recentCancellationsPrinted = true;
+ }
+ TimeUtils.formatDuration(mLastCancelledJobTimeElapsed[idx], nowElapsed, pw);
+ pw.println();
+ // Double indent for readability
+ pw.increaseIndent();
+ pw.increaseIndent();
+ pw.println(job.toShortString());
+ job.dump(pw, true, nowElapsed);
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ }
+ if (!recentCancellationsPrinted) {
+ pw.decreaseIndent();
+ pw.println();
+ }
+
if (filterUid == -1) {
pw.println();
pw.print("mReadyToRock="); pw.println(mReadyToRock);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 59cd82e..3bbc5a3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -176,12 +176,13 @@
Slog.d(TAG, "maybeReportNewChargingStateLocked: "
+ powerConnected + "/" + stablePower + "/" + batteryNotLow);
}
- mFlexibilityController.setConstraintSatisfied(
- JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging());
- mFlexibilityController
- .setConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow);
-
final long nowElapsed = sElapsedRealtimeClock.millis();
+
+ mFlexibilityController.setConstraintSatisfied(
+ JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging(), nowElapsed);
+ mFlexibilityController.setConstraintSatisfied(
+ JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow, nowElapsed);
+
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.valueAt(i);
if (ts.hasChargingConstraint()) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index aca381f..9b59560 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -93,7 +93,12 @@
}
};
- private final SparseArrayMap<ComponentName, ServiceInfo> mServiceInfoCache =
+ /**
+ * Cache containing the processName of the ServiceInfo (see {@link ServiceInfo#processName})
+ * if the Service exists and is available.
+ * {@code null} will be stored if the service is currently unavailable.
+ */
+ private final SparseArrayMap<ComponentName, String> mServiceProcessCache =
new SparseArrayMap<>();
private final ComponentStateUpdateFunctor mComponentStateUpdateFunctor =
@@ -135,18 +140,18 @@
@Override
@GuardedBy("mLock")
public void onUserRemovedLocked(int userId) {
- mServiceInfoCache.delete(userId);
+ mServiceProcessCache.delete(userId);
}
@Nullable
@GuardedBy("mLock")
- private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) {
+ private String getServiceProcessLocked(JobStatus jobStatus) {
final ComponentName service = jobStatus.getServiceComponent();
final int userId = jobStatus.getUserId();
- if (mServiceInfoCache.contains(userId, service)) {
+ if (mServiceProcessCache.contains(userId, service)) {
// Return whatever is in the cache, even if it's null. When something changes, we
// clear the cache.
- return mServiceInfoCache.get(userId, service);
+ return mServiceProcessCache.get(userId, service);
}
ServiceInfo si;
@@ -165,30 +170,31 @@
// Write null to the cache so we don't keep querying PM.
si = null;
}
- mServiceInfoCache.add(userId, service, si);
+ final String processName = si == null ? null : si.processName;
+ mServiceProcessCache.add(userId, service, processName);
- return si;
+ return processName;
}
@GuardedBy("mLock")
private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) {
- final ServiceInfo service = getServiceInfoLocked(jobStatus);
+ final String processName = getServiceProcessLocked(jobStatus);
- if (DEBUG && service == null) {
+ if (DEBUG && processName == null) {
Slog.v(TAG, jobStatus.toShortString() + " component not present");
}
- final ServiceInfo ogService = jobStatus.serviceInfo;
- jobStatus.serviceInfo = service;
- return !Objects.equals(ogService, service);
+ final String ogProcess = jobStatus.serviceProcessName;
+ jobStatus.serviceProcessName = processName;
+ return !Objects.equals(ogProcess, processName);
}
@GuardedBy("mLock")
private void clearComponentsForPackageLocked(final int userId, final String pkg) {
- final int uIdx = mServiceInfoCache.indexOfKey(userId);
- for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
- final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c);
+ final int uIdx = mServiceProcessCache.indexOfKey(userId);
+ for (int c = mServiceProcessCache.numElementsForKey(userId) - 1; c >= 0; --c) {
+ final ComponentName cn = mServiceProcessCache.keyAt(uIdx, c);
if (cn.getPackageName().equals(pkg)) {
- mServiceInfoCache.delete(userId, cn);
+ mServiceProcessCache.delete(userId, cn);
}
}
}
@@ -207,7 +213,7 @@
private void updateComponentStateForUser(final int userId) {
synchronized (mLock) {
- mServiceInfoCache.delete(userId);
+ mServiceProcessCache.delete(userId);
updateComponentStatesLocked(jobStatus -> {
// Using user ID instead of source user ID because the service will run under the
// user ID, not source user ID.
@@ -247,15 +253,15 @@
@Override
@GuardedBy("mLock")
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
- for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) {
- final int userId = mServiceInfoCache.keyAt(u);
- for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) {
- final ComponentName componentName = mServiceInfoCache.keyAt(u, p);
+ for (int u = 0; u < mServiceProcessCache.numMaps(); ++u) {
+ final int userId = mServiceProcessCache.keyAt(u);
+ for (int p = 0; p < mServiceProcessCache.numElementsForKey(userId); ++p) {
+ final ComponentName componentName = mServiceProcessCache.keyAt(u, p);
pw.print(userId);
pw.print("-");
pw.print(componentName);
pw.print(": ");
- pw.print(mServiceInfoCache.valueAt(u, p));
+ pw.print(mServiceProcessCache.valueAt(u, p));
pw.println();
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 2e41dfd..3ca1ad5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -137,9 +137,9 @@
new PrefetchController.PrefetchChangedListener() {
@Override
public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId,
- String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+ String pkgName, long prevEstimatedLaunchTime,
+ long newEstimatedLaunchTime, long nowElapsed) {
synchronized (mLock) {
- final long nowElapsed = sElapsedRealtimeClock.millis();
final long prefetchThreshold =
mPrefetchController.getLaunchTimeThresholdMs();
boolean jobWasInPrefetchWindow = prevEstimatedLaunchTime
@@ -158,8 +158,8 @@
if (!js.hasFlexibilityConstraint()) {
continue;
}
- mFlexibilityTracker.resetJobNumDroppedConstraints(js);
- mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+ mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+ mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
}
}
}
@@ -191,7 +191,7 @@
js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
final long nowElapsed = sElapsedRealtimeClock.millis();
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
- mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+ mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
}
}
@@ -239,7 +239,7 @@
* Changes flexibility constraint satisfaction for affected jobs.
*/
@VisibleForTesting
- void setConstraintSatisfied(int constraint, boolean state) {
+ void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) {
synchronized (mLock) {
final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
if (old == state) {
@@ -255,8 +255,6 @@
// The rest did not have a change in state and are still satisfied or unsatisfied.
final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied);
- final long nowElapsed = sElapsedRealtimeClock.millis();
-
// In order to get the range of all potentially satisfied jobs, we start at the number
// of satisfied system-wide constraints and iterate to the max number of potentially
// satisfied constraints, determined by how many job-specific constraints exist.
@@ -329,10 +327,9 @@
@VisibleForTesting
@GuardedBy("mLock")
- int getCurPercentOfLifecycleLocked(JobStatus js) {
+ int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
final long latest = getLifeCycleEndElapsedLocked(js, earliest);
- final long nowElapsed = sElapsedRealtimeClock.millis();
if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
return 0;
}
@@ -414,8 +411,8 @@
.getJobsByNumRequiredConstraints(j);
for (int i = 0; i < jobs.size(); i++) {
JobStatus js = jobs.valueAt(i);
- mFlexibilityTracker.resetJobNumDroppedConstraints(js);
- mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+ mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
+ mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
if (js.setFlexibilityConstraintSatisfied(
nowElapsed, isFlexibilitySatisfiedLocked(js))) {
changedJobs.add(js);
@@ -479,8 +476,8 @@
mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
}
- public void resetJobNumDroppedConstraints(JobStatus js) {
- final int curPercent = getCurPercentOfLifecycleLocked(js);
+ public void resetJobNumDroppedConstraints(JobStatus js, long nowElapsed) {
+ final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
int toDrop = 0;
final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
+ (js.getPreferUnmetered() ? 1 : 0);
@@ -489,7 +486,8 @@
toDrop++;
}
}
- adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop);
+ adjustJobsRequiredConstraints(
+ js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed);
}
/** Returns all tracked jobs. */
@@ -502,13 +500,12 @@
* Returns false if the job status's number of flexible constraints is now 0.
* Jobs with 0 required flexible constraints are removed from the tracker.
*/
- public boolean adjustJobsRequiredConstraints(JobStatus js, int n) {
+ public boolean adjustJobsRequiredConstraints(JobStatus js, int n, long nowElapsed) {
if (n == 0) {
return false;
}
remove(js);
js.adjustNumRequiredFlexibleConstraints(n);
- final long nowElapsed = sElapsedRealtimeClock.millis();
js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
if (js.getNumRequiredFlexibleConstraints() <= 0) {
maybeStopTrackingJobLocked(js, null, false);
@@ -553,7 +550,7 @@
return js.getSourceUserId() == userId;
}
- public void scheduleDropNumConstraintsAlarm(JobStatus js) {
+ public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) {
long nextTimeElapsed;
synchronized (mLock) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
@@ -567,7 +564,7 @@
if (latest - nextTimeElapsed < mDeadlineProximityLimitMs) {
mFlexibilityTracker.adjustJobsRequiredConstraints(
- js, -js.getNumRequiredFlexibleConstraints());
+ js, -js.getNumRequiredFlexibleConstraints(), nowElapsed);
return;
}
addAlarm(js, nextTimeElapsed);
@@ -578,21 +575,21 @@
protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
synchronized (mLock) {
ArraySet<JobStatus> changedJobs = new ArraySet<>();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
for (int i = 0; i < expired.size(); i++) {
JobStatus js = expired.valueAt(i);
boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
final long earliest = getLifeCycleBeginningElapsedLocked(js);
final long latest = getLifeCycleEndElapsedLocked(js, earliest);
- final long nowElapsed = sElapsedRealtimeClock.millis();
if (latest - nowElapsed < mDeadlineProximityLimitMs) {
mFlexibilityTracker.adjustJobsRequiredConstraints(js,
- -js.getNumRequiredFlexibleConstraints());
+ -js.getNumRequiredFlexibleConstraints(), nowElapsed);
} else {
long nextTimeElapsed =
getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
- if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1)
+ if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)
&& nextTimeElapsed != NO_LIFECYCLE_END) {
mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
index d5750f8..dd06217 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
@@ -95,9 +95,10 @@
*/
@Override
public void reportNewIdleState(boolean isIdle) {
- mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_IDLE, isIdle);
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
+ mFlexibilityController.setConstraintSatisfied(
+ JobStatus.CONSTRAINT_IDLE, isIdle, nowElapsed);
for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(nowElapsed, isIdle);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 57c7317..4ce6b321 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -35,7 +35,6 @@
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
-import android.content.pm.ServiceInfo;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.Uri;
@@ -356,7 +355,7 @@
public ArraySet<Uri> changedUris;
public ArraySet<String> changedAuthorities;
public Network network;
- public ServiceInfo serviceInfo;
+ public String serviceProcessName;
/** The evaluated bias of the job when it started running. */
public int lastEvaluatedBias;
@@ -1757,7 +1756,7 @@
// run if its constraints are satisfied).
// DeviceNotDozing implicit constraint must be satisfied
// NotRestrictedInBackground implicit constraint must be satisfied
- return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceInfo != null)
+ return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceProcessName != null)
&& (mReadyDeadlineSatisfied || isConstraintsSatisfied(satisfiedConstraints));
}
@@ -2296,7 +2295,7 @@
pw.println(mReadyDynamicSatisfied);
}
pw.print("readyComponentEnabled: ");
- pw.println(serviceInfo != null);
+ pw.println(serviceProcessName != null);
if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) {
pw.print("expeditedQuotaApproved: ");
pw.print(mExpeditedQuotaApproved);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index 0945b7e..e04cec3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -105,7 +105,7 @@
public interface PrefetchChangedListener {
/** Callback to inform listeners when estimated launch times change. */
void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName,
- long prevEstimatedLaunchTime, long newEstimatedLaunchTime);
+ long prevEstimatedLaunchTime, long newEstimatedLaunchTime, long nowElapsed);
}
@SuppressWarnings("FieldCanBeLocal")
@@ -308,8 +308,9 @@
final long nowElapsed = sElapsedRealtimeClock.millis();
updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
for (int i = 0; i < mPrefetchChangedListeners.size(); i++) {
- mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs,
- userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime);
+ mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(
+ jobs, userId, pkgName, prevEstimatedLaunchTime,
+ newEstimatedLaunchTime, nowElapsed);
}
if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
mStateChangedListener.onControllerStateChanged(jobs);
diff --git a/core/api/current.txt b/core/api/current.txt
index 32bdec0..e043339 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12448,6 +12448,16 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SigningInfo> CREATOR;
}
+ public final class UserProperties implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getShowInLauncher();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
+ field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
+ field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
+ }
+
public final class VersionedPackage implements android.os.Parcelable {
ctor public VersionedPackage(@NonNull String, int);
ctor public VersionedPackage(@NonNull String, long);
@@ -16903,6 +16913,7 @@
method public int describeContents();
method public int getFormat();
method public int getHeight();
+ method public long getId();
method public int getLayers();
method public long getUsage();
method public int getWidth();
@@ -32250,6 +32261,7 @@
method public android.os.UserHandle getUserForSerialNumber(long);
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
+ 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 public boolean hasUserRestriction(String);
@@ -51967,6 +51979,7 @@
method public boolean isTextEntryKey();
method public boolean isTextSelectable();
method public boolean isVisibleToUser();
+ method public void makeQueryableFromAppProcess(@NonNull android.view.View);
method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 610cb94..e890005 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -172,6 +172,7 @@
method @NonNull public static android.media.BluetoothProfileConnectionInfo createA2dpSinkInfo(int);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createHearingAidInfo(boolean);
method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioInfo(boolean, boolean);
+ method @NonNull public static android.media.BluetoothProfileConnectionInfo createLeAudioOutputInfo(boolean, int);
method public int describeContents();
method public int getProfile();
method public int getVolume();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9a3c8a0..fefdfd8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -131,6 +131,7 @@
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -1875,6 +1876,7 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method public static boolean isSplitSystemUser();
+ method public static boolean isUsersOnSecondaryDisplaysEnabled();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
@@ -3331,11 +3333,12 @@
ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
method @NonNull public java.util.concurrent.Executor getExecutor();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
- method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
- method public void onTaskFragmentError(@NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
- method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
- method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
- method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
+ method public void onActivityReparentedToTask(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.Intent, @NonNull android.os.IBinder);
+ method public void onTaskFragmentAppeared(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
+ method public void onTaskFragmentError(@NonNull android.window.WindowContainerTransaction, @NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
+ method public void onTaskFragmentInfoChanged(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
+ method public void onTaskFragmentParentInfoChanged(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.res.Configuration);
+ method public void onTaskFragmentVanished(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
method @CallSuper public void registerOrganizer();
method @CallSuper public void unregisterOrganizer();
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 449729e..182b0a3 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4336,13 +4336,42 @@
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public boolean switchUser(@NonNull UserHandle user) {
- if (user == null) {
- throw new IllegalArgumentException("UserHandle cannot be null.");
- }
+ Preconditions.checkNotNull(user, "UserHandle cannot be null.");
+
return switchUser(user.getIdentifier());
}
/**
+ * Starts the given user in background and associate the user with the given display.
+ *
+ * <p>This method will allow the user to launch activities on that display, and it's typically
+ * used only on automotive builds when the vehicle has multiple displays (you can verify if it's
+ * supported by calling {@link UserManager#isBackgroundUsersOnSecondaryDisplaysSupported()}).
+ *
+ * @return whether the user was started.
+ *
+ * @throws UnsupportedOperationException if the device does not support background users on
+ * secondary displays.
+ * @throws IllegalArgumentException if the display does not exist.
+ * @throws IllegalStateException if the user cannot be started on that display (for example, if
+ * there's already a user using that display or if the user is already associated with other
+ * display).
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
+ int displayId) {
+ try {
+ return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the message that is shown when a user is switched from.
*
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 9210958..419b8e1 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -47,6 +47,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiFunction;
/**
* Activity manager local system service interface.
@@ -623,6 +624,11 @@
* broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are
* automatically allowlisted.
*
+ * @param filterExtrasForReceiver A function to filter intent extras for the given receiver by
+ * using the rules of package visibility. Returns extras with legitimate package info that the
+ * receiver is able to access, or {@code null} if none of the packages is visible to the
+ * receiver.
+ *
* @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
* IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
* String[], int, Bundle, boolean, boolean, int)
@@ -630,7 +636,9 @@
public abstract int broadcastIntent(Intent intent,
IIntentReceiver resultTo,
String[] requiredPermissions, boolean serialized,
- int userId, int[] appIdAllowList, @Nullable Bundle bOptions);
+ int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions);
/**
* Add uid to the ActivityManagerService PendingStartActivityUids list.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6b3dc82..b383d7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -107,7 +107,6 @@
import android.net.Proxy;
import android.net.TrafficStats;
import android.net.Uri;
-import android.net.wifi.WifiFrameworkInitializer;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.BluetoothServiceManager;
@@ -7901,8 +7900,6 @@
BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
BinderCallsStats.startForBluetooth(context); });
- WifiFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
- BinderCallsStats.startForWifi(context); });
}
private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpInfo.java b/core/java/android/app/AppOpInfo.java
new file mode 100644
index 0000000..979c910
--- /dev/null
+++ b/core/java/android/app/AppOpInfo.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.AppOpsManager.OP_NONE;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Information about a particular app op.
+ */
+class AppOpInfo {
+ /**
+ * A unique constant identifying this app op.
+ */
+ public final int code;
+
+ /**
+ * This maps each operation to the operation that serves as the
+ * switch to determine whether it is allowed. Generally this is
+ * a 1:1 mapping, but for some things (like location) that have
+ * multiple low-level operations being tracked that should be
+ * presented to the user as one switch then this can be used to
+ * make them all controlled by the same single operation.
+ */
+ public final int switchCode;
+
+ /**
+ * This maps each operation to the public string constant for it.
+ */
+ public final String name;
+
+ /**
+ * This provides a simple name for each operation to be used
+ * in debug output.
+ */
+ public final String simpleName;
+
+ /**
+ * This optionally maps a permission to an operation. If there
+ * is no permission associated with an operation, it is null.
+ */
+ public final String permission;
+
+ /**
+ * Specifies whether an Op should be restricted by a user restriction.
+ * Each Op should be filled with a restriction string from UserManager or
+ * null to specify it is not affected by any user restriction.
+ */
+ public final String restriction;
+
+ /**
+ * In which cases should an app be allowed to bypass the
+ * {@link AppOpsManager#setUserRestriction user restriction} for a certain app-op.
+ */
+ public final AppOpsManager.RestrictionBypass allowSystemRestrictionBypass;
+
+ /**
+ * This specifies the default mode for each operation.
+ */
+ public final int defaultMode;
+
+ /**
+ * This specifies whether each option is allowed to be reset
+ * when resetting all app preferences. Disable reset for
+ * app ops that are under strong control of some part of the
+ * system (such as OP_WRITE_SMS, which should be allowed only
+ * for whichever app is selected as the current SMS app).
+ */
+ public final boolean disableReset;
+
+ /**
+ * This specifies whether each option is only allowed to be read
+ * by apps with manage appops permission.
+ */
+ public final boolean restrictRead;
+
+ AppOpInfo(int code,
+ int switchCode,
+ @NonNull String name,
+ @NonNull String simpleName,
+ String permission,
+ String restriction,
+ AppOpsManager.RestrictionBypass allowSystemRestrictionBypass,
+ int defaultMode,
+ boolean disableReset,
+ boolean restrictRead) {
+ if (code < OP_NONE) throw new IllegalArgumentException();
+ if (switchCode < OP_NONE) throw new IllegalArgumentException();
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(simpleName);
+ this.code = code;
+ this.switchCode = switchCode;
+ this.name = name;
+ this.simpleName = simpleName;
+ this.permission = permission;
+ this.restriction = restriction;
+ this.allowSystemRestrictionBypass = allowSystemRestrictionBypass;
+ this.defaultMode = defaultMode;
+ this.disableReset = disableReset;
+ this.restrictRead = restrictRead;
+ }
+
+ static class Builder {
+ private int mCode;
+ private int mSwitchCode;
+ private String mName;
+ private String mSimpleName;
+ private String mPermission = null;
+ private String mRestriction = null;
+ private AppOpsManager.RestrictionBypass mAllowSystemRestrictionBypass = null;
+ private int mDefaultMode = AppOpsManager.MODE_DEFAULT;
+ private boolean mDisableReset = false;
+ private boolean mRestrictRead = false;
+
+ Builder(int code, @NonNull String name, @NonNull String simpleName) {
+ if (code < OP_NONE) throw new IllegalArgumentException();
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(simpleName);
+ this.mCode = code;
+ this.mSwitchCode = code;
+ this.mName = name;
+ this.mSimpleName = simpleName;
+ }
+
+ public Builder setCode(int value) {
+ this.mCode = value;
+ return this;
+ }
+
+ public Builder setSwitchCode(int value) {
+ this.mSwitchCode = value;
+ return this;
+ }
+
+ public Builder setName(String value) {
+ this.mName = value;
+ return this;
+ }
+
+ public Builder setSimpleName(String value) {
+ this.mSimpleName = value;
+ return this;
+ }
+
+ public Builder setPermission(String value) {
+ this.mPermission = value;
+ return this;
+ }
+
+ public Builder setRestriction(String value) {
+ this.mRestriction = value;
+ return this;
+ }
+
+ public Builder setAllowSystemRestrictionBypass(
+ AppOpsManager.RestrictionBypass value) {
+ this.mAllowSystemRestrictionBypass = value;
+ return this;
+ }
+
+ public Builder setDefaultMode(int value) {
+ this.mDefaultMode = value;
+ return this;
+ }
+
+ public Builder setDisableReset(boolean value) {
+ this.mDisableReset = value;
+ return this;
+ }
+
+ public Builder setRestrictRead(boolean value) {
+ this.mRestrictRead = value;
+ return this;
+ }
+
+ public AppOpInfo build() {
+ return new AppOpInfo(mCode, mSwitchCode, mName, mSimpleName, mPermission, mRestriction,
+ mAllowSystemRestrictionBypass, mDefaultMode, mDisableReset, mRestrictRead);
+ }
+ }
+}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9072b50..d6b90a2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -867,8 +867,7 @@
// when adding one of these:
// - increment _NUM_OP
// - define an OPSTR_* constant (marked as @SystemApi)
- // - add rows to sOpToSwitch, sOpToString, sOpNames, sOpPerms, sOpDefaultMode, sOpDisableReset,
- // sOpRestrictions, sOpAllowSystemRestrictionBypass
+ // - add row to sAppOpInfos
// - add descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -1907,1165 +1906,382 @@
OP_SCHEDULE_EXACT_ALARM,
OP_MANAGE_MEDIA,
OP_TURN_SCREEN_ON,
+ OP_GET_USAGE_STATS,
};
- /**
- * This maps each operation to the operation that serves as the
- * switch to determine whether it is allowed. Generally this is
- * a 1:1 mapping, but for some things (like location) that have
- * multiple low-level operations being tracked that should be
- * presented to the user as one switch then this can be used to
- * make them all controlled by the same single operation.
- */
- private static int[] sOpToSwitch = new int[] {
- OP_COARSE_LOCATION, // COARSE_LOCATION
- OP_FINE_LOCATION, // FINE_LOCATION
- OP_COARSE_LOCATION, // GPS
- OP_VIBRATE, // VIBRATE
- OP_READ_CONTACTS, // READ_CONTACTS
- OP_WRITE_CONTACTS, // WRITE_CONTACTS
- OP_READ_CALL_LOG, // READ_CALL_LOG
- OP_WRITE_CALL_LOG, // WRITE_CALL_LOG
- OP_READ_CALENDAR, // READ_CALENDAR
- OP_WRITE_CALENDAR, // WRITE_CALENDAR
- OP_COARSE_LOCATION, // WIFI_SCAN
- OP_POST_NOTIFICATION, // POST_NOTIFICATION
- OP_COARSE_LOCATION, // NEIGHBORING_CELLS
- OP_CALL_PHONE, // CALL_PHONE
- OP_READ_SMS, // READ_SMS
- OP_WRITE_SMS, // WRITE_SMS
- OP_RECEIVE_SMS, // RECEIVE_SMS
- OP_RECEIVE_SMS, // RECEIVE_EMERGECY_SMS
- OP_RECEIVE_MMS, // RECEIVE_MMS
- OP_RECEIVE_WAP_PUSH, // RECEIVE_WAP_PUSH
- OP_SEND_SMS, // SEND_SMS
- OP_READ_SMS, // READ_ICC_SMS
- OP_WRITE_SMS, // WRITE_ICC_SMS
- OP_WRITE_SETTINGS, // WRITE_SETTINGS
- OP_SYSTEM_ALERT_WINDOW, // SYSTEM_ALERT_WINDOW
- OP_ACCESS_NOTIFICATIONS, // ACCESS_NOTIFICATIONS
- OP_CAMERA, // CAMERA
- OP_RECORD_AUDIO, // RECORD_AUDIO
- OP_PLAY_AUDIO, // PLAY_AUDIO
- OP_READ_CLIPBOARD, // READ_CLIPBOARD
- OP_WRITE_CLIPBOARD, // WRITE_CLIPBOARD
- OP_TAKE_MEDIA_BUTTONS, // TAKE_MEDIA_BUTTONS
- OP_TAKE_AUDIO_FOCUS, // TAKE_AUDIO_FOCUS
- OP_AUDIO_MASTER_VOLUME, // AUDIO_MASTER_VOLUME
- OP_AUDIO_VOICE_VOLUME, // AUDIO_VOICE_VOLUME
- OP_AUDIO_RING_VOLUME, // AUDIO_RING_VOLUME
- OP_AUDIO_MEDIA_VOLUME, // AUDIO_MEDIA_VOLUME
- OP_AUDIO_ALARM_VOLUME, // AUDIO_ALARM_VOLUME
- OP_AUDIO_NOTIFICATION_VOLUME, // AUDIO_NOTIFICATION_VOLUME
- OP_AUDIO_BLUETOOTH_VOLUME, // AUDIO_BLUETOOTH_VOLUME
- OP_WAKE_LOCK, // WAKE_LOCK
- OP_COARSE_LOCATION, // MONITOR_LOCATION
- OP_COARSE_LOCATION, // MONITOR_HIGH_POWER_LOCATION
- OP_GET_USAGE_STATS, // GET_USAGE_STATS
- OP_MUTE_MICROPHONE, // MUTE_MICROPHONE
- OP_TOAST_WINDOW, // TOAST_WINDOW
- OP_PROJECT_MEDIA, // PROJECT_MEDIA
- OP_ACTIVATE_VPN, // ACTIVATE_VPN
- OP_WRITE_WALLPAPER, // WRITE_WALLPAPER
- OP_ASSIST_STRUCTURE, // ASSIST_STRUCTURE
- OP_ASSIST_SCREENSHOT, // ASSIST_SCREENSHOT
- OP_READ_PHONE_STATE, // READ_PHONE_STATE
- OP_ADD_VOICEMAIL, // ADD_VOICEMAIL
- OP_USE_SIP, // USE_SIP
- OP_PROCESS_OUTGOING_CALLS, // PROCESS_OUTGOING_CALLS
- OP_USE_FINGERPRINT, // USE_FINGERPRINT
- OP_BODY_SENSORS, // BODY_SENSORS
- OP_READ_CELL_BROADCASTS, // READ_CELL_BROADCASTS
- OP_MOCK_LOCATION, // MOCK_LOCATION
- OP_READ_EXTERNAL_STORAGE, // READ_EXTERNAL_STORAGE
- OP_WRITE_EXTERNAL_STORAGE, // WRITE_EXTERNAL_STORAGE
- OP_TURN_SCREEN_ON, // TURN_SCREEN_ON
- OP_GET_ACCOUNTS, // GET_ACCOUNTS
- OP_RUN_IN_BACKGROUND, // RUN_IN_BACKGROUND
- OP_AUDIO_ACCESSIBILITY_VOLUME, // AUDIO_ACCESSIBILITY_VOLUME
- OP_READ_PHONE_NUMBERS, // READ_PHONE_NUMBERS
- OP_REQUEST_INSTALL_PACKAGES, // REQUEST_INSTALL_PACKAGES
- OP_PICTURE_IN_PICTURE, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
- OP_INSTANT_APP_START_FOREGROUND, // INSTANT_APP_START_FOREGROUND
- OP_ANSWER_PHONE_CALLS, // ANSWER_PHONE_CALLS
- OP_RUN_ANY_IN_BACKGROUND, // OP_RUN_ANY_IN_BACKGROUND
- OP_CHANGE_WIFI_STATE, // OP_CHANGE_WIFI_STATE
- OP_REQUEST_DELETE_PACKAGES, // OP_REQUEST_DELETE_PACKAGES
- OP_BIND_ACCESSIBILITY_SERVICE, // OP_BIND_ACCESSIBILITY_SERVICE
- OP_ACCEPT_HANDOVER, // ACCEPT_HANDOVER
- OP_MANAGE_IPSEC_TUNNELS, // MANAGE_IPSEC_HANDOVERS
- OP_START_FOREGROUND, // START_FOREGROUND
- OP_BLUETOOTH_SCAN, // BLUETOOTH_SCAN
- OP_USE_BIOMETRIC, // BIOMETRIC
- OP_ACTIVITY_RECOGNITION, // ACTIVITY_RECOGNITION
- OP_SMS_FINANCIAL_TRANSACTIONS, // SMS_FINANCIAL_TRANSACTIONS
- OP_READ_MEDIA_AUDIO, // READ_MEDIA_AUDIO
- OP_WRITE_MEDIA_AUDIO, // WRITE_MEDIA_AUDIO
- OP_READ_MEDIA_VIDEO, // READ_MEDIA_VIDEO
- OP_WRITE_MEDIA_VIDEO, // WRITE_MEDIA_VIDEO
- OP_READ_MEDIA_IMAGES, // READ_MEDIA_IMAGES
- OP_WRITE_MEDIA_IMAGES, // WRITE_MEDIA_IMAGES
- OP_LEGACY_STORAGE, // LEGACY_STORAGE
- OP_ACCESS_ACCESSIBILITY, // ACCESS_ACCESSIBILITY
- OP_READ_DEVICE_IDENTIFIERS, // READ_DEVICE_IDENTIFIERS
- OP_ACCESS_MEDIA_LOCATION, // ACCESS_MEDIA_LOCATION
- OP_QUERY_ALL_PACKAGES, // QUERY_ALL_PACKAGES
- OP_MANAGE_EXTERNAL_STORAGE, // MANAGE_EXTERNAL_STORAGE
- OP_INTERACT_ACROSS_PROFILES, //INTERACT_ACROSS_PROFILES
- OP_ACTIVATE_PLATFORM_VPN, // ACTIVATE_PLATFORM_VPN
- OP_LOADER_USAGE_STATS, // LOADER_USAGE_STATS
- OP_DEPRECATED_1, // deprecated
- OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED
- OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
- OP_NO_ISOLATED_STORAGE, // NO_ISOLATED_STORAGE
- OP_PHONE_CALL_MICROPHONE, // OP_PHONE_CALL_MICROPHONE
- OP_PHONE_CALL_CAMERA, // OP_PHONE_CALL_CAMERA
- OP_RECORD_AUDIO_HOTWORD, // RECORD_AUDIO_HOTWORD
- OP_MANAGE_ONGOING_CALLS, // MANAGE_ONGOING_CALLS
- OP_MANAGE_CREDENTIALS, // MANAGE_CREDENTIALS
- OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
- OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT
- OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM
- OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE
- OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE
- OP_MANAGE_MEDIA, // MANAGE_MEDIA
- OP_BLUETOOTH_CONNECT, // OP_BLUETOOTH_CONNECT
- OP_UWB_RANGING, // OP_UWB_RANGING
- OP_ACTIVITY_RECOGNITION, // OP_ACTIVITY_RECOGNITION_SOURCE
- OP_BLUETOOTH_ADVERTISE, // OP_BLUETOOTH_ADVERTISE
- OP_RECORD_INCOMING_PHONE_AUDIO, // OP_RECORD_INCOMING_PHONE_AUDIO
- OP_NEARBY_WIFI_DEVICES, // OP_NEARBY_WIFI_DEVICES
- OP_ESTABLISH_VPN_SERVICE, // OP_ESTABLISH_VPN_SERVICE
- OP_ESTABLISH_VPN_MANAGER, // OP_ESTABLISH_VPN_MANAGER
- OP_ACCESS_RESTRICTED_SETTINGS, // OP_ACCESS_RESTRICTED_SETTINGS
- OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, // RECEIVE_SOUNDTRIGGER_AUDIO
- };
-
- /**
- * This maps each operation to the public string constant for it.
- */
- private static String[] sOpToString = new String[]{
- OPSTR_COARSE_LOCATION,
- OPSTR_FINE_LOCATION,
- OPSTR_GPS,
- OPSTR_VIBRATE,
- OPSTR_READ_CONTACTS,
- OPSTR_WRITE_CONTACTS,
- OPSTR_READ_CALL_LOG,
- OPSTR_WRITE_CALL_LOG,
- OPSTR_READ_CALENDAR,
- OPSTR_WRITE_CALENDAR,
- OPSTR_WIFI_SCAN,
- OPSTR_POST_NOTIFICATION,
- OPSTR_NEIGHBORING_CELLS,
- OPSTR_CALL_PHONE,
- OPSTR_READ_SMS,
- OPSTR_WRITE_SMS,
- OPSTR_RECEIVE_SMS,
- OPSTR_RECEIVE_EMERGENCY_BROADCAST,
- OPSTR_RECEIVE_MMS,
- OPSTR_RECEIVE_WAP_PUSH,
- OPSTR_SEND_SMS,
- OPSTR_READ_ICC_SMS,
- OPSTR_WRITE_ICC_SMS,
- OPSTR_WRITE_SETTINGS,
- OPSTR_SYSTEM_ALERT_WINDOW,
- OPSTR_ACCESS_NOTIFICATIONS,
- OPSTR_CAMERA,
- OPSTR_RECORD_AUDIO,
- OPSTR_PLAY_AUDIO,
- OPSTR_READ_CLIPBOARD,
- OPSTR_WRITE_CLIPBOARD,
- OPSTR_TAKE_MEDIA_BUTTONS,
- OPSTR_TAKE_AUDIO_FOCUS,
- OPSTR_AUDIO_MASTER_VOLUME,
- OPSTR_AUDIO_VOICE_VOLUME,
- OPSTR_AUDIO_RING_VOLUME,
- OPSTR_AUDIO_MEDIA_VOLUME,
- OPSTR_AUDIO_ALARM_VOLUME,
- OPSTR_AUDIO_NOTIFICATION_VOLUME,
- OPSTR_AUDIO_BLUETOOTH_VOLUME,
- OPSTR_WAKE_LOCK,
- OPSTR_MONITOR_LOCATION,
- OPSTR_MONITOR_HIGH_POWER_LOCATION,
- OPSTR_GET_USAGE_STATS,
- OPSTR_MUTE_MICROPHONE,
- OPSTR_TOAST_WINDOW,
- OPSTR_PROJECT_MEDIA,
- OPSTR_ACTIVATE_VPN,
- OPSTR_WRITE_WALLPAPER,
- OPSTR_ASSIST_STRUCTURE,
- OPSTR_ASSIST_SCREENSHOT,
- OPSTR_READ_PHONE_STATE,
- OPSTR_ADD_VOICEMAIL,
- OPSTR_USE_SIP,
- OPSTR_PROCESS_OUTGOING_CALLS,
- OPSTR_USE_FINGERPRINT,
- OPSTR_BODY_SENSORS,
- OPSTR_READ_CELL_BROADCASTS,
- OPSTR_MOCK_LOCATION,
- OPSTR_READ_EXTERNAL_STORAGE,
- OPSTR_WRITE_EXTERNAL_STORAGE,
- OPSTR_TURN_SCREEN_ON,
- OPSTR_GET_ACCOUNTS,
- OPSTR_RUN_IN_BACKGROUND,
- OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
- OPSTR_READ_PHONE_NUMBERS,
- OPSTR_REQUEST_INSTALL_PACKAGES,
- OPSTR_PICTURE_IN_PICTURE,
- OPSTR_INSTANT_APP_START_FOREGROUND,
- OPSTR_ANSWER_PHONE_CALLS,
- OPSTR_RUN_ANY_IN_BACKGROUND,
- OPSTR_CHANGE_WIFI_STATE,
- OPSTR_REQUEST_DELETE_PACKAGES,
- OPSTR_BIND_ACCESSIBILITY_SERVICE,
- OPSTR_ACCEPT_HANDOVER,
- OPSTR_MANAGE_IPSEC_TUNNELS,
- OPSTR_START_FOREGROUND,
- OPSTR_BLUETOOTH_SCAN,
- OPSTR_USE_BIOMETRIC,
- OPSTR_ACTIVITY_RECOGNITION,
- OPSTR_SMS_FINANCIAL_TRANSACTIONS,
- OPSTR_READ_MEDIA_AUDIO,
- OPSTR_WRITE_MEDIA_AUDIO,
- OPSTR_READ_MEDIA_VIDEO,
- OPSTR_WRITE_MEDIA_VIDEO,
- OPSTR_READ_MEDIA_IMAGES,
- OPSTR_WRITE_MEDIA_IMAGES,
- OPSTR_LEGACY_STORAGE,
- OPSTR_ACCESS_ACCESSIBILITY,
- OPSTR_READ_DEVICE_IDENTIFIERS,
- OPSTR_ACCESS_MEDIA_LOCATION,
- OPSTR_QUERY_ALL_PACKAGES,
- OPSTR_MANAGE_EXTERNAL_STORAGE,
- OPSTR_INTERACT_ACROSS_PROFILES,
- OPSTR_ACTIVATE_PLATFORM_VPN,
- OPSTR_LOADER_USAGE_STATS,
- "", // deprecated
- OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
- OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER,
- OPSTR_NO_ISOLATED_STORAGE,
- OPSTR_PHONE_CALL_MICROPHONE,
- OPSTR_PHONE_CALL_CAMERA,
- OPSTR_RECORD_AUDIO_HOTWORD,
- OPSTR_MANAGE_ONGOING_CALLS,
- OPSTR_MANAGE_CREDENTIALS,
- OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
- OPSTR_RECORD_AUDIO_OUTPUT,
- OPSTR_SCHEDULE_EXACT_ALARM,
- OPSTR_FINE_LOCATION_SOURCE,
- OPSTR_COARSE_LOCATION_SOURCE,
- OPSTR_MANAGE_MEDIA,
- OPSTR_BLUETOOTH_CONNECT,
- OPSTR_UWB_RANGING,
- OPSTR_ACTIVITY_RECOGNITION_SOURCE,
- OPSTR_BLUETOOTH_ADVERTISE,
- OPSTR_RECORD_INCOMING_PHONE_AUDIO,
- OPSTR_NEARBY_WIFI_DEVICES,
- OPSTR_ESTABLISH_VPN_SERVICE,
- OPSTR_ESTABLISH_VPN_MANAGER,
- OPSTR_ACCESS_RESTRICTED_SETTINGS,
- OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
- };
-
- /**
- * This provides a simple name for each operation to be used
- * in debug output.
- */
- private static String[] sOpNames = new String[] {
- "COARSE_LOCATION",
- "FINE_LOCATION",
- "GPS",
- "VIBRATE",
- "READ_CONTACTS",
- "WRITE_CONTACTS",
- "READ_CALL_LOG",
- "WRITE_CALL_LOG",
- "READ_CALENDAR",
- "WRITE_CALENDAR",
- "WIFI_SCAN",
- "POST_NOTIFICATION",
- "NEIGHBORING_CELLS",
- "CALL_PHONE",
- "READ_SMS",
- "WRITE_SMS",
- "RECEIVE_SMS",
- "RECEIVE_EMERGECY_SMS",
- "RECEIVE_MMS",
- "RECEIVE_WAP_PUSH",
- "SEND_SMS",
- "READ_ICC_SMS",
- "WRITE_ICC_SMS",
- "WRITE_SETTINGS",
- "SYSTEM_ALERT_WINDOW",
- "ACCESS_NOTIFICATIONS",
- "CAMERA",
- "RECORD_AUDIO",
- "PLAY_AUDIO",
- "READ_CLIPBOARD",
- "WRITE_CLIPBOARD",
- "TAKE_MEDIA_BUTTONS",
- "TAKE_AUDIO_FOCUS",
- "AUDIO_MASTER_VOLUME",
- "AUDIO_VOICE_VOLUME",
- "AUDIO_RING_VOLUME",
- "AUDIO_MEDIA_VOLUME",
- "AUDIO_ALARM_VOLUME",
- "AUDIO_NOTIFICATION_VOLUME",
- "AUDIO_BLUETOOTH_VOLUME",
- "WAKE_LOCK",
- "MONITOR_LOCATION",
- "MONITOR_HIGH_POWER_LOCATION",
- "GET_USAGE_STATS",
- "MUTE_MICROPHONE",
- "TOAST_WINDOW",
- "PROJECT_MEDIA",
- "ACTIVATE_VPN",
- "WRITE_WALLPAPER",
- "ASSIST_STRUCTURE",
- "ASSIST_SCREENSHOT",
- "READ_PHONE_STATE",
- "ADD_VOICEMAIL",
- "USE_SIP",
- "PROCESS_OUTGOING_CALLS",
- "USE_FINGERPRINT",
- "BODY_SENSORS",
- "READ_CELL_BROADCASTS",
- "MOCK_LOCATION",
- "READ_EXTERNAL_STORAGE",
- "WRITE_EXTERNAL_STORAGE",
- "TURN_ON_SCREEN",
- "GET_ACCOUNTS",
- "RUN_IN_BACKGROUND",
- "AUDIO_ACCESSIBILITY_VOLUME",
- "READ_PHONE_NUMBERS",
- "REQUEST_INSTALL_PACKAGES",
- "PICTURE_IN_PICTURE",
- "INSTANT_APP_START_FOREGROUND",
- "ANSWER_PHONE_CALLS",
- "RUN_ANY_IN_BACKGROUND",
- "CHANGE_WIFI_STATE",
- "REQUEST_DELETE_PACKAGES",
- "BIND_ACCESSIBILITY_SERVICE",
- "ACCEPT_HANDOVER",
- "MANAGE_IPSEC_TUNNELS",
- "START_FOREGROUND",
- "BLUETOOTH_SCAN",
- "USE_BIOMETRIC",
- "ACTIVITY_RECOGNITION",
- "SMS_FINANCIAL_TRANSACTIONS",
- "READ_MEDIA_AUDIO",
- "WRITE_MEDIA_AUDIO",
- "READ_MEDIA_VIDEO",
- "WRITE_MEDIA_VIDEO",
- "READ_MEDIA_IMAGES",
- "WRITE_MEDIA_IMAGES",
- "LEGACY_STORAGE",
- "ACCESS_ACCESSIBILITY",
- "READ_DEVICE_IDENTIFIERS",
- "ACCESS_MEDIA_LOCATION",
- "QUERY_ALL_PACKAGES",
- "MANAGE_EXTERNAL_STORAGE",
- "INTERACT_ACROSS_PROFILES",
- "ACTIVATE_PLATFORM_VPN",
- "LOADER_USAGE_STATS",
- "deprecated",
- "AUTO_REVOKE_PERMISSIONS_IF_UNUSED",
- "AUTO_REVOKE_MANAGED_BY_INSTALLER",
- "NO_ISOLATED_STORAGE",
- "PHONE_CALL_MICROPHONE",
- "PHONE_CALL_CAMERA",
- "RECORD_AUDIO_HOTWORD",
- "MANAGE_ONGOING_CALLS",
- "MANAGE_CREDENTIALS",
- "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER",
- "RECORD_AUDIO_OUTPUT",
- "SCHEDULE_EXACT_ALARM",
- "FINE_LOCATION_SOURCE",
- "COARSE_LOCATION_SOURCE",
- "MANAGE_MEDIA",
- "BLUETOOTH_CONNECT",
- "UWB_RANGING",
- "ACTIVITY_RECOGNITION_SOURCE",
- "BLUETOOTH_ADVERTISE",
- "RECORD_INCOMING_PHONE_AUDIO",
- "NEARBY_WIFI_DEVICES",
- "ESTABLISH_VPN_SERVICE",
- "ESTABLISH_VPN_MANAGER",
- "ACCESS_RESTRICTED_SETTINGS",
- "RECEIVE_SOUNDTRIGGER_AUDIO",
- };
-
- /**
- * This optionally maps a permission to an operation. If there
- * is no permission associated with an operation, it is null.
- */
- @UnsupportedAppUsage
- private static String[] sOpPerms = new String[] {
- android.Manifest.permission.ACCESS_COARSE_LOCATION,
- android.Manifest.permission.ACCESS_FINE_LOCATION,
- null,
- android.Manifest.permission.VIBRATE,
- android.Manifest.permission.READ_CONTACTS,
- android.Manifest.permission.WRITE_CONTACTS,
- android.Manifest.permission.READ_CALL_LOG,
- android.Manifest.permission.WRITE_CALL_LOG,
- android.Manifest.permission.READ_CALENDAR,
- android.Manifest.permission.WRITE_CALENDAR,
- android.Manifest.permission.ACCESS_WIFI_STATE,
- android.Manifest.permission.POST_NOTIFICATIONS,
- null, // neighboring cells shares the coarse location perm
- android.Manifest.permission.CALL_PHONE,
- android.Manifest.permission.READ_SMS,
- null, // no permission required for writing sms
- android.Manifest.permission.RECEIVE_SMS,
- android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
- android.Manifest.permission.RECEIVE_MMS,
- android.Manifest.permission.RECEIVE_WAP_PUSH,
- android.Manifest.permission.SEND_SMS,
- android.Manifest.permission.READ_SMS,
- null, // no permission required for writing icc sms
- android.Manifest.permission.WRITE_SETTINGS,
- android.Manifest.permission.SYSTEM_ALERT_WINDOW,
- android.Manifest.permission.ACCESS_NOTIFICATIONS,
- android.Manifest.permission.CAMERA,
- android.Manifest.permission.RECORD_AUDIO,
- null, // no permission for playing audio
- null, // no permission for reading clipboard
- null, // no permission for writing clipboard
- null, // no permission for taking media buttons
- null, // no permission for taking audio focus
- null, // no permission for changing global volume
- null, // no permission for changing voice volume
- null, // no permission for changing ring volume
- null, // no permission for changing media volume
- null, // no permission for changing alarm volume
- null, // no permission for changing notification volume
- null, // no permission for changing bluetooth volume
- android.Manifest.permission.WAKE_LOCK,
- null, // no permission for generic location monitoring
- null, // no permission for high power location monitoring
- android.Manifest.permission.PACKAGE_USAGE_STATS,
- null, // no permission for muting/unmuting microphone
- null, // no permission for displaying toasts
- null, // no permission for projecting media
- null, // no permission for activating vpn
- null, // no permission for supporting wallpaper
- null, // no permission for receiving assist structure
- null, // no permission for receiving assist screenshot
- Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.ADD_VOICEMAIL,
- Manifest.permission.USE_SIP,
- Manifest.permission.PROCESS_OUTGOING_CALLS,
- Manifest.permission.USE_FINGERPRINT,
- Manifest.permission.BODY_SENSORS,
- Manifest.permission.READ_CELL_BROADCASTS,
- null,
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.TURN_SCREEN_ON,
- Manifest.permission.GET_ACCOUNTS,
- null, // no permission for running in background
- null, // no permission for changing accessibility volume
- Manifest.permission.READ_PHONE_NUMBERS,
- Manifest.permission.REQUEST_INSTALL_PACKAGES,
- null, // no permission for entering picture-in-picture on hide
- Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
- Manifest.permission.ANSWER_PHONE_CALLS,
- null, // no permission for OP_RUN_ANY_IN_BACKGROUND
- Manifest.permission.CHANGE_WIFI_STATE,
- Manifest.permission.REQUEST_DELETE_PACKAGES,
- Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
- Manifest.permission.ACCEPT_HANDOVER,
- Manifest.permission.MANAGE_IPSEC_TUNNELS,
- Manifest.permission.FOREGROUND_SERVICE,
- Manifest.permission.BLUETOOTH_SCAN,
- Manifest.permission.USE_BIOMETRIC,
- Manifest.permission.ACTIVITY_RECOGNITION,
- Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
- Manifest.permission.READ_MEDIA_AUDIO,
- null, // no permission for OP_WRITE_MEDIA_AUDIO
- Manifest.permission.READ_MEDIA_VIDEO,
- null, // no permission for OP_WRITE_MEDIA_VIDEO
- Manifest.permission.READ_MEDIA_IMAGES,
- null, // no permission for OP_WRITE_MEDIA_IMAGES
- null, // no permission for OP_LEGACY_STORAGE
- null, // no permission for OP_ACCESS_ACCESSIBILITY
- null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
- Manifest.permission.ACCESS_MEDIA_LOCATION,
- null, // no permission for OP_QUERY_ALL_PACKAGES
- Manifest.permission.MANAGE_EXTERNAL_STORAGE,
- android.Manifest.permission.INTERACT_ACROSS_PROFILES,
- null, // no permission for OP_ACTIVATE_PLATFORM_VPN
- android.Manifest.permission.LOADER_USAGE_STATS,
- null, // deprecated operation
- null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
- null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
- null, // no permission for OP_NO_ISOLATED_STORAGE
- null, // no permission for OP_PHONE_CALL_MICROPHONE
- null, // no permission for OP_PHONE_CALL_CAMERA
- null, // no permission for OP_RECORD_AUDIO_HOTWORD
- Manifest.permission.MANAGE_ONGOING_CALLS,
- null, // no permission for OP_MANAGE_CREDENTIALS
- Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
- null, // no permission for OP_RECORD_AUDIO_OUTPUT
- Manifest.permission.SCHEDULE_EXACT_ALARM,
- null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE,
- null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE,
- Manifest.permission.MANAGE_MEDIA,
- Manifest.permission.BLUETOOTH_CONNECT,
- Manifest.permission.UWB_RANGING,
- null, // no permission for OP_ACTIVITY_RECOGNITION_SOURCE,
- Manifest.permission.BLUETOOTH_ADVERTISE,
- null, // no permission for OP_RECORD_INCOMING_PHONE_AUDIO,
- Manifest.permission.NEARBY_WIFI_DEVICES,
- null, // no permission for OP_ESTABLISH_VPN_SERVICE
- null, // no permission for OP_ESTABLISH_VPN_MANAGER
- null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS,
- null, // no permission for OP_RECEIVE_SOUNDTRIGGER_AUDIO
- };
-
- /**
- * Specifies whether an Op should be restricted by a user restriction.
- * Each Op should be filled with a restriction string from UserManager or
- * null to specify it is not affected by any user restriction.
- */
- private static String[] sOpRestrictions = new String[] {
- UserManager.DISALLOW_SHARE_LOCATION, //COARSE_LOCATION
- UserManager.DISALLOW_SHARE_LOCATION, //FINE_LOCATION
- UserManager.DISALLOW_SHARE_LOCATION, //GPS
- null, //VIBRATE
- null, //READ_CONTACTS
- null, //WRITE_CONTACTS
- UserManager.DISALLOW_OUTGOING_CALLS, //READ_CALL_LOG
- UserManager.DISALLOW_OUTGOING_CALLS, //WRITE_CALL_LOG
- null, //READ_CALENDAR
- null, //WRITE_CALENDAR
- UserManager.DISALLOW_SHARE_LOCATION, //WIFI_SCAN
- null, //POST_NOTIFICATION
- null, //NEIGHBORING_CELLS
- null, //CALL_PHONE
- UserManager.DISALLOW_SMS, //READ_SMS
- UserManager.DISALLOW_SMS, //WRITE_SMS
- UserManager.DISALLOW_SMS, //RECEIVE_SMS
- null, //RECEIVE_EMERGENCY_SMS
- UserManager.DISALLOW_SMS, //RECEIVE_MMS
- null, //RECEIVE_WAP_PUSH
- UserManager.DISALLOW_SMS, //SEND_SMS
- UserManager.DISALLOW_SMS, //READ_ICC_SMS
- UserManager.DISALLOW_SMS, //WRITE_ICC_SMS
- null, //WRITE_SETTINGS
- UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW
- null, //ACCESS_NOTIFICATIONS
- UserManager.DISALLOW_CAMERA, //CAMERA
- UserManager.DISALLOW_RECORD_AUDIO, //RECORD_AUDIO
- null, //PLAY_AUDIO
- null, //READ_CLIPBOARD
- null, //WRITE_CLIPBOARD
- null, //TAKE_MEDIA_BUTTONS
- null, //TAKE_AUDIO_FOCUS
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MASTER_VOLUME
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_VOICE_VOLUME
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_RING_VOLUME
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MEDIA_VOLUME
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ALARM_VOLUME
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_NOTIFICATION_VOLUME
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_BLUETOOTH_VOLUME
- null, //WAKE_LOCK
- UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_LOCATION
- UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_HIGH_POWER_LOCATION
- null, //GET_USAGE_STATS
- UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE
- UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
- null, //PROJECT_MEDIA
- null, // ACTIVATE_VPN
- UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER
- null, // ASSIST_STRUCTURE
- null, // ASSIST_SCREENSHOT
- null, // READ_PHONE_STATE
- null, // ADD_VOICEMAIL
- null, // USE_SIP
- null, // PROCESS_OUTGOING_CALLS
- null, // USE_FINGERPRINT
- null, // BODY_SENSORS
- null, // READ_CELL_BROADCASTS
- null, // MOCK_LOCATION
- null, // READ_EXTERNAL_STORAGE
- null, // WRITE_EXTERNAL_STORAGE
- null, // TURN_SCREEN_ON
- null, // GET_ACCOUNTS
- null, // RUN_IN_BACKGROUND
- UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
- null, // READ_PHONE_NUMBERS
- null, // REQUEST_INSTALL_PACKAGES
- null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
- null, // INSTANT_APP_START_FOREGROUND
- null, // ANSWER_PHONE_CALLS
- null, // OP_RUN_ANY_IN_BACKGROUND
- null, // OP_CHANGE_WIFI_STATE
- null, // REQUEST_DELETE_PACKAGES
- null, // OP_BIND_ACCESSIBILITY_SERVICE
- null, // ACCEPT_HANDOVER
- null, // MANAGE_IPSEC_TUNNELS
- null, // START_FOREGROUND
- null, // maybe should be UserManager.DISALLOW_SHARE_LOCATION, //BLUETOOTH_SCAN
- null, // USE_BIOMETRIC
- null, // ACTIVITY_RECOGNITION
- UserManager.DISALLOW_SMS, // SMS_FINANCIAL_TRANSACTIONS
- null, // READ_MEDIA_AUDIO
- null, // WRITE_MEDIA_AUDIO
- null, // READ_MEDIA_VIDEO
- null, // WRITE_MEDIA_VIDEO
- null, // READ_MEDIA_IMAGES
- null, // WRITE_MEDIA_IMAGES
- null, // LEGACY_STORAGE
- null, // ACCESS_ACCESSIBILITY
- null, // READ_DEVICE_IDENTIFIERS
- null, // ACCESS_MEDIA_LOCATION
- null, // QUERY_ALL_PACKAGES
- null, // MANAGE_EXTERNAL_STORAGE
- null, // INTERACT_ACROSS_PROFILES
- null, // ACTIVATE_PLATFORM_VPN
- null, // LOADER_USAGE_STATS
- null, // deprecated operation
- null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
- null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
- null, // NO_ISOLATED_STORAGE
- null, // PHONE_CALL_MICROPHONE
- null, // PHONE_CALL_MICROPHONE
- null, // RECORD_AUDIO_HOTWORD
- null, // MANAGE_ONGOING_CALLS
- null, // MANAGE_CREDENTIALS
- null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
- null, // RECORD_AUDIO_OUTPUT
- null, // SCHEDULE_EXACT_ALARM
- null, // ACCESS_FINE_LOCATION_SOURCE
- null, // ACCESS_COARSE_LOCATION_SOURCE
- null, // MANAGE_MEDIA
- null, // BLUETOOTH_CONNECT
- null, // UWB_RANGING
- null, // ACTIVITY_RECOGNITION_SOURCE
- null, // BLUETOOTH_ADVERTISE
- null, // RECORD_INCOMING_PHONE_AUDIO
- null, // NEARBY_WIFI_DEVICES
- null, // ESTABLISH_VPN_SERVICE
- null, // ESTABLISH_VPN_MANAGER
- null, // ACCESS_RESTRICTED_SETTINGS
- null, // RECEIVE_SOUNDTRIGGER_AUDIO
- };
-
- /**
- * In which cases should an app be allowed to bypass the {@link #setUserRestriction user
- * restriction} for a certain app-op.
- */
- private static RestrictionBypass[] sOpAllowSystemRestrictionBypass = new RestrictionBypass[] {
- new RestrictionBypass(true, false, false), //COARSE_LOCATION
- new RestrictionBypass(true, false, false), //FINE_LOCATION
- null, //GPS
- null, //VIBRATE
- null, //READ_CONTACTS
- null, //WRITE_CONTACTS
- null, //READ_CALL_LOG
- null, //WRITE_CALL_LOG
- null, //READ_CALENDAR
- null, //WRITE_CALENDAR
- new RestrictionBypass(false, true, false), //WIFI_SCAN
- null, //POST_NOTIFICATION
- null, //NEIGHBORING_CELLS
- null, //CALL_PHONE
- null, //READ_SMS
- null, //WRITE_SMS
- null, //RECEIVE_SMS
- null, //RECEIVE_EMERGECY_SMS
- null, //RECEIVE_MMS
- null, //RECEIVE_WAP_PUSH
- null, //SEND_SMS
- null, //READ_ICC_SMS
- null, //WRITE_ICC_SMS
- null, //WRITE_SETTINGS
- new RestrictionBypass(false, true, false), //SYSTEM_ALERT_WINDOW
- null, //ACCESS_NOTIFICATIONS
- null, //CAMERA
- new RestrictionBypass(false, false, true), //RECORD_AUDIO
- null, //PLAY_AUDIO
- null, //READ_CLIPBOARD
- null, //WRITE_CLIPBOARD
- null, //TAKE_MEDIA_BUTTONS
- null, //TAKE_AUDIO_FOCUS
- null, //AUDIO_MASTER_VOLUME
- null, //AUDIO_VOICE_VOLUME
- null, //AUDIO_RING_VOLUME
- null, //AUDIO_MEDIA_VOLUME
- null, //AUDIO_ALARM_VOLUME
- null, //AUDIO_NOTIFICATION_VOLUME
- null, //AUDIO_BLUETOOTH_VOLUME
- null, //WAKE_LOCK
- null, //MONITOR_LOCATION
- null, //MONITOR_HIGH_POWER_LOCATION
- null, //GET_USAGE_STATS
- null, //MUTE_MICROPHONE
- new RestrictionBypass(false, true, false), //TOAST_WINDOW
- null, //PROJECT_MEDIA
- null, //ACTIVATE_VPN
- null, //WALLPAPER
- null, //ASSIST_STRUCTURE
- null, //ASSIST_SCREENSHOT
- null, //READ_PHONE_STATE
- null, //ADD_VOICEMAIL
- null, // USE_SIP
- null, // PROCESS_OUTGOING_CALLS
- null, // USE_FINGERPRINT
- null, // BODY_SENSORS
- null, // READ_CELL_BROADCASTS
- null, // MOCK_LOCATION
- null, // READ_EXTERNAL_STORAGE
- null, // WRITE_EXTERNAL_STORAGE
- null, // TURN_SCREEN_ON
- null, // GET_ACCOUNTS
- null, // RUN_IN_BACKGROUND
- null, // AUDIO_ACCESSIBILITY_VOLUME
- null, // READ_PHONE_NUMBERS
- null, // REQUEST_INSTALL_PACKAGES
- null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
- null, // INSTANT_APP_START_FOREGROUND
- null, // ANSWER_PHONE_CALLS
- null, // OP_RUN_ANY_IN_BACKGROUND
- null, // OP_CHANGE_WIFI_STATE
- null, // OP_REQUEST_DELETE_PACKAGES
- null, // OP_BIND_ACCESSIBILITY_SERVICE
- null, // ACCEPT_HANDOVER
- null, // MANAGE_IPSEC_HANDOVERS
- null, // START_FOREGROUND
- new RestrictionBypass(false, true, false), // BLUETOOTH_SCAN
- null, // USE_BIOMETRIC
- null, // ACTIVITY_RECOGNITION
- null, // SMS_FINANCIAL_TRANSACTIONS
- null, // READ_MEDIA_AUDIO
- null, // WRITE_MEDIA_AUDIO
- null, // READ_MEDIA_VIDEO
- null, // WRITE_MEDIA_VIDEO
- null, // READ_MEDIA_IMAGES
- null, // WRITE_MEDIA_IMAGES
- null, // LEGACY_STORAGE
- null, // ACCESS_ACCESSIBILITY
- null, // READ_DEVICE_IDENTIFIERS
- null, // ACCESS_MEDIA_LOCATION
- null, // QUERY_ALL_PACKAGES
- null, // MANAGE_EXTERNAL_STORAGE
- null, // INTERACT_ACROSS_PROFILES
- null, // ACTIVATE_PLATFORM_VPN
- null, // LOADER_USAGE_STATS
- null, // deprecated operation
- null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
- null, // AUTO_REVOKE_MANAGED_BY_INSTALLER
- null, // NO_ISOLATED_STORAGE
- null, // PHONE_CALL_MICROPHONE
- null, // PHONE_CALL_CAMERA
- null, // RECORD_AUDIO_HOTWORD
- null, // MANAGE_ONGOING_CALLS
- null, // MANAGE_CREDENTIALS
- null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
- null, // RECORD_AUDIO_OUTPUT
- null, // SCHEDULE_EXACT_ALARM
- null, // ACCESS_FINE_LOCATION_SOURCE
- null, // ACCESS_COARSE_LOCATION_SOURCE
- null, // MANAGE_MEDIA
- null, // BLUETOOTH_CONNECT
- null, // UWB_RANGING
- null, // ACTIVITY_RECOGNITION_SOURCE
- null, // BLUETOOTH_ADVERTISE
- null, // RECORD_INCOMING_PHONE_AUDIO
- null, // NEARBY_WIFI_DEVICES
- null, // ESTABLISH_VPN_SERVICE
- null, // ESTABLISH_VPN_MANAGER
- null, // ACCESS_RESTRICTED_SETTINGS
- null, // RECEIVE_SOUNDTRIGGER_AUDIO
- };
-
- /**
- * This specifies the default mode for each operation.
- */
- private static int[] sOpDefaultMode = new int[] {
- AppOpsManager.MODE_ALLOWED, // COARSE_LOCATION
- AppOpsManager.MODE_ALLOWED, // FINE_LOCATION
- AppOpsManager.MODE_ALLOWED, // GPS
- AppOpsManager.MODE_ALLOWED, // VIBRATE
- AppOpsManager.MODE_ALLOWED, // READ_CONTACTS
- AppOpsManager.MODE_ALLOWED, // WRITE_CONTACTS
- AppOpsManager.MODE_ALLOWED, // READ_CALL_LOG
- AppOpsManager.MODE_ALLOWED, // WRITE_CALL_LOG
- AppOpsManager.MODE_ALLOWED, // READ_CALENDAR
- AppOpsManager.MODE_ALLOWED, // WRITE_CALENDAR
- AppOpsManager.MODE_ALLOWED, // WIFI_SCAN
- AppOpsManager.MODE_ALLOWED, // POST_NOTIFICATION
- AppOpsManager.MODE_ALLOWED, // NEIGHBORING_CELLS
- AppOpsManager.MODE_ALLOWED, // CALL_PHONE
- AppOpsManager.MODE_ALLOWED, // READ_SMS
- AppOpsManager.MODE_IGNORED, // WRITE_SMS
- AppOpsManager.MODE_ALLOWED, // RECEIVE_SMS
- AppOpsManager.MODE_ALLOWED, // RECEIVE_EMERGENCY_BROADCAST
- AppOpsManager.MODE_ALLOWED, // RECEIVE_MMS
- AppOpsManager.MODE_ALLOWED, // RECEIVE_WAP_PUSH
- AppOpsManager.MODE_ALLOWED, // SEND_SMS
- AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
- AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
- AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
- getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
- AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
- AppOpsManager.MODE_ALLOWED, // CAMERA
- AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
- AppOpsManager.MODE_ALLOWED, // PLAY_AUDIO
- AppOpsManager.MODE_ALLOWED, // READ_CLIPBOARD
- AppOpsManager.MODE_ALLOWED, // WRITE_CLIPBOARD
- AppOpsManager.MODE_ALLOWED, // TAKE_MEDIA_BUTTONS
- AppOpsManager.MODE_ALLOWED, // TAKE_AUDIO_FOCUS
- AppOpsManager.MODE_ALLOWED, // AUDIO_MASTER_VOLUME
- AppOpsManager.MODE_ALLOWED, // AUDIO_VOICE_VOLUME
- AppOpsManager.MODE_ALLOWED, // AUDIO_RING_VOLUME
- AppOpsManager.MODE_ALLOWED, // AUDIO_MEDIA_VOLUME
- AppOpsManager.MODE_ALLOWED, // AUDIO_ALARM_VOLUME
- AppOpsManager.MODE_ALLOWED, // AUDIO_NOTIFICATION_VOLUME
- AppOpsManager.MODE_ALLOWED, // AUDIO_BLUETOOTH_VOLUME
- AppOpsManager.MODE_ALLOWED, // WAKE_LOCK
- AppOpsManager.MODE_ALLOWED, // MONITOR_LOCATION
- AppOpsManager.MODE_ALLOWED, // MONITOR_HIGH_POWER_LOCATION
- AppOpsManager.MODE_DEFAULT, // GET_USAGE_STATS
- AppOpsManager.MODE_ALLOWED, // MUTE_MICROPHONE
- AppOpsManager.MODE_ALLOWED, // TOAST_WINDOW
- AppOpsManager.MODE_IGNORED, // PROJECT_MEDIA
- AppOpsManager.MODE_IGNORED, // ACTIVATE_VPN
- AppOpsManager.MODE_ALLOWED, // WRITE_WALLPAPER
- AppOpsManager.MODE_ALLOWED, // ASSIST_STRUCTURE
- AppOpsManager.MODE_ALLOWED, // ASSIST_SCREENSHOT
- AppOpsManager.MODE_ALLOWED, // READ_PHONE_STATE
- AppOpsManager.MODE_ALLOWED, // ADD_VOICEMAIL
- AppOpsManager.MODE_ALLOWED, // USE_SIP
- AppOpsManager.MODE_ALLOWED, // PROCESS_OUTGOING_CALLS
- AppOpsManager.MODE_ALLOWED, // USE_FINGERPRINT
- AppOpsManager.MODE_ALLOWED, // BODY_SENSORS
- AppOpsManager.MODE_ALLOWED, // READ_CELL_BROADCASTS
- AppOpsManager.MODE_ERRORED, // MOCK_LOCATION
- AppOpsManager.MODE_ALLOWED, // READ_EXTERNAL_STORAGE
- AppOpsManager.MODE_ALLOWED, // WRITE_EXTERNAL_STORAGE
- AppOpsManager.MODE_ERRORED, // TURN_SCREEN_ON
- AppOpsManager.MODE_ALLOWED, // GET_ACCOUNTS
- AppOpsManager.MODE_ALLOWED, // RUN_IN_BACKGROUND
- AppOpsManager.MODE_ALLOWED, // AUDIO_ACCESSIBILITY_VOLUME
- AppOpsManager.MODE_ALLOWED, // READ_PHONE_NUMBERS
- AppOpsManager.MODE_DEFAULT, // REQUEST_INSTALL_PACKAGES
- AppOpsManager.MODE_ALLOWED, // PICTURE_IN_PICTURE
- AppOpsManager.MODE_DEFAULT, // INSTANT_APP_START_FOREGROUND
- AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
- AppOpsManager.MODE_ALLOWED, // RUN_ANY_IN_BACKGROUND
- AppOpsManager.MODE_ALLOWED, // CHANGE_WIFI_STATE
- AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES
- AppOpsManager.MODE_ALLOWED, // BIND_ACCESSIBILITY_SERVICE
- AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER
- AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS
- AppOpsManager.MODE_ALLOWED, // START_FOREGROUND
- AppOpsManager.MODE_ALLOWED, // BLUETOOTH_SCAN
- AppOpsManager.MODE_ALLOWED, // USE_BIOMETRIC
- AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION
- AppOpsManager.MODE_DEFAULT, // SMS_FINANCIAL_TRANSACTIONS
- AppOpsManager.MODE_ALLOWED, // READ_MEDIA_AUDIO
- AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_AUDIO
- AppOpsManager.MODE_ALLOWED, // READ_MEDIA_VIDEO
- AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_VIDEO
- AppOpsManager.MODE_ALLOWED, // READ_MEDIA_IMAGES
- AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_IMAGES
- AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
- AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
- AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
- AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION
- AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
- AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
- AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
- AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
- AppOpsManager.MODE_DEFAULT, // LOADER_USAGE_STATS
- AppOpsManager.MODE_IGNORED, // deprecated operation
- AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
- AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER
- AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE
- AppOpsManager.MODE_ALLOWED, // PHONE_CALL_MICROPHONE
- AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA
- AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_HOTWORD
- AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS
- AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS
- AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
- AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT
- AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM
- AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE
- AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE
- AppOpsManager.MODE_DEFAULT, // MANAGE_MEDIA
- AppOpsManager.MODE_ALLOWED, // BLUETOOTH_CONNECT
- AppOpsManager.MODE_ALLOWED, // UWB_RANGING
- AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION_SOURCE
- AppOpsManager.MODE_ALLOWED, // BLUETOOTH_ADVERTISE
- AppOpsManager.MODE_ALLOWED, // RECORD_INCOMING_PHONE_AUDIO
- AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES
- AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE
- AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER
- AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS,
- AppOpsManager.MODE_ALLOWED, // RECEIVE_SOUNDTRIGGER_AUDIO
- };
-
- /**
- * This specifies whether each option is allowed to be reset
- * when resetting all app preferences. Disable reset for
- * app ops that are under strong control of some part of the
- * system (such as OP_WRITE_SMS, which should be allowed only
- * for whichever app is selected as the current SMS app).
- */
- private static boolean[] sOpDisableReset = new boolean[] {
- false, // COARSE_LOCATION
- false, // FINE_LOCATION
- false, // GPS
- false, // VIBRATE
- false, // READ_CONTACTS
- false, // WRITE_CONTACTS
- false, // READ_CALL_LOG
- false, // WRITE_CALL_LOG
- false, // READ_CALENDAR
- false, // WRITE_CALENDAR
- false, // WIFI_SCAN
- false, // POST_NOTIFICATION
- false, // NEIGHBORING_CELLS
- false, // CALL_PHONE
- true, // READ_SMS
- true, // WRITE_SMS
- true, // RECEIVE_SMS
- false, // RECEIVE_EMERGENCY_BROADCAST
- false, // RECEIVE_MMS
- true, // RECEIVE_WAP_PUSH
- true, // SEND_SMS
- false, // READ_ICC_SMS
- false, // WRITE_ICC_SMS
- false, // WRITE_SETTINGS
- false, // SYSTEM_ALERT_WINDOW
- false, // ACCESS_NOTIFICATIONS
- false, // CAMERA
- false, // RECORD_AUDIO
- false, // PLAY_AUDIO
- false, // READ_CLIPBOARD
- false, // WRITE_CLIPBOARD
- false, // TAKE_MEDIA_BUTTONS
- false, // TAKE_AUDIO_FOCUS
- false, // AUDIO_MASTER_VOLUME
- false, // AUDIO_VOICE_VOLUME
- false, // AUDIO_RING_VOLUME
- false, // AUDIO_MEDIA_VOLUME
- false, // AUDIO_ALARM_VOLUME
- false, // AUDIO_NOTIFICATION_VOLUME
- false, // AUDIO_BLUETOOTH_VOLUME
- false, // WAKE_LOCK
- false, // MONITOR_LOCATION
- false, // MONITOR_HIGH_POWER_LOCATION
- false, // GET_USAGE_STATS
- false, // MUTE_MICROPHONE
- false, // TOAST_WINDOW
- false, // PROJECT_MEDIA
- false, // ACTIVATE_VPN
- false, // WRITE_WALLPAPER
- false, // ASSIST_STRUCTURE
- false, // ASSIST_SCREENSHOT
- false, // READ_PHONE_STATE
- false, // ADD_VOICEMAIL
- false, // USE_SIP
- false, // PROCESS_OUTGOING_CALLS
- false, // USE_FINGERPRINT
- false, // BODY_SENSORS
- true, // READ_CELL_BROADCASTS
- false, // MOCK_LOCATION
- false, // READ_EXTERNAL_STORAGE
- false, // WRITE_EXTERNAL_STORAGE
- false, // TURN_SCREEN_ON
- false, // GET_ACCOUNTS
- false, // RUN_IN_BACKGROUND
- false, // AUDIO_ACCESSIBILITY_VOLUME
- false, // READ_PHONE_NUMBERS
- false, // REQUEST_INSTALL_PACKAGES
- false, // PICTURE_IN_PICTURE
- false, // INSTANT_APP_START_FOREGROUND
- false, // ANSWER_PHONE_CALLS
- false, // RUN_ANY_IN_BACKGROUND
- false, // CHANGE_WIFI_STATE
- false, // REQUEST_DELETE_PACKAGES
- false, // BIND_ACCESSIBILITY_SERVICE
- false, // ACCEPT_HANDOVER
- false, // MANAGE_IPSEC_TUNNELS
- false, // START_FOREGROUND
- false, // BLUETOOTH_SCAN
- false, // USE_BIOMETRIC
- false, // ACTIVITY_RECOGNITION
- false, // SMS_FINANCIAL_TRANSACTIONS
- false, // READ_MEDIA_AUDIO
- false, // WRITE_MEDIA_AUDIO
- false, // READ_MEDIA_VIDEO
- true, // WRITE_MEDIA_VIDEO
- false, // READ_MEDIA_IMAGES
- true, // WRITE_MEDIA_IMAGES
- true, // LEGACY_STORAGE
- false, // ACCESS_ACCESSIBILITY
- false, // READ_DEVICE_IDENTIFIERS
- false, // ACCESS_MEDIA_LOCATION
- false, // QUERY_ALL_PACKAGES
- false, // MANAGE_EXTERNAL_STORAGE
- false, // INTERACT_ACROSS_PROFILES
- false, // ACTIVATE_PLATFORM_VPN
- false, // LOADER_USAGE_STATS
- false, // deprecated operation
- false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
- false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
- true, // NO_ISOLATED_STORAGE
- false, // PHONE_CALL_MICROPHONE
- false, // PHONE_CALL_CAMERA
- false, // RECORD_AUDIO_HOTWORD
- true, // MANAGE_ONGOING_CALLS
- false, // MANAGE_CREDENTIALS
- true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
- false, // RECORD_AUDIO_OUTPUT
- false, // SCHEDULE_EXACT_ALARM
- false, // ACCESS_FINE_LOCATION_SOURCE
- false, // ACCESS_COARSE_LOCATION_SOURCE
- false, // MANAGE_MEDIA
- false, // BLUETOOTH_CONNECT
- false, // UWB_RANGING
- false, // ACTIVITY_RECOGNITION_SOURCE
- false, // BLUETOOTH_ADVERTISE
- false, // RECORD_INCOMING_PHONE_AUDIO
- false, // NEARBY_WIFI_DEVICES
- false, // OP_ESTABLISH_VPN_SERVICE
- false, // OP_ESTABLISH_VPN_MANAGER
- true, // ACCESS_RESTRICTED_SETTINGS
- false, // RECEIVE_SOUNDTRIGGER_AUDIO
- };
-
- /**
- * This specifies whether each option is only allowed to be read
- * by apps with manage appops permission.
- */
- private static boolean[] sOpRestrictRead = new boolean[] {
- false, // COARSE_LOCATION
- false, // FINE_LOCATION
- false, // GPS
- false, // VIBRATE
- false, // READ_CONTACTS
- false, // WRITE_CONTACTS
- false, // READ_CALL_LOG
- false, // WRITE_CALL_LOG
- false, // READ_CALENDAR
- false, // WRITE_CALENDAR
- false, // WIFI_SCAN
- false, // POST_NOTIFICATION
- false, // NEIGHBORING_CELLS
- false, // CALL_PHONE
- false, // READ_SMS
- false, // WRITE_SMS
- false, // RECEIVE_SMS
- false, // RECEIVE_EMERGENCY_BROADCAST
- false, // RECEIVE_MMS
- false, // RECEIVE_WAP_PUSH
- false, // SEND_SMS
- false, // READ_ICC_SMS
- false, // WRITE_ICC_SMS
- false, // WRITE_SETTINGS
- false, // SYSTEM_ALERT_WINDOW
- false, // ACCESS_NOTIFICATIONS
- false, // CAMERA
- false, // RECORD_AUDIO
- false, // PLAY_AUDIO
- false, // READ_CLIPBOARD
- false, // WRITE_CLIPBOARD
- false, // TAKE_MEDIA_BUTTONS
- false, // TAKE_AUDIO_FOCUS
- false, // AUDIO_MASTER_VOLUME
- false, // AUDIO_VOICE_VOLUME
- false, // AUDIO_RING_VOLUME
- false, // AUDIO_MEDIA_VOLUME
- false, // AUDIO_ALARM_VOLUME
- false, // AUDIO_NOTIFICATION_VOLUME
- false, // AUDIO_BLUETOOTH_VOLUME
- false, // WAKE_LOCK
- false, // MONITOR_LOCATION
- false, // MONITOR_HIGH_POWER_LOCATION
- false, // GET_USAGE_STATS
- false, // MUTE_MICROPHONE
- false, // TOAST_WINDOW
- false, // PROJECT_MEDIA
- false, // ACTIVATE_VPN
- false, // WRITE_WALLPAPER
- false, // ASSIST_STRUCTURE
- false, // ASSIST_SCREENSHOT
- false, // READ_PHONE_STATE
- false, // ADD_VOICEMAIL
- false, // USE_SIP
- false, // PROCESS_OUTGOING_CALLS
- false, // USE_FINGERPRINT
- false, // BODY_SENSORS
- false, // READ_CELL_BROADCASTS
- false, // MOCK_LOCATION
- false, // READ_EXTERNAL_STORAGE
- false, // WRITE_EXTERNAL_STORAGE
- false, // TURN_SCREEN_ON
- false, // GET_ACCOUNTS
- false, // RUN_IN_BACKGROUND
- false, // AUDIO_ACCESSIBILITY_VOLUME
- false, // READ_PHONE_NUMBERS
- false, // REQUEST_INSTALL_PACKAGES
- false, // PICTURE_IN_PICTURE
- false, // INSTANT_APP_START_FOREGROUND
- false, // ANSWER_PHONE_CALLS
- false, // RUN_ANY_IN_BACKGROUND
- false, // CHANGE_WIFI_STATE
- false, // REQUEST_DELETE_PACKAGES
- false, // BIND_ACCESSIBILITY_SERVICE
- false, // ACCEPT_HANDOVER
- false, // MANAGE_IPSEC_TUNNELS
- false, // START_FOREGROUND
- false, // BLUETOOTH_SCAN
- false, // USE_BIOMETRIC
- false, // ACTIVITY_RECOGNITION
- false, // SMS_FINANCIAL_TRANSACTIONS
- false, // READ_MEDIA_AUDIO
- false, // WRITE_MEDIA_AUDIO
- false, // READ_MEDIA_VIDEO
- false, // WRITE_MEDIA_VIDEO
- false, // READ_MEDIA_IMAGES
- false, // WRITE_MEDIA_IMAGES
- false, // LEGACY_STORAGE
- false, // ACCESS_ACCESSIBILITY
- false, // READ_DEVICE_IDENTIFIERS
- false, // ACCESS_MEDIA_LOCATION
- false, // QUERY_ALL_PACKAGES
- false, // MANAGE_EXTERNAL_STORAGE
- false, // INTERACT_ACROSS_PROFILES
- false, // ACTIVATE_PLATFORM_VPN
- false, // LOADER_USAGE_STATS
- false, // deprecated operation
- false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED
- false, // AUTO_REVOKE_MANAGED_BY_INSTALLER
- false, // NO_ISOLATED_STORAGE
- false, // PHONE_CALL_MICROPHONE
- false, // PHONE_CALL_CAMERA
- false, // RECORD_AUDIO_HOTWORD
- false, // MANAGE_ONGOING_CALLS
- false, // MANAGE_CREDENTIALS
- false, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER
- false, // RECORD_AUDIO_OUTPUT
- false, // SCHEDULE_EXACT_ALARM
- false, // ACCESS_FINE_LOCATION_SOURCE
- false, // ACCESS_COARSE_LOCATION_SOURCE
- false, // MANAGE_MEDIA
- false, // BLUETOOTH_CONNECT
- false, // UWB_RANGING
- false, // ACTIVITY_RECOGNITION_SOURCE
- false, // BLUETOOTH_ADVERTISE
- false, // RECORD_INCOMING_PHONE_AUDIO
- false, // NEARBY_WIFI_DEVICES
- false, // OP_ESTABLISH_VPN_SERVICE
- false, // OP_ESTABLISH_VPN_MANAGER
- true, // ACCESS_RESTRICTED_SETTINGS
- false, // RECEIVE_SOUNDTRIGGER_AUDIO
+ static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
+ new AppOpInfo.Builder(OP_COARSE_LOCATION, OPSTR_COARSE_LOCATION, "COARSE_LOCATION")
+ .setPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+ .setAllowSystemRestrictionBypass(new RestrictionBypass(true, false, false))
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_FINE_LOCATION, OPSTR_FINE_LOCATION, "FINE_LOCATION")
+ .setPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+ .setAllowSystemRestrictionBypass(new RestrictionBypass(true, false, false))
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_GPS, OPSTR_GPS, "GPS")
+ .setSwitchCode(OP_COARSE_LOCATION)
+ .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_VIBRATE, OPSTR_VIBRATE, "VIBRATE")
+ .setSwitchCode(OP_VIBRATE).setPermission(android.Manifest.permission.VIBRATE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_CONTACTS, OPSTR_READ_CONTACTS, "READ_CONTACTS")
+ .setPermission(android.Manifest.permission.READ_CONTACTS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_CONTACTS, OPSTR_WRITE_CONTACTS, "WRITE_CONTACTS")
+ .setPermission(android.Manifest.permission.WRITE_CONTACTS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_CALL_LOG, OPSTR_READ_CALL_LOG, "READ_CALL_LOG")
+ .setPermission(android.Manifest.permission.READ_CALL_LOG)
+ .setRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_CALL_LOG, OPSTR_WRITE_CALL_LOG, "WRITE_CALL_LOG")
+ .setPermission(android.Manifest.permission.WRITE_CALL_LOG)
+ .setRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_CALENDAR, OPSTR_READ_CALENDAR, "READ_CALENDAR")
+ .setPermission(android.Manifest.permission.READ_CALENDAR)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_CALENDAR, OPSTR_WRITE_CALENDAR, "WRITE_CALENDAR")
+ .setPermission(android.Manifest.permission.WRITE_CALENDAR)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WIFI_SCAN, OPSTR_WIFI_SCAN, "WIFI_SCAN")
+ .setSwitchCode(OP_COARSE_LOCATION)
+ .setPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
+ .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+ .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_POST_NOTIFICATION, OPSTR_POST_NOTIFICATION, "POST_NOTIFICATION")
+ .setPermission(android.Manifest.permission.POST_NOTIFICATIONS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_NEIGHBORING_CELLS, OPSTR_NEIGHBORING_CELLS, "NEIGHBORING_CELLS")
+ .setSwitchCode(OP_COARSE_LOCATION).setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_CALL_PHONE, OPSTR_CALL_PHONE, "CALL_PHONE")
+ .setSwitchCode(OP_CALL_PHONE).setPermission(android.Manifest.permission.CALL_PHONE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_SMS, OPSTR_READ_SMS, "READ_SMS")
+ .setPermission(android.Manifest.permission.READ_SMS)
+ .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_WRITE_SMS, OPSTR_WRITE_SMS, "WRITE_SMS")
+ .setRestriction(UserManager.DISALLOW_SMS)
+ .setDefaultMode(AppOpsManager.MODE_IGNORED).setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_RECEIVE_SMS, OPSTR_RECEIVE_SMS, "RECEIVE_SMS")
+ .setPermission(android.Manifest.permission.RECEIVE_SMS)
+ .setRestriction(UserManager.DISALLOW_SMS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_RECEIVE_EMERGECY_SMS, OPSTR_RECEIVE_EMERGENCY_BROADCAST,
+ "RECEIVE_EMERGENCY_BROADCAST").setSwitchCode(OP_RECEIVE_SMS)
+ .setPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RECEIVE_MMS, OPSTR_RECEIVE_MMS, "RECEIVE_MMS")
+ .setPermission(android.Manifest.permission.RECEIVE_MMS)
+ .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .build(),
+ new AppOpInfo.Builder(OP_RECEIVE_WAP_PUSH, OPSTR_RECEIVE_WAP_PUSH, "RECEIVE_WAP_PUSH")
+ .setPermission(android.Manifest.permission.RECEIVE_WAP_PUSH)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_SEND_SMS, OPSTR_SEND_SMS, "SEND_SMS")
+ .setPermission(android.Manifest.permission.SEND_SMS)
+ .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_READ_ICC_SMS, OPSTR_READ_ICC_SMS, "READ_ICC_SMS")
+ .setSwitchCode(OP_READ_SMS).setPermission(android.Manifest.permission.READ_SMS)
+ .setRestriction(UserManager.DISALLOW_SMS).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .build(),
+ new AppOpInfo.Builder(OP_WRITE_ICC_SMS, OPSTR_WRITE_ICC_SMS, "WRITE_ICC_SMS")
+ .setSwitchCode(OP_WRITE_SMS).setRestriction(UserManager.DISALLOW_SMS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_SETTINGS, OPSTR_WRITE_SETTINGS, "WRITE_SETTINGS")
+ .setPermission(android.Manifest.permission.WRITE_SETTINGS).build(),
+ new AppOpInfo.Builder(OP_SYSTEM_ALERT_WINDOW, OPSTR_SYSTEM_ALERT_WINDOW,
+ "SYSTEM_ALERT_WINDOW")
+ .setPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW)
+ .setRestriction(UserManager.DISALLOW_CREATE_WINDOWS)
+ .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+ .setDefaultMode(getSystemAlertWindowDefault()).build(),
+ new AppOpInfo.Builder(OP_ACCESS_NOTIFICATIONS, OPSTR_ACCESS_NOTIFICATIONS,
+ "ACCESS_NOTIFICATIONS")
+ .setPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_CAMERA, OPSTR_CAMERA, "CAMERA")
+ .setPermission(android.Manifest.permission.CAMERA)
+ .setRestriction(UserManager.DISALLOW_CAMERA)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RECORD_AUDIO, OPSTR_RECORD_AUDIO, "RECORD_AUDIO")
+ .setPermission(android.Manifest.permission.RECORD_AUDIO)
+ .setRestriction(UserManager.DISALLOW_RECORD_AUDIO)
+ .setAllowSystemRestrictionBypass(new RestrictionBypass(false, false, true))
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_PLAY_AUDIO, OPSTR_PLAY_AUDIO, "PLAY_AUDIO")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_CLIPBOARD, OPSTR_READ_CLIPBOARD, "READ_CLIPBOARD")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_CLIPBOARD, OPSTR_WRITE_CLIPBOARD, "WRITE_CLIPBOARD")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_TAKE_MEDIA_BUTTONS, OPSTR_TAKE_MEDIA_BUTTONS, "TAKE_MEDIA_BUTTONS")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .build(),
+ new AppOpInfo.Builder(OP_TAKE_AUDIO_FOCUS, OPSTR_TAKE_AUDIO_FOCUS, "TAKE_AUDIO_FOCUS")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_AUDIO_MASTER_VOLUME, OPSTR_AUDIO_MASTER_VOLUME,
+ "AUDIO_MASTER_VOLUME").setSwitchCode(OP_AUDIO_MASTER_VOLUME)
+ .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_AUDIO_VOICE_VOLUME, OPSTR_AUDIO_VOICE_VOLUME, "AUDIO_VOICE_VOLUME")
+ .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_AUDIO_RING_VOLUME, OPSTR_AUDIO_RING_VOLUME, "AUDIO_RING_VOLUME")
+ .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_AUDIO_MEDIA_VOLUME, OPSTR_AUDIO_MEDIA_VOLUME, "AUDIO_MEDIA_VOLUME")
+ .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_AUDIO_ALARM_VOLUME, OPSTR_AUDIO_ALARM_VOLUME, "AUDIO_ALARM_VOLUME")
+ .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_AUDIO_NOTIFICATION_VOLUME, OPSTR_AUDIO_NOTIFICATION_VOLUME,
+ "AUDIO_NOTIFICATION_VOLUME").setSwitchCode(OP_AUDIO_NOTIFICATION_VOLUME)
+ .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_AUDIO_BLUETOOTH_VOLUME, OPSTR_AUDIO_BLUETOOTH_VOLUME,
+ "AUDIO_BLUETOOTH_VOLUME").setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WAKE_LOCK, OPSTR_WAKE_LOCK, "WAKE_LOCK")
+ .setPermission(android.Manifest.permission.WAKE_LOCK)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_MONITOR_LOCATION, OPSTR_MONITOR_LOCATION, "MONITOR_LOCATION")
+ .setSwitchCode(OP_COARSE_LOCATION)
+ .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_MONITOR_HIGH_POWER_LOCATION, OPSTR_MONITOR_HIGH_POWER_LOCATION,
+ "MONITOR_HIGH_POWER_LOCATION").setSwitchCode(OP_COARSE_LOCATION)
+ .setRestriction(UserManager.DISALLOW_SHARE_LOCATION)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_GET_USAGE_STATS, OPSTR_GET_USAGE_STATS, "GET_USAGE_STATS")
+ .setPermission(android.Manifest.permission.PACKAGE_USAGE_STATS).build(),
+ new AppOpInfo.Builder(OP_MUTE_MICROPHONE, OPSTR_MUTE_MICROPHONE, "MUTE_MICROPHONE")
+ .setRestriction(UserManager.DISALLOW_UNMUTE_MICROPHONE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_TOAST_WINDOW, OPSTR_TOAST_WINDOW, "TOAST_WINDOW")
+ .setRestriction(UserManager.DISALLOW_CREATE_WINDOWS)
+ .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_PROJECT_MEDIA, OPSTR_PROJECT_MEDIA, "PROJECT_MEDIA")
+ .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+ new AppOpInfo.Builder(OP_ACTIVATE_VPN, OPSTR_ACTIVATE_VPN, "ACTIVATE_VPN")
+ .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+ new AppOpInfo.Builder(OP_WRITE_WALLPAPER, OPSTR_WRITE_WALLPAPER, "WRITE_WALLPAPER")
+ .setRestriction(UserManager.DISALLOW_WALLPAPER)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ASSIST_STRUCTURE, OPSTR_ASSIST_STRUCTURE, "ASSIST_STRUCTURE")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ASSIST_SCREENSHOT, OPSTR_ASSIST_SCREENSHOT, "ASSIST_SCREENSHOT")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .build(),
+ new AppOpInfo.Builder(OP_READ_PHONE_STATE, OPSTR_READ_PHONE_STATE, "READ_PHONE_STATE")
+ .setPermission(Manifest.permission.READ_PHONE_STATE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ADD_VOICEMAIL, OPSTR_ADD_VOICEMAIL, "ADD_VOICEMAIL")
+ .setPermission(Manifest.permission.ADD_VOICEMAIL)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_USE_SIP, OPSTR_USE_SIP, "USE_SIP")
+ .setPermission(Manifest.permission.USE_SIP)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_PROCESS_OUTGOING_CALLS, OPSTR_PROCESS_OUTGOING_CALLS,
+ "PROCESS_OUTGOING_CALLS").setSwitchCode(OP_PROCESS_OUTGOING_CALLS)
+ .setPermission(Manifest.permission.PROCESS_OUTGOING_CALLS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_USE_FINGERPRINT, OPSTR_USE_FINGERPRINT, "USE_FINGERPRINT")
+ .setPermission(Manifest.permission.USE_FINGERPRINT)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_BODY_SENSORS, OPSTR_BODY_SENSORS, "BODY_SENSORS")
+ .setPermission(Manifest.permission.BODY_SENSORS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_CELL_BROADCASTS, OPSTR_READ_CELL_BROADCASTS,
+ "READ_CELL_BROADCASTS").setPermission(Manifest.permission.READ_CELL_BROADCASTS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_MOCK_LOCATION, OPSTR_MOCK_LOCATION, "MOCK_LOCATION")
+ .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+ new AppOpInfo.Builder(OP_READ_EXTERNAL_STORAGE, OPSTR_READ_EXTERNAL_STORAGE,
+ "READ_EXTERNAL_STORAGE").setPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE,
+ "WRITE_EXTERNAL_STORAGE").setPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_TURN_SCREEN_ON, OPSTR_TURN_SCREEN_ON, "TURN_SCREEN_ON")
+ .setPermission(Manifest.permission.TURN_SCREEN_ON)
+ .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+ new AppOpInfo.Builder(OP_GET_ACCOUNTS, OPSTR_GET_ACCOUNTS, "GET_ACCOUNTS")
+ .setPermission(Manifest.permission.GET_ACCOUNTS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RUN_IN_BACKGROUND, OPSTR_RUN_IN_BACKGROUND, "RUN_IN_BACKGROUND")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .build(),
+ new AppOpInfo.Builder(OP_AUDIO_ACCESSIBILITY_VOLUME, OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
+ "AUDIO_ACCESSIBILITY_VOLUME")
+ .setRestriction(UserManager.DISALLOW_ADJUST_VOLUME)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_PHONE_NUMBERS, OPSTR_READ_PHONE_NUMBERS, "READ_PHONE_NUMBERS")
+ .setPermission(Manifest.permission.READ_PHONE_NUMBERS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_REQUEST_INSTALL_PACKAGES, OPSTR_REQUEST_INSTALL_PACKAGES,
+ "REQUEST_INSTALL_PACKAGES").setSwitchCode(OP_REQUEST_INSTALL_PACKAGES)
+ .setPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES).build(),
+ new AppOpInfo.Builder(OP_PICTURE_IN_PICTURE, OPSTR_PICTURE_IN_PICTURE, "PICTURE_IN_PICTURE")
+ .setSwitchCode(OP_PICTURE_IN_PICTURE).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .build(),
+ new AppOpInfo.Builder(OP_INSTANT_APP_START_FOREGROUND, OPSTR_INSTANT_APP_START_FOREGROUND,
+ "INSTANT_APP_START_FOREGROUND")
+ .setPermission(Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE).build(),
+ new AppOpInfo.Builder(OP_ANSWER_PHONE_CALLS, OPSTR_ANSWER_PHONE_CALLS, "ANSWER_PHONE_CALLS")
+ .setSwitchCode(OP_ANSWER_PHONE_CALLS)
+ .setPermission(Manifest.permission.ANSWER_PHONE_CALLS)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RUN_ANY_IN_BACKGROUND, OPSTR_RUN_ANY_IN_BACKGROUND,
+ "RUN_ANY_IN_BACKGROUND")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_CHANGE_WIFI_STATE, OPSTR_CHANGE_WIFI_STATE, "CHANGE_WIFI_STATE")
+ .setSwitchCode(OP_CHANGE_WIFI_STATE)
+ .setPermission(Manifest.permission.CHANGE_WIFI_STATE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_REQUEST_DELETE_PACKAGES, OPSTR_REQUEST_DELETE_PACKAGES,
+ "REQUEST_DELETE_PACKAGES")
+ .setPermission(Manifest.permission.REQUEST_DELETE_PACKAGES)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_BIND_ACCESSIBILITY_SERVICE, OPSTR_BIND_ACCESSIBILITY_SERVICE,
+ "BIND_ACCESSIBILITY_SERVICE")
+ .setPermission(Manifest.permission.BIND_ACCESSIBILITY_SERVICE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ACCEPT_HANDOVER, OPSTR_ACCEPT_HANDOVER, "ACCEPT_HANDOVER")
+ .setSwitchCode(OP_ACCEPT_HANDOVER)
+ .setPermission(Manifest.permission.ACCEPT_HANDOVER)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_MANAGE_IPSEC_TUNNELS, OPSTR_MANAGE_IPSEC_TUNNELS,
+ "MANAGE_IPSEC_TUNNELS")
+ .setPermission(Manifest.permission.MANAGE_IPSEC_TUNNELS)
+ .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+ new AppOpInfo.Builder(OP_START_FOREGROUND, OPSTR_START_FOREGROUND, "START_FOREGROUND")
+ .setPermission(Manifest.permission.FOREGROUND_SERVICE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_BLUETOOTH_SCAN, OPSTR_BLUETOOTH_SCAN, "BLUETOOTH_SCAN")
+ .setPermission(Manifest.permission.BLUETOOTH_SCAN)
+ .setAllowSystemRestrictionBypass(new RestrictionBypass(false, true, false))
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_USE_BIOMETRIC, OPSTR_USE_BIOMETRIC, "USE_BIOMETRIC")
+ .setPermission(Manifest.permission.USE_BIOMETRIC)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ACTIVITY_RECOGNITION, OPSTR_ACTIVITY_RECOGNITION,
+ "ACTIVITY_RECOGNITION")
+ .setPermission(Manifest.permission.ACTIVITY_RECOGNITION)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_SMS_FINANCIAL_TRANSACTIONS, OPSTR_SMS_FINANCIAL_TRANSACTIONS,
+ "SMS_FINANCIAL_TRANSACTIONS")
+ .setPermission(Manifest.permission.SMS_FINANCIAL_TRANSACTIONS)
+ .setRestriction(UserManager.DISALLOW_SMS).build(),
+ new AppOpInfo.Builder(OP_READ_MEDIA_AUDIO, OPSTR_READ_MEDIA_AUDIO, "READ_MEDIA_AUDIO")
+ .setPermission(Manifest.permission.READ_MEDIA_AUDIO)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_MEDIA_AUDIO, OPSTR_WRITE_MEDIA_AUDIO, "WRITE_MEDIA_AUDIO")
+ .setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+ new AppOpInfo.Builder(OP_READ_MEDIA_VIDEO, OPSTR_READ_MEDIA_VIDEO, "READ_MEDIA_VIDEO")
+ .setPermission(Manifest.permission.READ_MEDIA_VIDEO)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_MEDIA_VIDEO, OPSTR_WRITE_MEDIA_VIDEO, "WRITE_MEDIA_VIDEO")
+ .setDefaultMode(AppOpsManager.MODE_ERRORED).setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_READ_MEDIA_IMAGES, OPSTR_READ_MEDIA_IMAGES, "READ_MEDIA_IMAGES")
+ .setPermission(Manifest.permission.READ_MEDIA_IMAGES)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_WRITE_MEDIA_IMAGES, OPSTR_WRITE_MEDIA_IMAGES, "WRITE_MEDIA_IMAGES")
+ .setDefaultMode(AppOpsManager.MODE_ERRORED).setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_LEGACY_STORAGE, OPSTR_LEGACY_STORAGE, "LEGACY_STORAGE")
+ .setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_ACCESS_ACCESSIBILITY, OPSTR_ACCESS_ACCESSIBILITY,
+ "ACCESS_ACCESSIBILITY").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_DEVICE_IDENTIFIERS, OPSTR_READ_DEVICE_IDENTIFIERS,
+ "READ_DEVICE_IDENTIFIERS").setDefaultMode(AppOpsManager.MODE_ERRORED).build(),
+ new AppOpInfo.Builder(OP_ACCESS_MEDIA_LOCATION, OPSTR_ACCESS_MEDIA_LOCATION,
+ "ACCESS_MEDIA_LOCATION").setPermission(Manifest.permission.ACCESS_MEDIA_LOCATION)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_QUERY_ALL_PACKAGES, OPSTR_QUERY_ALL_PACKAGES, "QUERY_ALL_PACKAGES")
+ .build(),
+ new AppOpInfo.Builder(OP_MANAGE_EXTERNAL_STORAGE, OPSTR_MANAGE_EXTERNAL_STORAGE,
+ "MANAGE_EXTERNAL_STORAGE")
+ .setPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE).build(),
+ new AppOpInfo.Builder(OP_INTERACT_ACROSS_PROFILES, OPSTR_INTERACT_ACROSS_PROFILES,
+ "INTERACT_ACROSS_PROFILES")
+ .setPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES).build(),
+ new AppOpInfo.Builder(OP_ACTIVATE_PLATFORM_VPN, OPSTR_ACTIVATE_PLATFORM_VPN,
+ "ACTIVATE_PLATFORM_VPN").setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+ new AppOpInfo.Builder(OP_LOADER_USAGE_STATS, OPSTR_LOADER_USAGE_STATS, "LOADER_USAGE_STATS")
+ .setPermission(android.Manifest.permission.LOADER_USAGE_STATS).build(),
+ new AppOpInfo.Builder(OP_NONE, "", "").setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
+ new AppOpInfo.Builder(OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+ OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, "AUTO_REVOKE_PERMISSIONS_IF_UNUSED")
+ .build(),
+ new AppOpInfo.Builder(OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
+ OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, "AUTO_REVOKE_MANAGED_BY_INSTALLER")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_NO_ISOLATED_STORAGE, OPSTR_NO_ISOLATED_STORAGE,
+ "NO_ISOLATED_STORAGE").setDefaultMode(AppOpsManager.MODE_ERRORED)
+ .setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_PHONE_CALL_MICROPHONE, OPSTR_PHONE_CALL_MICROPHONE,
+ "PHONE_CALL_MICROPHONE").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_PHONE_CALL_CAMERA, OPSTR_PHONE_CALL_CAMERA, "PHONE_CALL_CAMERA")
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RECORD_AUDIO_HOTWORD, OPSTR_RECORD_AUDIO_HOTWORD,
+ "RECORD_AUDIO_HOTWORD").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_MANAGE_ONGOING_CALLS, OPSTR_MANAGE_ONGOING_CALLS,
+ "MANAGE_ONGOING_CALLS").setPermission(Manifest.permission.MANAGE_ONGOING_CALLS)
+ .setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_MANAGE_CREDENTIALS, OPSTR_MANAGE_CREDENTIALS, "MANAGE_CREDENTIALS")
+ .build(),
+ new AppOpInfo.Builder(OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
+ OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER")
+ .setPermission(Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER)
+ .setDisableReset(true).build(),
+ new AppOpInfo.Builder(OP_RECORD_AUDIO_OUTPUT, OPSTR_RECORD_AUDIO_OUTPUT,
+ "RECORD_AUDIO_OUTPUT").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_SCHEDULE_EXACT_ALARM, OPSTR_SCHEDULE_EXACT_ALARM,
+ "SCHEDULE_EXACT_ALARM").setPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
+ .build(),
+ new AppOpInfo.Builder(OP_FINE_LOCATION_SOURCE, OPSTR_FINE_LOCATION_SOURCE,
+ "FINE_LOCATION_SOURCE").setSwitchCode(OP_FINE_LOCATION)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_COARSE_LOCATION_SOURCE, OPSTR_COARSE_LOCATION_SOURCE,
+ "COARSE_LOCATION_SOURCE").setSwitchCode(OP_COARSE_LOCATION)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_MANAGE_MEDIA, OPSTR_MANAGE_MEDIA, "MANAGE_MEDIA")
+ .setPermission(Manifest.permission.MANAGE_MEDIA).build(),
+ new AppOpInfo.Builder(OP_BLUETOOTH_CONNECT, OPSTR_BLUETOOTH_CONNECT, "BLUETOOTH_CONNECT")
+ .setPermission(Manifest.permission.BLUETOOTH_CONNECT)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_UWB_RANGING, OPSTR_UWB_RANGING, "UWB_RANGING")
+ .setPermission(Manifest.permission.UWB_RANGING)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ACTIVITY_RECOGNITION_SOURCE, OPSTR_ACTIVITY_RECOGNITION_SOURCE,
+ "ACTIVITY_RECOGNITION_SOURCE")
+ .setSwitchCode(OP_ACTIVITY_RECOGNITION).setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .build(),
+ new AppOpInfo.Builder(OP_BLUETOOTH_ADVERTISE, OPSTR_BLUETOOTH_ADVERTISE,
+ "BLUETOOTH_ADVERTISE").setPermission(Manifest.permission.BLUETOOTH_ADVERTISE)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RECORD_INCOMING_PHONE_AUDIO, OPSTR_RECORD_INCOMING_PHONE_AUDIO,
+ "RECORD_INCOMING_PHONE_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_NEARBY_WIFI_DEVICES, OPSTR_NEARBY_WIFI_DEVICES,
+ "NEARBY_WIFI_DEVICES").setPermission(Manifest.permission.NEARBY_WIFI_DEVICES)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ESTABLISH_VPN_SERVICE, OPSTR_ESTABLISH_VPN_SERVICE,
+ "ESTABLISH_VPN_SERVICE").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER,
+ "ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED)
+ .setDisableReset(true).setRestrictRead(true).build(),
+ new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO,
+ "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
};
/**
@@ -3101,46 +2317,18 @@
private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
static {
- if (sOpToSwitch.length != _NUM_OP) {
- throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
+ if (sAppOpInfos.length != _NUM_OP) {
+ throw new IllegalStateException("mAppOpInfos length " + sAppOpInfos.length
+ " should be " + _NUM_OP);
}
- if (sOpToString.length != _NUM_OP) {
- throw new IllegalStateException("sOpToString length " + sOpToString.length
- + " should be " + _NUM_OP);
- }
- if (sOpNames.length != _NUM_OP) {
- throw new IllegalStateException("sOpNames length " + sOpNames.length
- + " should be " + _NUM_OP);
- }
- if (sOpPerms.length != _NUM_OP) {
- throw new IllegalStateException("sOpPerms length " + sOpPerms.length
- + " should be " + _NUM_OP);
- }
- if (sOpDefaultMode.length != _NUM_OP) {
- throw new IllegalStateException("sOpDefaultMode length " + sOpDefaultMode.length
- + " should be " + _NUM_OP);
- }
- if (sOpDisableReset.length != _NUM_OP) {
- throw new IllegalStateException("sOpDisableReset length " + sOpDisableReset.length
- + " should be " + _NUM_OP);
- }
- if (sOpRestrictions.length != _NUM_OP) {
- throw new IllegalStateException("sOpRestrictions length " + sOpRestrictions.length
- + " should be " + _NUM_OP);
- }
- if (sOpAllowSystemRestrictionBypass.length != _NUM_OP) {
- throw new IllegalStateException("sOpAllowSYstemRestrictionsBypass length "
- + sOpRestrictions.length + " should be " + _NUM_OP);
- }
for (int i=0; i<_NUM_OP; i++) {
- if (sOpToString[i] != null) {
- sOpStrToOp.put(sOpToString[i], i);
+ if (sAppOpInfos[i].name != null) {
+ sOpStrToOp.put(sAppOpInfos[i].name, i);
}
}
for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) {
- if (sOpPerms[op] != null) {
- sPermToOp.put(sOpPerms[op], op);
+ if (sAppOpInfos[op].permission != null) {
+ sPermToOp.put(sAppOpInfos[op].permission, op);
}
}
@@ -3170,7 +2358,7 @@
*/
@UnsupportedAppUsage
public static int opToSwitch(int op) {
- return sOpToSwitch[op];
+ return sAppOpInfos[op].switchCode;
}
/**
@@ -3180,7 +2368,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static String opToName(int op) {
if (op == OP_NONE) return "NONE";
- return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")");
+ return op < sAppOpInfos.length ? sAppOpInfos[op].simpleName : ("Unknown(" + op + ")");
}
/**
@@ -3189,15 +2377,15 @@
* @hide
*/
public static @NonNull String opToPublicName(int op) {
- return sOpToString[op];
+ return sAppOpInfos[op].name;
}
/**
* @hide
*/
public static int strDebugOpToOp(String op) {
- for (int i=0; i<sOpNames.length; i++) {
- if (sOpNames[i].equals(op)) {
+ for (int i = 0; i < sAppOpInfos.length; i++) {
+ if (sAppOpInfos[i].simpleName.equals(op)) {
return i;
}
}
@@ -3211,7 +2399,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@TestApi
public static String opToPermission(int op) {
- return sOpPerms[op];
+ return sAppOpInfos[op].permission;
}
/**
@@ -3232,7 +2420,7 @@
* @hide
*/
public static String opToRestriction(int op) {
- return sOpRestrictions[op];
+ return sAppOpInfos[op].restriction;
}
/**
@@ -3254,7 +2442,7 @@
* @hide
*/
public static RestrictionBypass opAllowSystemBypassRestriction(int op) {
- return sOpAllowSystemRestrictionBypass[op];
+ return sAppOpInfos[op].allowSystemRestrictionBypass;
}
/**
@@ -3262,7 +2450,7 @@
* @hide
*/
public static @Mode int opToDefaultMode(int op) {
- return sOpDefaultMode[op];
+ return sAppOpInfos[op].defaultMode;
}
/**
@@ -3295,7 +2483,7 @@
* @hide
*/
public static boolean opRestrictsRead(int op) {
- return sOpRestrictRead[op];
+ return sAppOpInfos[op].restrictRead;
}
/**
@@ -3303,7 +2491,7 @@
* @hide
*/
public static boolean opAllowsReset(int op) {
- return !sOpDisableReset[op];
+ return !sAppOpInfos[op].disableReset;
}
/**
@@ -4436,7 +3624,7 @@
* @return This entry's op string name, such as {@link #OPSTR_COARSE_LOCATION}.
*/
public @NonNull String getOpStr() {
- return sOpToString[mOp];
+ return sAppOpInfos[mOp].name;
}
/**
@@ -6553,7 +5741,7 @@
if (mHistoricalOps == null) {
mHistoricalOps = new ArrayMap<>();
}
- final String opStr = sOpToString[opCode];
+ final String opStr = sAppOpInfos[opCode].name;
HistoricalOp op = mHistoricalOps.get(opStr);
if (op == null) {
op = new HistoricalOp(opCode);
@@ -6898,7 +6086,7 @@
* @return The op name.
*/
public @NonNull String getOpName() {
- return sOpToString[mOp];
+ return sAppOpInfos[mOp].name;
}
/** @hide */
@@ -7912,7 +7100,7 @@
if (opCode == null) {
return null;
}
- return sOpToString[opCode];
+ return sAppOpInfos[opCode].name;
}
/**
@@ -8007,8 +7195,8 @@
if (callback instanceof OnOpChangedInternalListener) {
((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
}
- if (sOpToString[op] != null) {
- callback.onOpChanged(sOpToString[op], packageName);
+ if (sAppOpInfos[op].name != null) {
+ callback.onOpChanged(sAppOpInfos[op].name, packageName);
}
}
};
@@ -8096,8 +7284,8 @@
((OnOpActiveChangedInternalListener) callback).onOpActiveChanged(op,
uid, packageName, active);
}
- if (sOpToString[op] != null) {
- callback.onOpActiveChanged(sOpToString[op], uid, packageName,
+ if (sAppOpInfos[op].name != null) {
+ callback.onOpActiveChanged(sAppOpInfos[op].name, uid, packageName,
attributionTag, active, attributionFlags, attributionChainId);
}
});
@@ -8273,7 +7461,8 @@
}
private String buildSecurityExceptionMsg(int op, int uid, String packageName) {
- return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op];
+ return packageName + " from uid " + uid + " not allowed to perform " +
+ sAppOpInfos[op].simpleName;
}
/**
@@ -8633,7 +7822,7 @@
+ attributionSource.getUid() + " or calling package "
+ attributionSource.getNextPackageName() + " from uid "
+ attributionSource.getNextUid() + " not allowed to perform "
- + sOpNames[op]);
+ + sAppOpInfos[op].simpleName);
}
return mode;
}
@@ -10084,10 +9273,13 @@
*/
@SystemApi
public static String[] getOpStrs() {
- return Arrays.copyOf(sOpToString, sOpToString.length);
+ String[] opStrs = new String[sAppOpInfos.length];
+ for(int i = 0; i < sAppOpInfos.length; i++) {
+ opStrs[i] = sAppOpInfos[i].name;
+ }
+ return opStrs;
}
-
/**
* @return number of App ops
* @hide
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f0e1448..aa5fa5b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -528,6 +528,28 @@
return mIsAlarmBroadcast;
}
+ /**
+ * Did this broadcast originate from a push message from the server?
+ *
+ * @return true if this broadcast is a push message, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingBroadcast() {
+ return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING;
+ }
+
+ /**
+ * Did this broadcast originate from a push message from the server which was over the allowed
+ * quota?
+ *
+ * @return true if this broadcast is a push message over quota, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingOverQuotaBroadcast() {
+ return mTemporaryAppAllowlistReasonCode
+ == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
+ }
+
/** {@hide} */
public long getRequireCompatChangeId() {
return mRequireCompatChangeId;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8367441..bd999fc 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -760,4 +760,15 @@
* </p>
*/
int getBackgroundRestrictionExemptionReason(int uid);
+
+ // Start (?) of T transactions
+ /**
+ * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
+ * but setting the user as the visible user of that display (i.e., allowing the user and its
+ * running profiles to launch activities on that display).
+ *
+ * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+ */
+ boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
+
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 0e89b25..b9ad595 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -124,8 +124,13 @@
/**
* The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
* limit.
+ * @hide
*/
- private static final int MAX_TEXT_LENGTH = 1000;
+ public static final int MAX_TEXT_LENGTH = 1000;
+ /**
+ * @hide
+ */
+ public static final int MAX_VIBRATION_LENGTH = 1000;
private static final String TAG_CHANNEL = "channel";
private static final String ATT_NAME = "name";
@@ -283,17 +288,17 @@
*/
protected NotificationChannel(Parcel in) {
if (in.readByte() != 0) {
- mId = in.readString();
+ mId = getTrimmedString(in.readString());
} else {
mId = null;
}
if (in.readByte() != 0) {
- mName = in.readString();
+ mName = getTrimmedString(in.readString());
} else {
mName = null;
}
if (in.readByte() != 0) {
- mDesc = in.readString();
+ mDesc = getTrimmedString(in.readString());
} else {
mDesc = null;
}
@@ -302,18 +307,22 @@
mLockscreenVisibility = in.readInt();
if (in.readByte() != 0) {
mSound = Uri.CREATOR.createFromParcel(in);
+ mSound = Uri.parse(getTrimmedString(mSound.toString()));
} else {
mSound = null;
}
mLights = in.readByte() != 0;
mVibration = in.createLongArray();
+ if (mVibration != null && mVibration.length > MAX_VIBRATION_LENGTH) {
+ mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH);
+ }
mUserLockedFields = in.readInt();
mFgServiceShown = in.readByte() != 0;
mVibrationEnabled = in.readByte() != 0;
mShowBadge = in.readByte() != 0;
mDeleted = in.readByte() != 0;
if (in.readByte() != 0) {
- mGroup = in.readString();
+ mGroup = getTrimmedString(in.readString());
} else {
mGroup = null;
}
@@ -322,8 +331,8 @@
mBlockableSystem = in.readBoolean();
mAllowBubbles = in.readInt();
mOriginalImportance = in.readInt();
- mParentId = in.readString();
- mConversationId = in.readString();
+ mParentId = getTrimmedString(in.readString());
+ mConversationId = getTrimmedString(in.readString());
mDemoted = in.readBoolean();
mImportantConvo = in.readBoolean();
mDeletedTime = in.readLong();
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index f97415c..807bd57 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -20,6 +20,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -43,8 +44,9 @@
/**
* The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at
* this limit.
+ * @hide
*/
- private static final int MAX_TEXT_LENGTH = 1000;
+ public static final int MAX_TEXT_LENGTH = 1000;
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_NAME = "name";
@@ -66,7 +68,7 @@
private CharSequence mName;
private String mDescription;
private boolean mBlocked;
- private List<NotificationChannel> mChannels = new ArrayList<>();
+ private ParceledListSlice<NotificationChannel> mChannels;
// Bitwise representation of fields that have been changed by the user
private int mUserLockedFields;
@@ -90,17 +92,19 @@
*/
protected NotificationChannelGroup(Parcel in) {
if (in.readByte() != 0) {
- mId = in.readString();
+ mId = getTrimmedString(in.readString());
} else {
mId = null;
}
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mName = getTrimmedString(mName.toString());
if (in.readByte() != 0) {
- mDescription = in.readString();
+ mDescription = getTrimmedString(in.readString());
} else {
mDescription = null;
}
- in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
+ mChannels = in.readParcelable(
+ NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class);
mBlocked = in.readBoolean();
mUserLockedFields = in.readInt();
}
@@ -120,14 +124,14 @@
} else {
dest.writeByte((byte) 0);
}
- TextUtils.writeToParcel(mName, dest, flags);
+ TextUtils.writeToParcel(mName.toString(), dest, flags);
if (mDescription != null) {
dest.writeByte((byte) 1);
dest.writeString(mDescription);
} else {
dest.writeByte((byte) 0);
}
- dest.writeParcelableList(mChannels, flags);
+ dest.writeParcelable(mChannels, flags);
dest.writeBoolean(mBlocked);
dest.writeInt(mUserLockedFields);
}
@@ -157,7 +161,7 @@
* Returns the list of channels that belong to this group
*/
public List<NotificationChannel> getChannels() {
- return mChannels;
+ return mChannels == null ? new ArrayList<>() : mChannels.getList();
}
/**
@@ -191,15 +195,8 @@
/**
* @hide
*/
- public void addChannel(NotificationChannel channel) {
- mChannels.add(channel);
- }
-
- /**
- * @hide
- */
public void setChannels(List<NotificationChannel> channels) {
- mChannels = channels;
+ mChannels = new ParceledListSlice<>(channels);
}
/**
@@ -334,7 +331,7 @@
proto.write(NotificationChannelGroupProto.NAME, mName.toString());
proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
- for (NotificationChannel channel : mChannels) {
+ for (NotificationChannel channel : mChannels.getList()) {
channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS);
}
proto.end(token);
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 087e61d..f9d3222 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -11,3 +11,4 @@
per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
per-file UserInfo* = file:/MULTIUSER_OWNERS
+per-file *UserProperties* = file:/MULTIUSER_OWNERS
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/core/java/android/content/pm/UserProperties.aidl
similarity index 74%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to core/java/android/content/pm/UserProperties.aidl
index 8dde897..4d37067 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/core/java/android/content/pm/UserProperties.aidl
@@ -11,13 +11,9 @@
* 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.
+ * limitations under the License
*/
-package com.android.settingslib.spaprivileged.framework.app
+package android.content.pm;
-import android.content.pm.ApplicationInfo
-
-interface AppRecord {
- val app: ApplicationInfo
-}
+parcelable UserProperties;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
new file mode 100644
index 0000000..1a82e4d
--- /dev/null
+++ b/core/java/android/content/pm/UserProperties.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class holding the properties of a user that derive mostly from its user type.
+ */
+public final class UserProperties implements Parcelable {
+ private static final String LOG_TAG = UserProperties.class.getSimpleName();
+
+ // Attribute strings for reading/writing properties to/from XML.
+ private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
+ private static final String ATTR_START_WITH_PARENT = "startWithParent";
+
+ /** Index values of each property (to indicate whether they are present in this object). */
+ @IntDef(prefix = "INDEX_", value = {
+ INDEX_SHOW_IN_LAUNCHER,
+ INDEX_START_WITH_PARENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface PropertyIndex {
+ }
+ private static final int INDEX_SHOW_IN_LAUNCHER = 0;
+ private static final int INDEX_START_WITH_PARENT = 1;
+ /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
+ private long mPropertiesPresent = 0;
+
+
+ /**
+ * Possible values for whether or how to show this user in the Launcher.
+ * @hide
+ */
+ @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+ SHOW_IN_LAUNCHER_WITH_PARENT,
+ SHOW_IN_LAUNCHER_SEPARATE,
+ SHOW_IN_LAUNCHER_NO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShowInLauncher {
+ }
+ /**
+ * Suggests that the launcher should show this user's apps in the main tab.
+ * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+ * this user is a profile, then its apps should be shown alongside its parent's apps.
+ */
+ public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0;
+ /**
+ * Suggests that the launcher should show this user's apps, but separately from the apps of this
+ * user's parent.
+ */
+ public static final int SHOW_IN_LAUNCHER_SEPARATE = 1;
+ /**
+ * Suggests that the launcher should not show this user.
+ */
+ public static final int SHOW_IN_LAUNCHER_NO = 2;
+
+ /**
+ * Reference to the default user properties for this user's user type.
+ * <li>If non-null, then any absent property will use the default property from here instead.
+ * <li>If null, then any absent property indicates that the caller lacks permission to see it,
+ * so attempting to get that property will trigger a SecurityException.
+ */
+ private final @Nullable UserProperties mDefaultProperties;
+
+ /**
+ * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
+ * default properties, which it uses for any property not subsequently set.
+ * @hide
+ */
+ public UserProperties(@NonNull UserProperties defaultProperties) {
+ mDefaultProperties = defaultProperties;
+ mPropertiesPresent = 0;
+ }
+
+ /**
+ * Copies the given UserProperties, excluding any information that doesn't satisfy the specified
+ * permissions.
+ * Can only be used on the original version (one that won't throw on permission errors).
+ * Note that, internally, this does not perform an exact copy.
+ * @hide
+ */
+ public UserProperties(UserProperties orig,
+ boolean exposeAllFields,
+ boolean hasManagePermission,
+ boolean hasQueryPermission) {
+
+ if (orig.mDefaultProperties == null) {
+ throw new IllegalArgumentException("Attempting to copy a non-original UserProperties.");
+ }
+
+ this.mDefaultProperties = null;
+
+ // NOTE: Copy each property using getters to ensure default values are copied if needed.
+ if (exposeAllFields) {
+ setStartWithParent(orig.getStartWithParent());
+ }
+ if (hasManagePermission) {
+ // Add any items that require this permission.
+ }
+ if (hasQueryPermission) {
+ // Add any items that require this permission.
+ }
+ // Add any items that require no permissions at all.
+ setShowInLauncher(orig.getShowInLauncher());
+ }
+
+ /**
+ * Indicates that the given property is being stored explicitly in this object.
+ * If false, it means that either
+ * <li>the default property for the user type should be used instead (for SystemServer callers)
+ * <li>the caller lacks permission to see this property (for all other callers)
+ */
+ private boolean isPresent(@PropertyIndex long index) {
+ return (mPropertiesPresent & (1L << index)) != 0;
+ }
+
+ /** Indicates that the given property is henceforth being explicitly stored in this object. */
+ private void setPresent(@PropertyIndex long index) {
+ mPropertiesPresent |= (1L << index);
+ }
+
+ /** @hide Returns the internal mPropertiesPresent value. Only for testing purposes. */
+ @VisibleForTesting
+ public long getPropertiesPresent() {
+ return mPropertiesPresent;
+ }
+
+ /**
+ * Returns whether, and how, a user should be shown in the Launcher.
+ * This is generally inapplicable for non-profile users.
+ *
+ * Possible return values include
+ * {@link #SHOW_IN_LAUNCHER_WITH_PARENT}},
+ * {@link #SHOW_IN_LAUNCHER_SEPARATE},
+ * and {@link #SHOW_IN_LAUNCHER_NO}.
+ *
+ * @return whether, and how, a profile should be shown in the Launcher.
+ */
+ public @ShowInLauncher int getShowInLauncher() {
+ if (isPresent(INDEX_SHOW_IN_LAUNCHER)) return mShowInLauncher;
+ if (mDefaultProperties != null) return mDefaultProperties.mShowInLauncher;
+ throw new SecurityException("You don't have permission to query showInLauncher");
+ }
+ /** @hide */
+ public void setShowInLauncher(@ShowInLauncher int val) {
+ this.mShowInLauncher = val;
+ setPresent(INDEX_SHOW_IN_LAUNCHER);
+ }
+ private @ShowInLauncher int mShowInLauncher;
+
+ /**
+ * Returns whether a profile should be started when its parent starts (unless in quiet mode).
+ * This only applies for users that have parents (i.e. for profiles).
+ * @hide
+ */
+ public boolean getStartWithParent() {
+ if (isPresent(INDEX_START_WITH_PARENT)) return mStartWithParent;
+ if (mDefaultProperties != null) return mDefaultProperties.mStartWithParent;
+ throw new SecurityException("You don't have permission to query startWithParent");
+ }
+ /** @hide */
+ public void setStartWithParent(boolean val) {
+ this.mStartWithParent = val;
+ setPresent(INDEX_START_WITH_PARENT);
+ }
+ private boolean mStartWithParent;
+
+ @Override
+ public String toString() {
+ // Please print in increasing order of PropertyIndex.
+ return "UserProperties{"
+ + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
+ + ", mShowInLauncher=" + getShowInLauncher()
+ + ", mStartWithParent=" + getStartWithParent()
+ + "}";
+ }
+
+ /**
+ * Print the UserProperties to the given PrintWriter.
+ * @hide
+ */
+ public void println(PrintWriter pw, String prefix) {
+ // Please print in increasing order of PropertyIndex.
+ pw.println(prefix + "UserProperties:");
+ pw.println(prefix + " mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
+ pw.println(prefix + " mShowInLauncher=" + getShowInLauncher());
+ pw.println(prefix + " mStartWithParent=" + getStartWithParent());
+ }
+
+ /**
+ * Reads in a UserProperties from an xml file, for use by the SystemServer.
+ *
+ * The serializer should already be inside a tag from which to read the user properties.
+ *
+ * @param defaultUserPropertiesReference the default UserProperties to use for this user type.
+ * @see #writeToXml
+ * @hide
+ */
+ public UserProperties(
+ TypedXmlPullParser parser,
+ @NonNull UserProperties defaultUserPropertiesReference)
+ throws IOException, XmlPullParserException {
+
+ this(defaultUserPropertiesReference);
+ updateFromXml(parser);
+ }
+
+ /**
+ * Parses the given xml file and updates this UserProperties with its data.
+ * I.e., if a piece of data is present in the xml, it will overwrite whatever was
+ * previously stored in this UserProperties.
+ * @hide
+ */
+ public void updateFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ final int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; i++) {
+ final String attributeName = parser.getAttributeName(i);
+ switch(attributeName) {
+ case ATTR_SHOW_IN_LAUNCHER:
+ setShowInLauncher(parser.getAttributeInt(i));
+ break;
+ case ATTR_START_WITH_PARENT:
+ setStartWithParent(parser.getAttributeBoolean(i));
+ break;
+ default:
+ Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
+ }
+ }
+ }
+
+ /**
+ * Writes the UserProperties, as used by the SystemServer, to the xml file.
+ *
+ * The serializer should already be inside a tag in which to write the user properties.
+ *
+ * @see #UserProperties(TypedXmlPullParser, UserProperties)
+ * @hide
+ */
+ public void writeToXml(TypedXmlSerializer serializer)
+ throws IOException, XmlPullParserException {
+
+ if (isPresent(INDEX_SHOW_IN_LAUNCHER)) {
+ serializer.attributeInt(null, ATTR_SHOW_IN_LAUNCHER, mShowInLauncher);
+ }
+ if (isPresent(INDEX_START_WITH_PARENT)) {
+ serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
+ }
+ }
+
+ // For use only with an object that has already had any permission-lacking fields stripped out.
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int parcelableFlags) {
+ dest.writeLong(mPropertiesPresent);
+ dest.writeInt(mShowInLauncher);
+ dest.writeBoolean(mStartWithParent);
+ }
+
+ /**
+ * Reads a UserProperties object from the parcel.
+ * Not suitable for the canonical SystemServer version since it lacks mDefaultProperties.
+ */
+ private UserProperties(@NonNull Parcel source) {
+ mDefaultProperties = null;
+
+ mPropertiesPresent = source.readLong();
+ mShowInLauncher = source.readInt();
+ mStartWithParent = source.readBoolean();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<UserProperties> CREATOR
+ = new Parcelable.Creator<UserProperties>() {
+ public UserProperties createFromParcel(Parcel source) {
+ return new UserProperties(source);
+ }
+ public UserProperties[] newArray(int size) {
+ return new UserProperties[size];
+ }
+ };
+
+ /**
+ * Builder for the SystemServer's {@link UserProperties}; see that class for documentation.
+ * Intended for building default values (and so all properties are present in the built object).
+ * @hide
+ */
+ public static final class Builder {
+ // UserProperties fields and their default values.
+ private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
+ private boolean mStartWithParent = false;
+
+ public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
+ mShowInLauncher = showInLauncher;
+ return this;
+ }
+
+ public Builder setStartWithParent(boolean startWithParent) {
+ mStartWithParent = startWithParent;
+ return this;
+ }
+
+ /** Builds a UserProperties object with *all* values populated. */
+ public UserProperties build() {
+ return new UserProperties(
+ mShowInLauncher,
+ mStartWithParent);
+ }
+ } // end Builder
+
+ /** Creates a UserProperties with the given properties. Intended for building default values. */
+ private UserProperties(
+ @ShowInLauncher int showInLauncher,
+ boolean startWithParent) {
+
+ mDefaultProperties = null;
+ setShowInLauncher(showInLauncher);
+ setStartWithParent(startWithParent);
+ }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 1e4c9501..11892fe 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -188,9 +188,6 @@
public static HardwareBuffer create(
@IntRange(from = 1) int width, @IntRange(from = 1) int height,
@Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
- if (!HardwareBuffer.isSupportedFormat(format)) {
- throw new IllegalArgumentException("Invalid pixel format " + format);
- }
if (width <= 0) {
throw new IllegalArgumentException("Invalid width " + width);
}
@@ -226,9 +223,6 @@
*/
public static boolean isSupported(@IntRange(from = 1) int width, @IntRange(from = 1) int height,
@Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
- if (!HardwareBuffer.isSupportedFormat(format)) {
- throw new IllegalArgumentException("Invalid pixel format " + format);
- }
if (width <= 0) {
throw new IllegalArgumentException("Invalid width " + width);
}
@@ -286,10 +280,7 @@
* Returns the width of this buffer in pixels.
*/
public int getWidth() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its width "
- + "cannot be obtained.");
- }
+ checkClosed("width");
return nGetWidth(mNativeObject);
}
@@ -297,10 +288,7 @@
* Returns the height of this buffer in pixels.
*/
public int getHeight() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its height "
- + "cannot be obtained.");
- }
+ checkClosed("height");
return nGetHeight(mNativeObject);
}
@@ -309,10 +297,7 @@
*/
@Format
public int getFormat() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its format "
- + "cannot be obtained.");
- }
+ checkClosed("format");
return nGetFormat(mNativeObject);
}
@@ -320,10 +305,7 @@
* Returns the number of layers in this buffer.
*/
public int getLayers() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its layer "
- + "count cannot be obtained.");
- }
+ checkClosed("layer count");
return nGetLayers(mNativeObject);
}
@@ -331,14 +313,27 @@
* Returns the usage flags of the usage hints set on this buffer.
*/
public long getUsage() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its usage "
- + "cannot be obtained.");
- }
+ checkClosed("usage");
return nGetUsage(mNativeObject);
}
/**
+ * Returns the system-wide unique id for this buffer
+ *
+ */
+ public long getId() {
+ checkClosed("id");
+ return nGetId(mNativeObject);
+ }
+
+ private void checkClosed(String name) {
+ if (isClosed()) {
+ throw new IllegalStateException("This HardwareBuffer has been closed and its "
+ + name + " cannot be obtained.");
+ }
+ }
+
+ /**
* Destroys this buffer immediately. Calling this method frees up any
* underlying native resources. After calling this method, this buffer
* must not be used in any way.
@@ -407,36 +402,6 @@
}
};
- /**
- * Validates whether a particular format is supported by HardwareBuffer.
- *
- * @param format The format to validate.
- *
- * @return True if <code>format</code> is a supported format. false otherwise.
- * See {@link #create(int, int, int, int, long)}.
- */
- private static boolean isSupportedFormat(@Format int format) {
- switch(format) {
- case RGBA_8888:
- case RGBA_FP16:
- case RGBA_1010102:
- case RGBX_8888:
- case RGB_565:
- case RGB_888:
- case BLOB:
- case YCBCR_420_888:
- case D_16:
- case D_24:
- case DS_24UI8:
- case D_FP32:
- case DS_FP32UI8:
- case S_UI8:
- case YCBCR_P010:
- return true;
- }
- return false;
- }
-
private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
long usage);
private static native long nCreateFromGraphicBuffer(GraphicBuffer graphicBuffer);
@@ -457,4 +422,6 @@
long usage);
@CriticalNative
private static native long nEstimateSize(long nativeObject);
+ @CriticalNative
+ private static native long nGetId(long nativeObject);
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 7092e43..7247ef7 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.CryptoObject;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.os.Binder;
@@ -674,6 +675,45 @@
}
/**
+ * Forwards BiometricStateListener to FaceService.
+ *
+ * @param listener new BiometricStateListener being added
+ * @hide
+ */
+ public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
+ try {
+ mService.registerBiometricStateListener(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Adds a callback that gets called when the service registers all of the face
+ * authenticators (HALs).
+ *
+ * If the face authenticators are already registered when the callback is added, the
+ * callback is invoked immediately.
+ *
+ * The callback is automatically removed after it's invoked.
+ *
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void addAuthenticatorsRegisteredCallback(
+ IFaceAuthenticatorsRegisteredCallback callback) {
+ if (mService != null) {
+ try {
+ mService.addAuthenticatorsRegisteredCallback(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ Slog.w(TAG, "addAuthenticatorsRegisteredCallback(): Service not connected!");
+ }
+ }
+
+ /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
new file mode 100644
index 0000000..78f978d2
--- /dev/null
+++ b/core/java/android/hardware/face/IFaceAuthenticatorsRegisteredCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.face;
+
+import android.hardware.face.FaceSensorPropertiesInternal;
+import java.util.List;
+
+/**
+ * Callback to notify FaceManager that FaceService has registered all of the
+ * face authenticators (HALs).
+ * See {@link android.hardware.face.IFaceService#registerAuthenticators}.
+ *
+ * @hide
+ */
+oneway interface IFaceAuthenticatorsRegisteredCallback {
+ /**
+ * Notifies FaceManager that all of the face authenticators have been registered.
+ *
+ * @param sensors A consolidated list of sensor properties for all of the authenticators.
+ */
+ void onAllAuthenticatorsRegistered(in List<FaceSensorPropertiesInternal> sensors);
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 369248e..9b56f43 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -17,9 +17,11 @@
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -163,4 +165,11 @@
// hidlSensors must be non-null and empty. See AuthService.java
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
+
+ // Adds a callback which gets called when the service registers all of the face
+ // authenticators. The callback is automatically removed after it's invoked.
+ void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
+
+ // Registers BiometricStateListener.
+ void registerBiometricStateListener(IBiometricStateListener listener);
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index cc7ed18..1ba9a04 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -202,8 +202,10 @@
void setSidefpsController(in ISidefpsController controller);
// Registers BiometricStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerBiometricStateListener(IBiometricStateListener listener);
// Sends a power button pressed event to all listeners.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
oneway void onPowerPressed();
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a9d665c8..621eab5 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1963,7 +1963,6 @@
}
private static Object mServiceLock = new Object();
- private static ISoundTriggerMiddlewareService mService;
/**
* Translate an exception thrown from interaction with the underlying service to an error code.
@@ -2217,20 +2216,12 @@
binder =
ServiceManager.getServiceOrThrow(
Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
- binder.linkToDeath(() -> {
- synchronized (mServiceLock) {
- mService = null;
- }
- }, 0);
- mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
- break;
+ return ISoundTriggerMiddlewareService.Stub.asInterface(binder);
} catch (Exception e) {
Log.e(TAG, "Failed to bind to soundtrigger service", e);
}
}
- return mService;
}
-
}
/**
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index be57372..d6f191e 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -37,6 +37,7 @@
import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.vcn.util.PersistableBundleUtils;
@@ -58,6 +59,8 @@
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
public final class IkeSessionParamsUtils {
+ private static final String TAG = IkeSessionParamsUtils.class.getSimpleName();
+
private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY";
private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY";
@@ -72,6 +75,13 @@
private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY";
private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY";
+ // TODO: b/243181760 Use the IKE API when they are exposed
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6;
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7;
+
private static final Set<Integer> IKE_OPTIONS = new ArraySet<>();
static {
@@ -80,6 +90,26 @@
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE);
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500);
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
+ IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+ IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+ IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+ }
+
+ /**
+ * Check if an IKE option is supported in the IPsec module installed on the device
+ *
+ * <p>This method ensures caller to safely access options that are added between dessert
+ * releases.
+ */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static boolean isIkeOptionValid(int option) {
+ try {
+ new IkeSessionParams.Builder().addIkeOption(option);
+ return true;
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Option not supported; discarding: " + option);
+ return false;
+ }
}
/** Serializes an IkeSessionParams to a PersistableBundle. */
@@ -130,7 +160,7 @@
// IKE_OPTION is defined in IKE module and added in the IkeSessionParams
final List<Integer> enabledIkeOptions = new ArrayList<>();
for (int option : IKE_OPTIONS) {
- if (params.hasIkeOption(option)) {
+ if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
enabledIkeOptions.add(option);
}
}
@@ -205,12 +235,16 @@
// Clear IKE Options that are by default enabled
for (int option : IKE_OPTIONS) {
- builder.removeIkeOption(option);
+ if (isIkeOptionValid(option)) {
+ builder.removeIkeOption(option);
+ }
}
final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY);
for (int option : optionArray) {
- builder.addIkeOption(option);
+ if (isIkeOptionValid(option)) {
+ builder.addIkeOption(option);
+ }
}
return builder.build();
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 09a52e4..9d05cec0 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -955,16 +955,7 @@
public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5;
- /**
- * Note that these must match the constants in android.os.PowerManager.
- * Also, if the user activity types change, the BatteryStatsImpl.VERSION must
- * also be bumped.
- */
- static final String[] USER_ACTIVITY_TYPES = {
- "other", "button", "touch", "accessibility", "attention"
- };
-
- public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
+ public static final int NUM_USER_ACTIVITY_TYPES = PowerManager.USER_ACTIVITY_EVENT_MAX + 1;
public abstract void noteUserActivityLocked(int type);
public abstract boolean hasUserActivity();
@@ -6168,7 +6159,7 @@
}
sb.append(val);
sb.append(" ");
- sb.append(Uid.USER_ACTIVITY_TYPES[i]);
+ sb.append(PowerManager.userActivityEventToString(i));
}
}
if (hasData) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index f69d6b0..fa6b118 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -23,6 +23,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.IntentSender;
import android.content.RestrictionEntry;
import android.graphics.Bitmap;
@@ -71,9 +72,11 @@
boolean isUserOfType(int userId, in String userType);
@UnsupportedAppUsage
UserInfo getUserInfo(int userId);
+ UserProperties getUserPropertiesCopy(int userId);
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
+ boolean isUserSwitcherEnabled(int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
int getUserSerialNumber(int userId);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 01b75d1..a3a3e3f 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -348,6 +348,44 @@
public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
/**
+ * @hide
+ */
+ public static final int USER_ACTIVITY_EVENT_MAX = USER_ACTIVITY_EVENT_DEVICE_STATE;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
+ USER_ACTIVITY_EVENT_OTHER,
+ USER_ACTIVITY_EVENT_BUTTON,
+ USER_ACTIVITY_EVENT_TOUCH,
+ USER_ACTIVITY_EVENT_ACCESSIBILITY,
+ USER_ACTIVITY_EVENT_ATTENTION,
+ USER_ACTIVITY_EVENT_FACE_DOWN,
+ USER_ACTIVITY_EVENT_DEVICE_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserActivityEvent{}
+
+ /**
+ *
+ * Convert the user activity event to a string for debugging purposes.
+ * @hide
+ */
+ public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
+ switch (userActivityEvent) {
+ case USER_ACTIVITY_EVENT_OTHER: return "other";
+ case USER_ACTIVITY_EVENT_BUTTON: return "button";
+ case USER_ACTIVITY_EVENT_TOUCH: return "touch";
+ case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
+ case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
+ case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
+ case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
+ default: return Integer.toString(userActivityEvent);
+ }
+ }
+
+ /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 16352b8..ca0af2c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -51,6 +51,7 @@
import android.content.IntentSender;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -2786,7 +2787,7 @@
return isUserRunning(user.getIdentifier());
}
- /** {@hide} */
+ /** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserRunning(@UserIdInt int userId) {
@@ -2841,6 +2842,7 @@
/**
* @hide
*/
+ @TestApi
public static boolean isUsersOnSecondaryDisplaysEnabled() {
return SystemProperties.getBoolean("fw.users_on_secondary_displays",
Resources.getSystem()
@@ -2968,7 +2970,7 @@
}
};
- /** {@hide} */
+ /** @hide */
@UnsupportedAppUsage
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
@@ -2976,13 +2978,13 @@
return mIsUserUnlockedCache.query(userId);
}
- /** {@hide} */
+ /** @hide */
public void disableIsUserUnlockedCache() {
mIsUserUnlockedCache.disableLocal();
mIsUserUnlockingOrUnlockedCache.disableLocal();
}
- /** {@hide} */
+ /** @hide */
public static final void invalidateIsUserUnlockedCache() {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
}
@@ -3012,7 +3014,7 @@
return isUserUnlockingOrUnlocked(user.getIdentifier());
}
- /** {@hide} */
+ /** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
@@ -3083,6 +3085,30 @@
}
/**
+ * Returns a {@link UserProperties} object describing the properties of the given user.
+ *
+ * Note that the caller may not have permission to access all items; requesting any item for
+ * which permission is lacking will throw a {@link SecurityException}.
+ *
+ * <p> Requires
+ * {@code android.Manifest.permission#MANAGE_USERS},
+ * {@code android.Manifest.permission#QUERY_USERS}, or
+ * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * permission, or else the caller must be in the same profile group as the caller.
+ *
+ * @param userHandle the user handle of the user whose information is being requested.
+ * @return a UserProperties object for a specific user.
+ * @throws IllegalArgumentException if {@code userHandle} doesn't correspond to an existing user
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) {
+ return mUserPropertiesCache.query(userHandle.getIdentifier());
+ }
+
+ /**
* @hide
*
* Returns who set a user restriction on a user.
@@ -5220,23 +5246,13 @@
})
@UserHandleAware
public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) {
- if (!supportsMultipleUsers()) {
- return false;
- }
- if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId)) {
- return false;
- }
- // If Demo Mode is on, don't show user switcher
- if (isDeviceInDemoMode(mContext)) {
- return false;
- }
- // Check the Settings.Global.USER_SWITCHER_ENABLED that the user can toggle on/off.
- final boolean userSwitcherSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.USER_SWITCHER_ENABLED,
- Resources.getSystem().getBoolean(R.bool.config_showUserSwitcherByDefault) ? 1 : 0)
- != 0;
- if (!userSwitcherSettingOn) {
- return false;
+
+ try {
+ if (!mService.isUserSwitcherEnabled(mUserId)) {
+ return false;
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
// The feature is enabled. But is it worth showing?
@@ -5480,11 +5496,37 @@
}
};
- /** {@hide} */
+ /** @hide */
public static final void invalidateStaticUserProperties() {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STATIC_USER_PROPERTIES);
}
+ /* Cache key for UserProperties object. */
+ private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+
+ // TODO: It would be better to somehow have this as static, so that it can work cross-context.
+ private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
+ new PropertyInvalidatedCache<Integer, UserProperties>(16, CACHE_KEY_USER_PROPERTIES) {
+ @Override
+ public UserProperties recompute(Integer userId) {
+ try {
+ // If the userId doesn't exist, this will throw rather than cache garbage.
+ return mService.getUserPropertiesCopy(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+ /** @hide */
+ public static final void invalidateUserPropertiesCache() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_PROPERTIES);
+ }
+
/**
* @hide
* User that enforces a restriction.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index baf3eeca..8a50e79 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10661,6 +10661,13 @@
public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption";
/**
+ * Whether to enable media controls on lock screen.
+ * When enabled, media controls will appear on lock screen.
+ * @hide
+ */
+ public static final String MEDIA_CONTROLS_LOCK_SCREEN = "media_controls_lock_screen";
+
+ /**
* Controls whether contextual suggestions can be shown in the media controls.
* @hide
*/
@@ -10841,6 +10848,23 @@
"accessibility_software_cursor_enabled";
/**
+ * Software Cursor settings that specifies whether trigger hints are enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED =
+ "accessibility_software_cursor_trigger_hints_enabled";
+
+ /**
+ * Software Cursor settings that specifies whether triggers are shifted when the keyboard
+ * is shown.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED =
+ "accessibility_software_cursor_keyboard_shift_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a2cb1d5..4a72a62 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -314,6 +314,9 @@
(int) (startValue.right + fraction * (endValue.right - startValue.right)),
(int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+ /** Logging listener. */
+ private WindowInsetsAnimationControlListener mLoggingListener;
+
/**
* The default implementation of listener, to be used by InsetsController and InsetsPolicy to
* animate insets.
@@ -330,6 +333,7 @@
private final long mDurationMs;
private final boolean mDisable;
private final int mFloatingImeBottomInset;
+ private final WindowInsetsAnimationControlListener mLoggingListener;
private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
new ThreadLocal<AnimationHandler>() {
@@ -343,7 +347,7 @@
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
@InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
- int floatingImeBottomInset) {
+ int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
mShow = show;
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
@@ -351,12 +355,16 @@
mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
+ mLoggingListener = loggingListener;
}
@Override
public void onReady(WindowInsetsAnimationController controller, int types) {
mController = controller;
if (DEBUG) Log.d(TAG, "default animation onReady types: " + types);
+ if (mLoggingListener != null) {
+ mLoggingListener.onReady(controller, types);
+ }
if (mDisable) {
onAnimationFinish();
@@ -410,6 +418,9 @@
public void onFinished(WindowInsetsAnimationController controller) {
if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onFinished types:"
+ Type.toString(mRequestedTypes));
+ if (mLoggingListener != null) {
+ mLoggingListener.onFinished(controller);
+ }
}
@Override
@@ -420,6 +431,9 @@
}
if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onCancelled types:"
+ mRequestedTypes);
+ if (mLoggingListener != null) {
+ mLoggingListener.onCancelled(controller);
+ }
}
protected Interpolator getInsetsInterpolator() {
@@ -1147,6 +1161,13 @@
updateRequestedVisibilities();
}
+ // TODO(b/242962223): Make this setter restrictive.
+ @Override
+ public void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener) {
+ mLoggingListener = listener;
+ }
+
/**
* @return Pair of (types ready to animate, IME ready to animate).
*/
@@ -1159,7 +1180,7 @@
final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
boolean show = animationType == ANIMATION_TYPE_SHOW
|| animationType == ANIMATION_TYPE_USER;
- boolean canRun = false;
+ boolean canRun = true;
if (show) {
// Show request
if (fromIme) {
@@ -1169,7 +1190,6 @@
}
switch(consumer.requestShow(fromIme)) {
case ShowResult.SHOW_IMMEDIATELY:
- canRun = true;
break;
case ShowResult.IME_SHOW_DELAYED:
imeReady = false;
@@ -1180,6 +1200,7 @@
+ fromIme);
// IME cannot be shown (since it didn't have focus), proceed
// with animation of other types.
+ canRun = false;
break;
}
} else {
@@ -1189,7 +1210,6 @@
if (!fromIme) {
consumer.notifyHidden();
}
- canRun = true;
}
if (!canRun) {
if (WARN) Log.w(TAG, String.format(
@@ -1460,7 +1480,8 @@
boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
final InternalAnimationControlListener listener = new InternalAnimationControlListener(
show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
- skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
+ skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
+ mLoggingListener);
// We are about to playing the default animation (show/hide). Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 1e1c250..6d64022 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -81,8 +81,8 @@
per-file DisplayCutout.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file DisplayCutout.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file IDisplay*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
-per-file Inset*.java = file:/services/core/java/com/android/server/wm/OWNERS
-per-file Inset*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Inset*.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Inset*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file IPinnedStackListener.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file IRecents*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file IRemote*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
@@ -94,7 +94,6 @@
per-file SurfaceControl*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file SurfaceSession.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file SyncRtSurfaceTransactionApplier.java = file:/services/core/java/com/android/server/wm/OWNERS
-per-file ViewRootInsetsControllerHost.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file Window*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file TransactionCommittedCallback.java = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index c61baf6..3fe9110 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@
private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
+ private WindowInsetsAnimationControlListener mLoggingListener;
@Override
public void show(int types) {
@@ -176,6 +177,9 @@
controller.addOnControllableInsetsChangedListener(
mControllableInsetsChangedListeners.get(i));
}
+ if (mLoggingListener != null) {
+ controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener);
+ }
// Reset all state so it doesn't get applied twice just in case
mRequests.clear();
@@ -184,7 +188,7 @@
mAppearance = 0;
mAppearanceMask = 0;
mAnimationsDisabled = false;
-
+ mLoggingListener = null;
// After replaying, we forward everything directly to the replayed instance.
mReplayedInsetsController = controller;
}
@@ -198,6 +202,16 @@
}
@Override
+ public void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener) {
+ if (mReplayedInsetsController != null) {
+ mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ } else {
+ mLoggingListener = listener;
+ }
+ }
+
+ @Override
public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@Nullable Interpolator interpolator,
CancellationSignal cancellationSignal,
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 5721fa6..3acb053 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -28,8 +28,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
-import android.window.WindowTokenClient;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.window.WindowTokenClient;
import java.util.Objects;
@@ -271,14 +271,8 @@
/** @hide */
public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
@NonNull WindowlessWindowManager wwm) {
- this(c, d, wwm, false /* useSfChoreographer */);
- }
-
- /** @hide */
- public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
- @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
mWm = wwm;
- mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout(), useSfChoreographer);
+ mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
addConfigCallback(c, d);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 674f0a2..59bc061 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -173,6 +173,7 @@
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityManager.HighTextContrastChangeListener;
@@ -910,17 +911,11 @@
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
- this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout(),
- false /* useSfChoreographer */);
+ this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
WindowLayout windowLayout) {
- this(context, display, session, windowLayout, false /* useSfChoreographer */);
- }
-
- public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
- WindowLayout windowLayout, boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
mWindowLayout = windowLayout;
@@ -952,8 +947,7 @@
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
// TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
- mChoreographer = useSfChoreographer
- ? Choreographer.getSfInstance() : Choreographer.getInstance();
+ mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
mHandwritingInitiator = new HandwritingInitiator(
@@ -5325,6 +5319,7 @@
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
+ mAccessibilityInteractionConnectionManager.ensureNoDirectConnection();
removeSendWindowContentChangedCallback();
destroyHardwareRenderer();
@@ -9577,6 +9572,14 @@
}
}
+ /**
+ * Return the connection ID for the {@link AccessibilityInteractionController} of this instance.
+ * @see AccessibilityNodeInfo#makeQueryableFromAppProcess(View)
+ */
+ public int getDirectAccessibilityConnectionId() {
+ return mAccessibilityInteractionConnectionManager.ensureDirectConnection();
+ }
+
@Override
public boolean showContextMenuForChild(View originalView) {
return false;
@@ -10452,6 +10455,8 @@
*/
final class AccessibilityInteractionConnectionManager
implements AccessibilityStateChangeListener {
+ private int mDirectConnectionId = AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID;
+
@Override
public void onAccessibilityStateChanged(boolean enabled) {
if (enabled) {
@@ -10495,6 +10500,21 @@
mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
}
}
+
+ public int ensureDirectConnection() {
+ if (mDirectConnectionId == AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID) {
+ mDirectConnectionId = AccessibilityInteractionClient.addDirectConnection(
+ new AccessibilityInteractionConnection(ViewRootImpl.this));
+ }
+ return mDirectConnectionId;
+ }
+
+ public void ensureNoDirectConnection() {
+ if (mDirectConnectionId != AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID) {
+ AccessibilityInteractionClient.removeConnection(mDirectConnectionId);
+ mDirectConnectionId = AccessibilityNodeInfo.UNDEFINED_CONNECTION_ID;
+ }
+ }
}
final class HighContrastTextManager implements HighTextContrastChangeListener {
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 227b9f4..63f9e13 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -201,6 +201,21 @@
@NonNull WindowInsetsAnimationControlListener listener);
/**
+ * Lets the application add non-controllable listener object that can be called back
+ * when animation is invoked by the system by host calling methods such as {@link #show} or
+ * {@link #hide}.
+ *
+ * The listener is supposed to be used for logging only, using the control or
+ * relying on the timing of the callback in any other way is not supported.
+ *
+ * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when
+ * the animation is driven by the system and not the host
+ * @hide
+ */
+ void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener);
+
+ /**
* Controls the appearance of system bars.
* <p>
* For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}:
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fe8d64f..a49caaf 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1054,6 +1054,14 @@
}
}
+ /**
+ * Ensure scales are between 0 and 20.
+ * @hide
+ */
+ static float fixScale(float scale) {
+ return Math.max(Math.min(scale, 20), 0);
+ }
+
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
/**
* X position for this window. With the default gravity it is ignored.
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 6853278..227a8ef 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -114,6 +114,10 @@
private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
new SparseArray<>();
+ // Used to generate connection ids for direct app-process connections. Start sufficiently far
+ // enough from the connection ids generated by AccessibilityManagerService.
+ private static int sDirectConnectionIdCounter = 1 << 30;
+
/** List of timestamps which indicate the latest time an a11y service receives a scroll event
from a window, mapping from windowId -> timestamp. */
private static final SparseLongArray sScrollingWindows = new SparseLongArray();
@@ -232,6 +236,12 @@
return;
}
synchronized (sConnectionCache) {
+ IAccessibilityServiceConnection existingConnection = getConnection(connectionId);
+ if (existingConnection instanceof DirectAccessibilityConnection) {
+ throw new IllegalArgumentException(
+ "Cannot add service connection with id " + connectionId
+ + " which conflicts with existing direct connection.");
+ }
sConnectionCache.put(connectionId, connection);
if (!initializeCache) {
return;
@@ -242,6 +252,33 @@
}
/**
+ * Adds a new {@link DirectAccessibilityConnection} using the provided
+ * {@link IAccessibilityInteractionConnection} to create a direct connection between
+ * this client and the {@link android.view.ViewRootImpl} for queries inside the app process.
+ *
+ * <p>
+ * See {@link DirectAccessibilityConnection} for supported methods.
+ * </p>
+ *
+ * @param connection The ViewRootImpl's {@link IAccessibilityInteractionConnection}.
+ */
+ public static int addDirectConnection(IAccessibilityInteractionConnection connection) {
+ synchronized (sConnectionCache) {
+ int connectionId = sDirectConnectionIdCounter++;
+ if (getConnection(connectionId) != null) {
+ throw new IllegalArgumentException(
+ "Cannot add direct connection with existing id " + connectionId);
+ }
+ DirectAccessibilityConnection directAccessibilityConnection =
+ new DirectAccessibilityConnection(connection);
+ sConnectionCache.put(connectionId, directAccessibilityConnection);
+ // Do not use AccessibilityCache for this connection, since there is no corresponding
+ // AccessibilityService to handle cache invalidation events.
+ return connectionId;
+ }
+ }
+
+ /**
* Gets a cached associated with the connection id if available.
*
*/
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 953f261..5d52750 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -58,6 +58,7 @@
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.widget.TextView;
import com.android.internal.R;
@@ -82,7 +83,9 @@
* </p>
* <p>
* Once an accessibility node info is delivered to an accessibility service it is
- * made immutable and calling a state mutation method generates an error.
+ * made immutable and calling a state mutation method generates an error. See
+ * {@link #makeQueryableFromAppProcess(View)} if you would like to inspect the
+ * node tree from the app process for testing or debugging tools.
* </p>
* <p>
* Please refer to {@link android.accessibilityservice.AccessibilityService} for
@@ -1156,8 +1159,8 @@
* @param index The child index.
* @return The child node.
*
- * @throws IllegalStateException If called outside of an AccessibilityService.
- *
+ * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+ * calling {@link #makeQueryableFromAppProcess(View)}.
*/
public AccessibilityNodeInfo getChild(int index) {
return getChild(index, FLAG_PREFETCH_DESCENDANTS_HYBRID);
@@ -1171,7 +1174,8 @@
* @param prefetchingStrategy the prefetching strategy.
* @return The child node.
*
- * @throws IllegalStateException If called outside of an AccessibilityService.
+ * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+ * calling {@link #makeQueryableFromAppProcess(View)}.
*
* @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
*/
@@ -1893,6 +1897,9 @@
* Gets the parent.
*
* @return The parent.
+ *
+ * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+ * calling {@link #makeQueryableFromAppProcess(View)}.
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();
@@ -1920,7 +1927,8 @@
* @param prefetchingStrategy the prefetching strategy.
* @return The parent.
*
- * @throws IllegalStateException If called outside of an AccessibilityService.
+ * @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
+ * calling {@link #makeQueryableFromAppProcess(View)}.
*
* @see #FLAG_PREFETCH_ANCESTORS
* @see #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST
@@ -3642,6 +3650,47 @@
}
/**
+ * Connects this node to the View's root so that operations on this node can query the entire
+ * {@link AccessibilityNodeInfo} tree and perform accessibility actions on nodes.
+ *
+ * <p>
+ * This is intended for short-lived inspections from testing or debugging tools in the app
+ * process. After calling this method, all nodes linked to this node (children, ancestors, etc.)
+ * are also queryable. Operations on this node tree will only succeed as long as the associated
+ * view hierarchy remains attached to a window.
+ * </p>
+ *
+ * <p>
+ * Calling this method more than once on the same node is a no-op; if you wish to inspect a
+ * different view hierarchy then create a new node from any view in that hierarchy and call this
+ * method on that node.
+ * </p>
+ *
+ * <p>
+ * Testing or debugging tools should create this {@link AccessibilityNodeInfo} node using
+ * {@link View#createAccessibilityNodeInfo()} or {@link AccessibilityNodeProvider} and call this
+ * method, then navigate and interact with the node tree by calling methods on the node.
+ * </p>
+ *
+ * @param view The view that generated this node, or any view in the same view-root hierarchy.
+ * @throws IllegalStateException If called from an {@link AccessibilityService}, or if provided
+ * a {@link View} that is not attached to a window.
+ */
+ public void makeQueryableFromAppProcess(@NonNull View view) {
+ enforceNotSealed();
+ if (mConnectionId != UNDEFINED_CONNECTION_ID) {
+ return;
+ }
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ if (viewRootImpl == null) {
+ throw new IllegalStateException(
+ "Cannot link a node to a view that is not attached to a window.");
+ }
+ setConnectionId(viewRootImpl.getDirectAccessibilityConnectionId());
+ }
+
+ /**
* Sets if this instance is sealed.
*
* @param sealed Whether is sealed.
@@ -3665,15 +3714,21 @@
return mSealed;
}
+ private static boolean usingDirectConnection(int connectionId) {
+ return AccessibilityInteractionClient.getConnection(
+ connectionId) instanceof DirectAccessibilityConnection;
+ }
+
/**
- * Enforces that this instance is sealed.
+ * Enforces that this instance is sealed, unless using a {@link DirectAccessibilityConnection}
+ * which allows queries while the node is not sealed.
*
* @throws IllegalStateException If this instance is not sealed.
*
* @hide
*/
protected void enforceSealed() {
- if (!isSealed()) {
+ if (!usingDirectConnection(mConnectionId) && !isSealed()) {
throw new IllegalStateException("Cannot perform this "
+ "action on a not sealed instance.");
}
@@ -4499,7 +4554,8 @@
private static boolean canPerformRequestOverConnection(int connectionId,
int windowId, long accessibilityNodeId) {
- return ((windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
+ final boolean hasWindowId = windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ return ((usingDirectConnection(connectionId) || hasWindowId)
&& (getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID)
&& (connectionId != UNDEFINED_CONNECTION_ID));
}
diff --git a/core/java/android/view/accessibility/DirectAccessibilityConnection.java b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
new file mode 100644
index 0000000..71746ee
--- /dev/null
+++ b/core/java/android/view/accessibility/DirectAccessibilityConnection.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Matrix;
+import android.graphics.Region;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.view.MagnificationSpec;
+
+/**
+ * Minimal {@link IAccessibilityServiceConnection} implementation that interacts
+ * with the {@link android.view.AccessibilityInteractionController} of a
+ * {@link android.view.ViewRootImpl}.
+ *
+ * <p>
+ * Uses {@link android.view.ViewRootImpl}'s {@link IAccessibilityServiceConnection} that wraps
+ * {@link android.view.AccessibilityInteractionController} within the app process, so that no
+ * interprocess communication is performed.
+ * </p>
+ *
+ * <p>
+ * Only the following methods are supported:
+ * <li>{@link #findAccessibilityNodeInfoByAccessibilityId}</li>
+ * <li>{@link #findAccessibilityNodeInfosByText}</li>
+ * <li>{@link #findAccessibilityNodeInfosByViewId}</li>
+ * <li>{@link #findFocus}</li>
+ * <li>{@link #focusSearch}</li>
+ * <li>{@link #performAccessibilityAction}</li>
+ * </p>
+ *
+ * <p>
+ * Other methods are no-ops and return default values.
+ * </p>
+ */
+class DirectAccessibilityConnection extends IAccessibilityServiceConnection.Default {
+ private final IAccessibilityInteractionConnection mAccessibilityInteractionConnection;
+
+ // Fetch all views, but do not use prefetching/cache since this "connection" does not
+ // receive cache invalidation events (as it is not linked to an AccessibilityService).
+ private static final int FETCH_FLAGS =
+ AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS
+ | AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ private static final MagnificationSpec MAGNIFICATION_SPEC = new MagnificationSpec();
+ private static final int PID = Process.myPid();
+ private static final Region INTERACTIVE_REGION = null;
+ private static final float[] TRANSFORM_MATRIX = new float[9];
+
+ static {
+ Matrix.IDENTITY_MATRIX.getValues(TRANSFORM_MATRIX);
+ }
+
+ DirectAccessibilityConnection(
+ IAccessibilityInteractionConnection accessibilityInteractionConnection) {
+ mAccessibilityInteractionConnection = accessibilityInteractionConnection;
+ }
+
+ @Override
+ public String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+ long accessibilityNodeId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
+ Bundle arguments) throws RemoteException {
+ mAccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId(
+ accessibilityNodeId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID,
+ threadId, MAGNIFICATION_SPEC, TRANSFORM_MATRIX, arguments);
+ return new String[0];
+ }
+
+ @Override
+ public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId,
+ long accessibilityNodeId, String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId)
+ throws RemoteException {
+ mAccessibilityInteractionConnection.findAccessibilityNodeInfosByText(accessibilityNodeId,
+ text, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+ MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+ return new String[0];
+ }
+
+ @Override
+ public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId)
+ throws RemoteException {
+ mAccessibilityInteractionConnection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
+ viewId, INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+ MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+ return new String[0];
+ }
+
+ @Override
+ public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId)
+ throws RemoteException {
+ mAccessibilityInteractionConnection.findFocus(accessibilityNodeId, focusType,
+ INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+ MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+ return new String[0];
+ }
+
+ @Override
+ public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId)
+ throws RemoteException {
+ mAccessibilityInteractionConnection.focusSearch(accessibilityNodeId, direction,
+ INTERACTIVE_REGION, interactionId, callback, FETCH_FLAGS, PID, threadId,
+ MAGNIFICATION_SPEC, TRANSFORM_MATRIX);
+ return new String[0];
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
+ int action, Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId)
+ throws RemoteException {
+ mAccessibilityInteractionConnection.performAccessibilityAction(accessibilityNodeId, action,
+ arguments, interactionId, callback, FETCH_FLAGS, PID, threadId);
+ return true;
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8d3cf6d..6049613 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -91,6 +91,7 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowInsets;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -2150,8 +2151,9 @@
null /* icProto */);
synchronized (mH) {
final View view = getServedViewLocked();
- if (mImeInsetsConsumer != null && view != null) {
- if (mImeInsetsConsumer.isRequestedVisible()) {
+ if (view != null) {
+ final WindowInsets rootInsets = view.getRootWindowInsets();
+ if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) {
hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
} else {
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index e567ced..cd15df84 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -16,7 +16,7 @@
package android.window;
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -26,16 +26,15 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -74,12 +73,6 @@
*/
private final Executor mExecutor;
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
- /** Map from Task id to client tokens of TaskFragments in the Task. */
- private final SparseArray<List<IBinder>> mTaskIdToFragmentTokens = new SparseArray<>();
- /** Map from Task id to Task configuration. */
- private final SparseArray<Configuration> mTaskIdToConfigurations = new SparseArray<>();
-
public TaskFragmentOrganizer(@NonNull Executor executor) {
mExecutor = executor;
}
@@ -147,14 +140,35 @@
}
}
- /** Called when a TaskFragment is created and organized by this organizer. */
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+ /**
+ * Called when a TaskFragment is created and organized by this organizer.
+ *
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskFragmentInfo Info of the TaskFragment that is created.
+ */
+ public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {}
- /** Called when the status of an organized TaskFragment is changed. */
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+ /**
+ * Called when the status of an organized TaskFragment is changed.
+ *
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskFragmentInfo Info of the TaskFragment that is changed.
+ */
+ public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {}
- /** Called when an organized TaskFragment is removed. */
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+ /**
+ * Called when an organized TaskFragment is removed.
+ *
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskFragmentInfo Info of the TaskFragment that is removed.
+ */
+ public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {}
/**
* Called when the parent leaf Task of organized TaskFragments is changed.
@@ -164,35 +178,21 @@
* For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
* Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
* bounds.
- */
- public void onTaskFragmentParentInfoChanged(
- @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {}
-
- /**
- * Called when the parent leaf Task of organized TaskFragments is changed.
- * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
- * transaction.
*
- * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
- * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
- * bounds.
- * @hide
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskId Id of the parent Task that is changed.
+ * @param parentConfig Config of the parent Task.
*/
- public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
- final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
- if (tokens == null || tokens.isEmpty()) {
- return;
- }
- for (int i = tokens.size() - 1; i >= 0; i--) {
- onTaskFragmentParentInfoChanged(tokens.get(i), parentConfig);
- }
- }
+ public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId,
+ @NonNull Configuration parentConfig) {}
/**
* Called when the {@link WindowContainerTransaction} created with
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
*
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
* @param errorCallbackToken token set in
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
* @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
@@ -201,16 +201,18 @@
* transaction operation.
* @param exception exception from the server side.
*/
- public void onTaskFragmentError(
+ public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {}
/**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
* when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
- * orginial Task. In this case, we need to notify the organizer so that it can check if the
+ * original Task. In this case, we need to notify the organizer so that it can check if the
* Activity matches any split rule.
*
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
* @param taskId The Task that the activity is reparented to.
* @param activityIntent The intent that the activity is original launched with.
* @param activityToken If the activity belongs to the same process as the organizer, this
@@ -218,61 +220,41 @@
* different process, the server will generate a temporary token that
* the organizer can use to reparent the activity through
* {@link WindowContainerTransaction} if needed.
- * @hide
*/
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {}
+ public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {}
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
+ * Note: {@link WindowOrganizer#applyTransaction} permission requirement is conditional for
+ * {@link TaskFragmentOrganizer}.
+ * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission
* @hide
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
- // TODO(b/240519866): apply all changes in one WCT.
final int taskId = change.getTaskId();
switch (change.getType()) {
case TYPE_TASK_FRAGMENT_APPEARED:
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
- // release.
- if (!mTaskIdToFragmentTokens.contains(taskId)) {
- mTaskIdToFragmentTokens.put(taskId, new ArrayList<>());
- }
- mTaskIdToFragmentTokens.get(taskId).add(change.getTaskFragmentToken());
- onTaskFragmentParentInfoChanged(change.getTaskFragmentToken(),
- mTaskIdToConfigurations.get(taskId));
-
- onTaskFragmentAppeared(change.getTaskFragmentInfo());
+ onTaskFragmentAppeared(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_INFO_CHANGED:
- onTaskFragmentInfoChanged(change.getTaskFragmentInfo());
+ onTaskFragmentInfoChanged(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_VANISHED:
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
- // release.
- if (mTaskIdToFragmentTokens.contains(taskId)) {
- final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
- tokens.remove(change.getTaskFragmentToken());
- if (tokens.isEmpty()) {
- mTaskIdToFragmentTokens.remove(taskId);
- mTaskIdToConfigurations.remove(taskId);
- }
- }
-
- onTaskFragmentVanished(change.getTaskFragmentInfo());
+ onTaskFragmentVanished(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
- // release.
- mTaskIdToConfigurations.put(taskId, change.getTaskConfiguration());
-
- onTaskFragmentParentInfoChanged(taskId, change.getTaskConfiguration());
+ onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
break;
case TYPE_TASK_FRAGMENT_ERROR:
final Bundle errorBundle = change.getErrorBundle();
onTaskFragmentError(
+ wct,
change.getErrorCallbackToken(),
errorBundle.getParcelable(
KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
@@ -280,8 +262,9 @@
errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
java.lang.Throwable.class));
break;
- case TYPE_ACTIVITY_REPARENT_TO_TASK:
- onActivityReparentToTask(
+ case TYPE_ACTIVITY_REPARENTED_TO_TASK:
+ onActivityReparentedToTask(
+ wct,
change.getTaskId(),
change.getActivityIntent(),
change.getActivityToken());
@@ -291,6 +274,8 @@
"Unknown TaskFragmentEvent=" + change.getType());
}
}
+ // TODO(b/240519866): notify TaskFragmentOrganizerController that the transition is done.
+ applyTransaction(wct);
}
@Override
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 755864f..07e8e8c 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -122,7 +122,7 @@
* then exits Picture-in-picture, it will be reparented back to its original Task. In this case,
* we need to notify the organizer so that it can check if the Activity matches any split rule.
*/
- public static final int TYPE_ACTIVITY_REPARENT_TO_TASK = 6;
+ public static final int TYPE_ACTIVITY_REPARENTED_TO_TASK = 6;
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_TASK_FRAGMENT_APPEARED,
@@ -130,7 +130,7 @@
TYPE_TASK_FRAGMENT_VANISHED,
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED,
TYPE_TASK_FRAGMENT_ERROR,
- TYPE_ACTIVITY_REPARENT_TO_TASK
+ TYPE_ACTIVITY_REPARENTED_TO_TASK
})
@Retention(RetentionPolicy.SOURCE)
@interface ChangeType {}
@@ -247,7 +247,7 @@
/**
* Intent of the activity that is reparented to the Task for
- * {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ * {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
*/
public Change setActivityIntent(@NonNull Intent intent) {
mActivityIntent = requireNonNull(intent);
@@ -255,7 +255,7 @@
}
/**
- * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
* If the activity belongs to the same process as the organizer, this will be the actual
* activity token; if the activity belongs to a different process, the server will generate
* a temporary token that the organizer can use to reparent the activity through
diff --git a/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java b/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
index d2cb30e..833d88c 100644
--- a/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
@@ -20,6 +20,8 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.internal.R;
@@ -36,11 +38,21 @@
private final Locale mSecondaryLocale;
private final int mSecondaryLocaleTextDir;
+ private final boolean mShowSelection;
+ private LocaleStore.LocaleInfo mSelectedLocaleInfo;
public BilingualSuggestedLocaleAdapter(
Set<LocaleStore.LocaleInfo> localeOptions,
boolean countryMode,
Locale secondaryLocale) {
+ this(localeOptions, countryMode, secondaryLocale, false);
+ }
+
+ public BilingualSuggestedLocaleAdapter(
+ Set<LocaleStore.LocaleInfo> localeOptions,
+ boolean countryMode,
+ Locale secondaryLocale,
+ boolean showLastSelected) {
super(localeOptions, countryMode);
mSecondaryLocale = secondaryLocale;
if (TextUtils.getLayoutDirectionFromLocale(secondaryLocale) == View.LAYOUT_DIRECTION_RTL) {
@@ -48,6 +60,7 @@
} else {
mSecondaryLocaleTextDir = View.TEXT_DIRECTION_LTR;
}
+ mShowSelection = showLastSelected;
}
@Override
@@ -90,11 +103,55 @@
}
LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
+ if (mShowSelection) {
+ setItemState(isSelectedLocaleInfo(item), convertView);
+ }
setLocaleToListItem(convertView, item);
}
return convertView;
}
+ /**
+ * Set locale info as selected. Selected info can be the only one. Passing null would result to
+ * nothing is selected.
+ */
+ public void setSelectedLocaleInfo(LocaleStore.LocaleInfo info) {
+ mSelectedLocaleInfo = info;
+ notifyDataSetChanged();
+ }
+
+ /** Return selected locale info. */
+ public LocaleStore.LocaleInfo getSelectedLocaleInfo() {
+ return mSelectedLocaleInfo;
+ }
+
+ private boolean isSelectedLocaleInfo(LocaleStore.LocaleInfo item) {
+ return item != null
+ && mSelectedLocaleInfo != null
+ && item.getId().equals(mSelectedLocaleInfo.getId());
+ }
+
+ private void setItemState(boolean selected, View itemView) {
+ RelativeLayout background = (RelativeLayout) itemView;
+ ImageView indicator = itemView.findViewById(R.id.indicator);
+ TextView textNative = itemView.findViewById(R.id.locale_native);
+ TextView textSecondary = itemView.findViewById(R.id.locale_secondary);
+
+ if (indicator == null || textNative == null || textSecondary == null) {
+ return;
+ }
+
+ textNative.setSelected(selected);
+ textSecondary.setSelected(selected);
+ if (selected) {
+ background.setBackgroundResource(R.drawable.language_picker_item_bg_selected);
+ indicator.setVisibility(View.VISIBLE);
+ } else {
+ background.setBackgroundResource(0);
+ indicator.setVisibility(View.GONE);
+ }
+ }
+
private void setHeaderText(
TextView textView, int languageStringResourceId, int regionStringResourceId) {
if (mCountryMode) {
@@ -114,7 +171,7 @@
textNative.setTextLocale(localeInfo.getLocale());
textNative.setContentDescription(localeInfo.getContentDescription(mCountryMode));
- TextView textSecondary = (TextView) itemView.findViewById(R.id.locale_secondary);
+ TextView textSecondary = itemView.findViewById(R.id.locale_secondary);
textSecondary.setText(localeInfo.getLocale().getDisplayLanguage(mSecondaryLocale));
textSecondary.setTextDirection(mSecondaryLocaleTextDir);
if (mCountryMode) {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index fa6fa55..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1169,15 +1169,7 @@
}
- /** @hide */
- public static void startForWifi(Context context) {
- new BinderCallsStats.SettingsObserver(
- context,
- new BinderCallsStats(
- new BinderCallsStats.Injector(),
- com.android.internal.os.BinderLatencyProto.Dims.WIFI));
- }
/**
* Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
index 3eae89e..836786d 100644
--- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java
+++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
@@ -170,9 +170,9 @@
int delayTimeMs = mShowDelayConfigMs;
try {
- final float scale = Settings.Global.getFloat(
+ final float scale = WindowManager.fixScale(Settings.Global.getFloat(
anchor.getContext().getContentResolver(),
- Settings.Global.ANIMATOR_DURATION_SCALE);
+ Settings.Global.ANIMATOR_DURATION_SCALE));
delayTimeMs *= scale;
} catch (Settings.SettingNotFoundException e) {
// do nothing
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index f462523..5fcc46e 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -163,7 +163,7 @@
static jlong android_hardware_HardwareBuffer_getUsage(JNIEnv* env,
jobject clazz, jlong nativeObject) {
GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
- return AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage());
+ return static_cast<jlong>(AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage()));
}
static jlong android_hardware_HardwareBuffer_estimateSize(jlong nativeObject) {
@@ -177,7 +177,12 @@
const uint32_t bufferStride =
buffer->getStride() > 0 ? buffer->getStride() : buffer->getWidth();
- return static_cast<jlong>(buffer->getHeight() * bufferStride * bpp);
+ return static_cast<jlong>(static_cast<uint64_t>(buffer->getHeight() * bufferStride * bpp));
+}
+
+static jlong android_hardware_HardwareBuffer_getId(jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jlong>(buffer->getId());
}
// ----------------------------------------------------------------------------
@@ -223,16 +228,6 @@
}
}
-GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
- JNIEnv* env, jobject hardwareBufferObj) {
- if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
- return GraphicBufferWrapper_to_GraphicBuffer(
- env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
- } else {
- return nullptr;
- }
-}
-
jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(hardwareBuffer);
@@ -295,6 +290,7 @@
// --------------- @CriticalNative ----------------------
{ "nEstimateSize", "(J)J", (void*) android_hardware_HardwareBuffer_estimateSize },
+ { "nGetId", "(J)J", (void*) android_hardware_HardwareBuffer_getId },
};
// clang-format on
diff --git a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
index dfd8035..964c28f 100644
--- a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -28,10 +28,6 @@
extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
JNIEnv* env, jobject hardwareBufferObj);
-/* Gets the underlying GraphicBuffer for a HardwareBuffer. */
-extern GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
- JNIEnv* env, jobject hardwareBufferObj);
-
/* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer);
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 322354b..789ceff 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -89,6 +89,14 @@
// Setting for accessibility magnification for following typing.
optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_software_cursor_enabled = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ message SoftwareCursorSettings {
+ optional SettingProto trigger_hints_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto keyboard_shift_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+
+ optional SoftwareCursorSettings accessibility_software_cursor_settings = 45 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/drawable/bilingual_language_item_selection_indicator.xml b/core/res/res/drawable/bilingual_language_item_selection_indicator.xml
new file mode 100644
index 0000000..78f26cc
--- /dev/null
+++ b/core/res/res/drawable/bilingual_language_item_selection_indicator.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp" android:height="24dp"
+ android:viewportWidth="24" android:viewportHeight="24">
+ <path android:fillColor="@color/language_picker_item_selected_indicator"
+ android:pathData="M9.55,18l-5.7,-5.7 1.425,-1.425L9.55,15.15l9.175,-9.175L20.15,7.4z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_bg_selected.xml b/core/res/res/drawable/language_picker_item_bg_selected.xml
new file mode 100644
index 0000000..ef9a8e7
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_bg_selected.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:bottom="-5dp"
+ android:right="-5dp"
+ android:top="-5dp">
+ <shape android:shape="rectangle" >
+ <solid android:color="@color/language_picker_item_selected_bg" />
+
+ <stroke
+ android:width="2dp"
+ android:color="@color/language_picker_item_selected_stroke" />
+ </shape>
+ </item>
+
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_text_color2_selector.xml b/core/res/res/drawable/language_picker_item_text_color2_selector.xml
new file mode 100644
index 0000000..624ae29
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_text_color2_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:color="@color/language_picker_item_text_color_secondary_selected" />
+ <item android:color="@color/language_picker_item_text_color_secondary" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_text_color_selector.xml b/core/res/res/drawable/language_picker_item_text_color_selector.xml
new file mode 100644
index 0000000..8d419f8
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_text_color_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:color="@color/language_picker_item_text_color_selected" />
+ <item android:color="@color/language_picker_item_text_color" />
+</selector>
\ No newline at end of file
diff --git a/core/res/res/layout/language_picker_bilingual_item.xml b/core/res/res/layout/language_picker_bilingual_item.xml
index f56dda9..def0cccf 100644
--- a/core/res/res/layout/language_picker_bilingual_item.xml
+++ b/core/res/res/layout/language_picker_bilingual_item.xml
@@ -1,20 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/language_item"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ >
+
+<LinearLayout
android:id="@+id/frame"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:orientation="vertical">
+ android:orientation="vertical"
+ >
<TextView
android:id="@+id/locale_native"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/language_picker_item_text_color"
+ android:textColor="@drawable/language_picker_item_text_color_selector"
android:textSize="18sp"
android:textAppearance="?android:attr/textAppearanceListItem"
/>
@@ -23,9 +30,20 @@
android:id="@+id/locale_secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/language_picker_item_text_color_secondary"
+ android:textColor="@drawable/language_picker_item_text_color2_selector"
android:textSize="16sp"
android:textAppearance="?android:attr/textAppearanceListItem"
/>
+</LinearLayout>
-</LinearLayout>
\ No newline at end of file
+<ImageView
+ android:id="@+id/indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:adjustViewBounds="true"
+ android:layout_marginEnd="10dp"
+ android:layout_centerVertical="true"
+ android:src="@drawable/bilingual_language_item_selection_indicator"
+ android:visibility="gone" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 88171ce..d3f998f 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -41,6 +41,11 @@
<!-- Lily Language Picker language item view colors -->
<color name="language_picker_item_text_color">#F1F3F4</color>
<color name="language_picker_item_text_color_secondary">#BDC1C6</color>
+ <color name="language_picker_item_text_color_selected">#202124</color>
+ <color name="language_picker_item_text_color_secondary_selected">#202124</color>
+ <color name="language_picker_item_selected_indicator">#202124</color>
+ <color name="language_picker_item_selected_bg">#92B3F2</color>
+ <color name="language_picker_item_selected_stroke">#185ABC</color>
<!-- Color for side fps toast dark theme-->
<color name="side_fps_toast_background">#2E3132</color>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 8b1b46d..77d7c43 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -451,6 +451,11 @@
<!-- Lily Language Picker language item view colors -->
<color name="language_picker_item_text_color">#202124</color>
<color name="language_picker_item_text_color_secondary">#5F6368</color>
+ <color name="language_picker_item_text_color_selected">#202124</color>
+ <color name="language_picker_item_text_color_secondary_selected">#5F6368</color>
+ <color name="language_picker_item_selected_indicator">#4285F4</color>
+ <color name="language_picker_item_selected_bg">#E8F0FE</color>
+ <color name="language_picker_item_selected_stroke">#4C8DF6</color>
<!-- Color for side fps toast light theme -->
<color name="side_fps_toast_background">#F7F9FA</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9890614..b59cf7f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5872,4 +5872,10 @@
<!-- The number of tasks to scan to get the visibility of Home -->
<integer name="config_maxScanTasksForHomeVisibility">10</integer>
+
+ <!-- Device state that corresponds to rear display mode, feature provided
+ through Jetpack WindowManager
+ TODO(b/236022708) Move rear display state to device state config file
+ -->
+ <integer name="config_deviceStateRearDisplay">-1</integer>
</resources>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index e9b42d3..78ec145 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -23,7 +23,7 @@
<item>ak-GH</item> <!-- Akan (Ghana) -->
<item>am-ET</item> <!-- Amharic (Ethiopia) -->
<item>ar-AE</item> <!-- Arabic (United Arab Emirates) -->
- <item>ar-AE-u-nu-latn</item> <!-- Arabic (United Arab Emirates, Western Digits) -->
+ <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic Digits) -->
<item>ar-BH</item> <!-- Arabic (Bahrain) -->
<item>ar-BH-u-nu-latn</item> <!-- Arabic (Bahrain, Western Digits) -->
<item>ar-DJ</item> <!-- Arabic (Djibouti) -->
@@ -190,6 +190,7 @@
<item>en-MS</item> <!-- English (Montserrat) -->
<item>en-MT</item> <!-- English (Malta) -->
<item>en-MU</item> <!-- English (Mauritius) -->
+ <item>en-MV</item> <!-- English (Maldives) -->
<item>en-MW</item> <!-- English (Malawi) -->
<item>en-MY</item> <!-- English (Malaysia) -->
<item>en-NA</item> <!-- English (Namibia) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3d0493..b7da6ae 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6321,6 +6321,8 @@
<string name="vdm_camera_access_denied" product="default">Can’t access the phone’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
<!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] -->
<string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
+ <!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] -->
+ <string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string>
<!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
<string name="system_locale_title">System default</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 00dab04..d60fc20 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -97,10 +97,12 @@
<java-symbol type="id" name="icon" />
<java-symbol type="id" name="image" />
<java-symbol type="id" name="increment" />
+ <java-symbol type="id" name="indicator" />
<java-symbol type="id" name="internalEmpty" />
<java-symbol type="id" name="inputExtractAccessories" />
<java-symbol type="id" name="inputExtractAction" />
<java-symbol type="id" name="issued_on" />
+ <java-symbol type="id" name="language_item" />
<java-symbol type="id" name="left_icon" />
<java-symbol type="id" name="leftSpacer" />
<java-symbol type="id" name="line1" />
@@ -3126,6 +3128,12 @@
<java-symbol type="id" name="locale_search_menu" />
<java-symbol type="layout" name="language_picker_item" />
<java-symbol type="layout" name="language_picker_bilingual_item" />
+ <java-symbol type="color" name="language_picker_item_text_color" />
+ <java-symbol type="color" name="language_picker_item_text_color_selected" />
+ <java-symbol type="color" name="language_picker_item_text_color_secondary_selected" />
+ <java-symbol type="drawable" name="language_picker_item_text_color_selector" />
+ <java-symbol type="drawable" name="language_picker_item_text_color2_selector" />
+ <java-symbol type="drawable" name="language_picker_item_bg_selected" />
<java-symbol type="layout" name="language_picker_section_header" />
<java-symbol type="layout" name="language_picker_bilingual_section_header" />
<java-symbol type="menu" name="language_selection_list" />
@@ -4795,6 +4803,7 @@
<!-- For VirtualDeviceManager -->
<java-symbol type="string" name="vdm_camera_access_denied" />
+ <java-symbol type="string" name="vdm_secure_window" />
<java-symbol type="color" name="camera_privacy_light_day"/>
<java-symbol type="color" name="camera_privacy_light_night"/>
@@ -4821,6 +4830,7 @@
<java-symbol type="drawable" name="ic_swap_horiz" />
<java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
<java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
+ <java-symbol type="integer" name="config_deviceStateRearDisplay"/>
<!-- For app language picker -->
<java-symbol type="string" name="system_locale_title" />
diff --git a/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.java
new file mode 100644
index 0000000..2a3da05
--- /dev/null
+++ b/core/tests/coretests/src/android/app/NotificationChannelGroupTest.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 android.app;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NotificationChannelGroupTest {
+ private final String CLASS = "android.app.NotificationChannelGroup";
+
+ @Test
+ public void testLongStringFields() {
+ NotificationChannelGroup group = new NotificationChannelGroup("my_group_01", "groupName");
+
+ try {
+ String longString = Strings.repeat("A", 65536);
+ Field mName = Class.forName(CLASS).getDeclaredField("mName");
+ mName.setAccessible(true);
+ mName.set(group, longString);
+ Field mId = Class.forName(CLASS).getDeclaredField("mId");
+ mId.setAccessible(true);
+ mId.set(group, longString);
+ Field mDescription = Class.forName(CLASS).getDeclaredField("mDescription");
+ mDescription.setAccessible(true);
+ mDescription.set(group, longString);
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ Parcel parcel = Parcel.obtain();
+ group.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ NotificationChannelGroup fromParcel =
+ NotificationChannelGroup.CREATOR.createFromParcel(parcel);
+ assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getId().length());
+ assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH, fromParcel.getName().length());
+ assertEquals(NotificationChannelGroup.MAX_TEXT_LENGTH,
+ fromParcel.getDescription().length());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
new file mode 100644
index 0000000..647bfe8
--- /dev/null
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static junit.framework.TestCase.assertEquals;
+
+import android.net.Uri;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NotificationChannelTest {
+ private final String CLASS = "android.app.NotificationChannel";
+
+ @Test
+ public void testLongStringFields() {
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ try {
+ String longString = Strings.repeat("A", 65536);
+ Field mName = Class.forName(CLASS).getDeclaredField("mName");
+ mName.setAccessible(true);
+ mName.set(channel, longString);
+ Field mId = Class.forName(CLASS).getDeclaredField("mId");
+ mId.setAccessible(true);
+ mId.set(channel, longString);
+ Field mDesc = Class.forName(CLASS).getDeclaredField("mDesc");
+ mDesc.setAccessible(true);
+ mDesc.set(channel, longString);
+ Field mParentId = Class.forName(CLASS).getDeclaredField("mParentId");
+ mParentId.setAccessible(true);
+ mParentId.set(channel, longString);
+ Field mGroup = Class.forName(CLASS).getDeclaredField("mGroup");
+ mGroup.setAccessible(true);
+ mGroup.set(channel, longString);
+ Field mConversationId = Class.forName(CLASS).getDeclaredField("mConversationId");
+ mConversationId.setAccessible(true);
+ mConversationId.set(channel, longString);
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ Parcel parcel = Parcel.obtain();
+ channel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel);
+ assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getId().length());
+ assertEquals(NotificationChannel.MAX_TEXT_LENGTH, fromParcel.getName().length());
+ assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+ fromParcel.getDescription().length());
+ assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+ fromParcel.getParentChannelId().length());
+ assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+ fromParcel.getGroup().length());
+ assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+ fromParcel.getConversationId().length());
+ }
+
+ @Test
+ public void testLongAlertFields() {
+ NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+ channel.setSound(Uri.parse("content://" + Strings.repeat("A",65536)),
+ Notification.AUDIO_ATTRIBUTES_DEFAULT);
+ channel.setVibrationPattern(new long[65550/2]);
+
+ Parcel parcel = Parcel.obtain();
+ channel.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ NotificationChannel fromParcel = NotificationChannel.CREATOR.createFromParcel(parcel);
+ assertEquals(NotificationChannel.MAX_VIBRATION_LENGTH,
+ fromParcel.getVibrationPattern().length);
+ assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
+ fromParcel.getSound().toString().length());
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/OWNERS b/core/tests/coretests/src/android/hardware/input/OWNERS
new file mode 100644
index 0000000..3f8a602
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/OWNERS
@@ -0,0 +1,2 @@
+include /core/java/android/hardware/input/OWNERS
+
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ed6a649..cc68fce 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -236,6 +236,21 @@
}
@Test
+ public void testSystemDrivenInsetsAnimationLoggingListener_onReady() {
+ prepareControls();
+ // only the original thread that created view hierarchy can touch its views
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ WindowInsetsAnimationControlListener loggingListener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
+ mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
+ // since there is no focused view, forcefully make IME visible.
+ mController.show(Type.ime(), true /* fromIme */);
+ verify(loggingListener).onReady(notNull(), anyInt());
+ });
+ }
+
+ @Test
public void testAnimationEndState() {
InsetsSourceControl[] controls = prepareControls();
InsetsSourceControl navBar = controls[0];
@@ -914,6 +929,23 @@
});
}
+ @Test
+ public void testImeRequestedVisibleWhenImeNotControllable() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Simulate IME insets is not controllable
+ mController.onControlsChanged(new InsetsSourceControl[0]);
+ final InsetsSourceConsumer imeInsetsConsumer = mController.getSourceConsumer(ITYPE_IME);
+ assertNull(imeInsetsConsumer.getControl());
+
+ // Verify IME requested visibility should be updated to IME consumer from controller.
+ mController.show(ime());
+ assertTrue(imeInsetsConsumer.isRequestedVisible());
+
+ mController.hide(ime());
+ assertFalse(imeInsetsConsumer.isRequestedVisible());
+ });
+ }
+
private void waitUntilNextFrame() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 03c8b1b..690b3587 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -213,6 +213,25 @@
}
@Test
+ public void testSystemDrivenInsetsAnimationLoggingListener() {
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(eq(listener));
+ }
+
+ @Test
+ public void testSystemDrivenInsetsAnimationLoggingListener_direct() {
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(
+ eq(listener));
+ }
+
+ @Test
public void testDetachReattach() {
mPendingInsetsController.show(systemBars());
mPendingInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index b341a4e..c93b733 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -26,10 +26,21 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
UNKNOWN,
+ /**
+ * Since some APIs accept either ImageFormat or PixelFormat (and the two
+ * enums do not overlap since they're both partial versions of the
+ * internal format enum), add PixelFormat values here so linting
+ * tools won't complain when method arguments annotated with
+ * ImageFormat are provided with PixelFormat values.
+ */
+ PixelFormat.RGBA_8888,
+ PixelFormat.RGBX_8888,
+ PixelFormat.RGB_888,
RGB_565,
YV12,
Y8,
Y16,
+ YCBCR_P010,
NV16,
NV21,
YUY2,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index bdf703c..7e9c418 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -20,11 +20,14 @@
import android.content.Context;
import androidx.annotation.NonNull;
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.area.WindowAreaComponentImpl;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.SplitController;
import androidx.window.extensions.layout.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+
/**
* The reference implementation of {@link WindowExtensions} that implements the initial API version.
*/
@@ -33,10 +36,12 @@
private final Object mLock = new Object();
private volatile WindowLayoutComponent mWindowLayoutComponent;
private volatile SplitController mSplitController;
+ private volatile WindowAreaComponent mWindowAreaComponent;
+ // TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 1;
+ return 2;
}
/**
@@ -75,4 +80,23 @@
}
return mSplitController;
}
+
+ /**
+ * Returns a reference implementation of {@link WindowAreaComponent} if available,
+ * {@code null} otherwise. The implementation must match the API level reported in
+ * {@link WindowExtensions#getWindowAreaComponent()}.
+ * @return {@link WindowAreaComponent} OEM implementation.
+ */
+ public WindowAreaComponent getWindowAreaComponent() {
+ if (mWindowAreaComponent == null) {
+ synchronized (mLock) {
+ if (mWindowAreaComponent == null) {
+ Context context = ActivityThread.currentApplication();
+ mWindowAreaComponent =
+ new WindowAreaComponentImpl(context);
+ }
+ }
+ }
+ return mWindowAreaComponent;
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
new file mode 100644
index 0000000..3adae70
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -0,0 +1,255 @@
+/*
+ * 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 androidx.window.extensions.area;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Reference implementation of androidx.window.extensions.area OEM interface for use with
+ * WindowManager Jetpack.
+ *
+ * This component currently supports Rear Display mode with the ability to add and remove
+ * status listeners for this mode.
+ *
+ * The public methods in this class are thread-safe.
+ **/
+public class WindowAreaComponentImpl implements WindowAreaComponent,
+ DeviceStateManager.DeviceStateCallback {
+
+ private final Object mLock = new Object();
+
+ private final DeviceStateManager mDeviceStateManager;
+ private final Executor mExecutor;
+
+ @GuardedBy("mLock")
+ private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
+ private final int mRearDisplayState;
+ @WindowAreaSessionState
+ private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
+
+ @GuardedBy("mLock")
+ private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+ @GuardedBy("mLock")
+ private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE;
+ @GuardedBy("mLock")
+ private DeviceStateRequest mDeviceStateRequest;
+
+ public WindowAreaComponentImpl(@NonNull Context context) {
+ mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+ mExecutor = context.getMainExecutor();
+
+ // TODO(b/236022708) Move rear display state to device state config file
+ mRearDisplayState = context.getResources().getInteger(
+ R.integer.config_deviceStateRearDisplay);
+
+ mDeviceStateManager.registerCallback(mExecutor, this);
+ }
+
+ /**
+ * Adds a listener interested in receiving updates on the RearDisplayStatus
+ * of the device. Because this is being called from the OEM provided
+ * extensions, we will post the result of the listener on the executor
+ * provided by the developer at the initial call site.
+ *
+ * Depending on the initial state of the device, we will return either
+ * {@link WindowAreaComponent#STATUS_AVAILABLE} or
+ * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that
+ * state respectively. When the rear display feature is triggered, we update the status to be
+ * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_
+ *
+ * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently
+ * enabled.
+ *
+ * @param consumer {@link Consumer} interested in receiving updates to the status of
+ * rear display mode.
+ */
+ public void addRearDisplayStatusListener(
+ @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
+ synchronized (mLock) {
+ mRearDisplayStatusListeners.add(consumer);
+
+ // If current device state is still invalid, we haven't gotten our initial value yet
+ if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+ return;
+ }
+ consumer.accept(getCurrentStatus());
+ }
+ }
+
+ /**
+ * Removes a listener no longer interested in receiving updates.
+ * @param consumer no longer interested in receiving updates to RearDisplayStatus
+ */
+ public void removeRearDisplayStatusListener(
+ @NonNull Consumer<@WindowAreaStatus Integer> consumer) {
+ synchronized (mLock) {
+ mRearDisplayStatusListeners.remove(consumer);
+ }
+ }
+
+ /**
+ * Creates and starts a rear display session and provides updates to the
+ * callback provided. Because this is being called from the OEM provided
+ * extensions, we will post the result of the listener on the executor
+ * provided by the developer at the initial call site.
+ *
+ * When we enable rear display mode, we submit a request to {@link DeviceStateManager}
+ * to override the device state to the state that corresponds to RearDisplay
+ * mode. When the {@link DeviceStateRequest} is activated, we let the
+ * consumer know that the session is active by sending
+ * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}.
+ *
+ * @param activity to provide updates to the client on
+ * the status of the Session
+ * @param rearDisplaySessionCallback to provide updates to the client on
+ * the status of the Session
+ */
+ public void startRearDisplaySession(@NonNull Activity activity,
+ @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) {
+ synchronized (mLock) {
+ if (mDeviceStateRequest != null) {
+ // Rear display session is already active
+ throw new IllegalStateException(
+ "Unable to start new rear display session as one is already active");
+ }
+ mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build();
+ mDeviceStateManager.requestState(
+ mDeviceStateRequest,
+ mExecutor,
+ new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback)
+ );
+ }
+ }
+
+ /**
+ * Ends the current rear display session and provides updates to the
+ * callback provided. Because this is being called from the OEM provided
+ * extensions, we will post the result of the listener on the executor
+ * provided by the developer.
+ */
+ public void endRearDisplaySession() {
+ synchronized (mLock) {
+ if (mDeviceStateRequest != null || isRearDisplayActive()) {
+ mDeviceStateRequest = null;
+ mDeviceStateManager.cancelStateRequest();
+ } else {
+ throw new IllegalStateException(
+ "Unable to cancel a rear display session as there is no active session");
+ }
+ }
+ }
+
+ @Override
+ public void onBaseStateChanged(int state) {
+ synchronized (mLock) {
+ mCurrentDeviceBaseState = state;
+ if (state == mCurrentDeviceState) {
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ synchronized (mLock) {
+ mCurrentDeviceState = state;
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int getCurrentStatus() {
+ if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
+ || isRearDisplayActive()) {
+ return WindowAreaComponent.STATUS_UNAVAILABLE;
+ }
+ return WindowAreaComponent.STATUS_AVAILABLE;
+ }
+
+ /**
+ * Helper method to determine if a rear display session is currently active by checking
+ * if the current device configuration matches that of rear display. This would be true
+ * if there is a device override currently active (base state != current state) and the current
+ * state is that which corresponds to {@code mRearDisplayState}
+ * @return {@code true} if the device is in rear display mode and {@code false} if not
+ */
+ @GuardedBy("mLock")
+ private boolean isRearDisplayActive() {
+ return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState
+ == mRearDisplayState);
+ }
+
+ @GuardedBy("mLock")
+ private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) {
+ synchronized (mLock) {
+ for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
+ mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
+ }
+ }
+ }
+
+ /**
+ * Callback for the {@link DeviceStateRequest} to be notified of when the request has been
+ * activated or cancelled. This callback provides information to the client library
+ * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback}
+ */
+ private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback {
+
+ private final Consumer<Integer> mRearDisplaySessionCallback;
+
+ DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
+ mRearDisplaySessionCallback = callback;
+ }
+
+ @Override
+ public void onRequestActivated(@NonNull DeviceStateRequest request) {
+ synchronized (mLock) {
+ if (request.equals(mDeviceStateRequest)) {
+ mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
+ mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+ }
+
+ @Override
+ public void onRequestCanceled(DeviceStateRequest request) {
+ synchronized (mLock) {
+ if (request.equals(mDeviceStateRequest)) {
+ mDeviceStateRequest = null;
+ }
+ mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
+ mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
+ updateStatusConsumers(getCurrentStatus());
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d42fca2..1335e5e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -51,28 +51,37 @@
@VisibleForTesting
final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
+ @NonNull
private final TaskFragmentCallback mCallback;
+
@VisibleForTesting
+ @Nullable
TaskFragmentAnimationController mAnimationController;
/**
* Callback that notifies the controller about changes to task fragments.
*/
interface TaskFragmentCallback {
- void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig);
- void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken);
- void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType);
+ void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig);
+ void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken);
+ void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @Nullable TaskFragmentInfo taskFragmentInfo, int opType);
}
/**
* @param executor callbacks from WM Core are posted on this executor. It should be tied to the
* UI thread that all other calls into methods of this class are also on.
*/
- JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) {
+ JetpackTaskFragmentOrganizer(@NonNull Executor executor,
+ @NonNull TaskFragmentCallback callback) {
super(executor);
mCallback = callback;
}
@@ -147,41 +156,31 @@
* @param wct WindowContainerTransaction in which the task fragment should be resized.
* @param fragmentToken token of an existing TaskFragment.
*/
- void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ void expandTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
}
/**
- * Expands an existing TaskFragment to fill parent.
- * @param fragmentToken token of an existing TaskFragment.
- */
- void expandTaskFragment(IBinder fragmentToken) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- expandTaskFragment(wct, fragmentToken);
- applyTransaction(wct);
- }
-
- /**
* Expands an Activity to fill parent by moving it to a new TaskFragment.
* @param fragmentToken token to create new TaskFragment with.
* @param activity activity to move to the fill-parent TaskFragment.
*/
- void expandActivity(IBinder fragmentToken, Activity activity) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull Activity activity) {
createTaskFragmentAndReparentActivity(
wct, fragmentToken, activity.getActivityToken(), new Rect(),
WINDOWING_MODE_UNDEFINED, activity);
- applyTransaction(wct);
}
/**
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
- IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+ void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
final TaskFragmentCreationParams fragmentOptions =
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
wct.createTaskFragment(fragmentOptions);
@@ -191,9 +190,9 @@
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- private void createTaskFragmentAndReparentActivity(
- WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
- @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
+ private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+ @WindowingMode int windowingMode, @NonNull Activity activity) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
}
@@ -202,9 +201,9 @@
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- private void createTaskFragmentAndStartActivity(
- WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
- @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
+ private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+ @WindowingMode int windowingMode, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
@@ -225,8 +224,8 @@
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
- TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
- Rect bounds, @WindowingMode int windowingMode) {
+ TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
if (mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"There is an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -241,7 +240,7 @@
.build();
}
- void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+ void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect bounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
@@ -253,8 +252,8 @@
wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
}
- void updateWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken,
- @WindowingMode int windowingMode) {
+ void updateWindowingMode(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -262,7 +261,8 @@
wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
}
- void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -271,60 +271,49 @@
}
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
- if (mCallback != null) {
- mCallback.onTaskFragmentAppeared(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo);
}
@Override
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
- if (mCallback != null) {
- mCallback.onTaskFragmentInfoChanged(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo);
}
@Override
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
-
- if (mCallback != null) {
- mCallback.onTaskFragmentVanished(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentVanished(wct, taskFragmentInfo);
}
@Override
- public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
- if (mCallback != null) {
- mCallback.onTaskFragmentParentInfoChanged(taskId, parentConfig);
- }
+ public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig) {
+ mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig);
}
@Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {
- if (mCallback != null) {
- mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken);
- }
+ public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
+ mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken);
}
@Override
- public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
+ public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder errorCallbackToken,
@Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {
if (taskFragmentInfo != null) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
}
-
- if (mCallback != null) {
- mCallback.onTaskFragmentError(taskFragmentInfo, opType);
- }
+ mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index f09a910..c8ac0fc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -16,17 +16,21 @@
package androidx.window.extensions.embedding;
-import android.annotation.NonNull;
import android.app.Activity;
import android.util.Pair;
import android.util.Size;
+import androidx.annotation.NonNull;
+
/**
* Client-side descriptor of a split that holds two containers.
*/
class SplitContainer {
+ @NonNull
private final TaskFragmentContainer mPrimaryContainer;
+ @NonNull
private final TaskFragmentContainer mSecondaryContainer;
+ @NonNull
private final SplitRule mSplitRule;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index dad0739..0597809f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -145,35 +145,36 @@
}
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
}
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
- mPresenter.updateContainer(container);
+ updateContainer(wct, container);
}
updateCallbackIfNecessary();
}
}
@Override
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
}
- final WindowContainerTransaction wct = new WindowContainerTransaction();
final boolean wasInPip = isInPictureInPicture(container);
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
final boolean isInPip = isInPictureInPicture(container);
// Check if there are no running activities - consider the container empty if there are
// no non-finishing activities left.
@@ -183,15 +184,15 @@
// Instead, the original split should be cleanup, and the dependent may be
// expanded to fullscreen.
cleanupForEnterPip(wct, container);
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
// Do not finish the dependents if this TaskFragment was cleared due to
// launching activity in the Task.
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
- mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
// No update until exit PIP.
@@ -208,13 +209,13 @@
// needed.
updateContainer(wct, container);
}
- mPresenter.applyTransaction(wct);
updateCallbackIfNecessary();
}
}
@Override
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
final TaskFragmentContainer container = getContainer(
taskFragmentInfo.getFragmentToken());
@@ -225,9 +226,7 @@
final TaskFragmentContainer newTopContainer = getTopActiveContainer(
container.getTaskId());
if (newTopContainer != null) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
updateContainer(wct, newTopContainer);
- mPresenter.applyTransaction(wct);
}
updateCallbackIfNecessary();
}
@@ -236,7 +235,8 @@
}
@Override
- public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
+ public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig) {
synchronized (mLock) {
onTaskConfigurationChanged(taskId, parentConfig);
if (isInPictureInPicture(parentConfig)) {
@@ -256,7 +256,7 @@
final TaskFragmentContainer container = containers.get(i);
// Wait until onTaskFragmentAppeared to update new container.
if (!container.isFinished() && !container.isWaitingActivityAppear()) {
- mPresenter.updateContainer(container);
+ updateContainer(wct, container);
}
}
updateCallbackIfNecessary();
@@ -264,7 +264,8 @@
}
@Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) {
synchronized (mLock) {
// If the activity belongs to the current app process, we treat it as a new activity
@@ -275,10 +276,10 @@
// launching to top. We allow split as primary for activity reparent because the
// activity may be split as primary before it is reparented out. In that case, we
// want to show it as primary again when it is reparented back.
- if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
+ if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
- placeActivityInTopContainer(activity);
+ placeActivityInTopContainer(wct, activity);
}
updateCallbackIfNecessary();
return;
@@ -293,7 +294,6 @@
// If the activity belongs to a different app process, we treat it as starting new
// intent, since both actions might result in a new activity that should appear in an
// organized TaskFragment.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
activityIntent, null /* launchingActivity */);
if (targetContainer == null) {
@@ -306,14 +306,14 @@
}
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
activityToken);
- mPresenter.applyTransaction(wct);
// Because the activity does not belong to the organizer process, we wait until
// onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
}
}
@Override
- public void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
+ public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
synchronized (mLock) {
switch (opType) {
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
@@ -329,10 +329,11 @@
}
// Update the latest taskFragmentInfo and perform necessary clean-up
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ mPresenter.cleanupContainer(wct, container,
+ false /* shouldFinishDependent */);
}
break;
}
@@ -343,7 +344,7 @@
}
}
- /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
+ /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
@@ -422,10 +423,12 @@
}
@VisibleForTesting
- void onActivityCreated(@NonNull Activity launchedActivity) {
+ @GuardedBy("mLock")
+ void onActivityCreated(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchedActivity) {
// TODO(b/229680885): we don't support launching into primary yet because we want to always
// launch the new activity on top.
- resolveActivityToContainer(launchedActivity, false /* isOnReparent */);
+ resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */);
updateCallbackIfNecessary();
}
@@ -440,7 +443,8 @@
*/
@VisibleForTesting
@GuardedBy("mLock")
- boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
+ boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, boolean isOnReparent) {
if (isInPictureInPicture(activity) || activity.isFinishing()) {
// We don't embed activity when it is in PIP, or finishing. Return true since we don't
// want any extra handling.
@@ -472,12 +476,12 @@
// 1. Whether the new launched activity should always expand.
if (shouldExpand(activity, null /* intent */)) {
- expandActivity(activity);
+ expandActivity(wct, activity);
return true;
}
// 2. Whether the new launched activity should launch a placeholder.
- if (launchPlaceholderIfNecessary(activity, !isOnReparent)) {
+ if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) {
return true;
}
@@ -492,11 +496,11 @@
// Can't find any activity below.
return false;
}
- if (putActivitiesIntoSplitIfNecessary(activityBelow, activity)) {
+ if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) {
// Have split rule of [ activityBelow | launchedActivity ].
return true;
}
- if (isOnReparent && putActivitiesIntoSplitIfNecessary(activity, activityBelow)) {
+ if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) {
// Have split rule of [ launchedActivity | activityBelow].
return true;
}
@@ -519,19 +523,20 @@
// Can't find the top activity on the other split TaskFragment.
return false;
}
- if (putActivitiesIntoSplitIfNecessary(otherTopActivity, activity)) {
+ if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) {
// Have split rule of [ otherTopActivity | launchedActivity ].
return true;
}
// Have split rule of [ launchedActivity | otherTopActivity].
- return isOnReparent && putActivitiesIntoSplitIfNecessary(activity, otherTopActivity);
+ return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity);
}
/**
* Places the given activity to the top most TaskFragment in the task if there is any.
*/
@VisibleForTesting
- void placeActivityInTopContainer(@NonNull Activity activity) {
+ void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
if (getContainerWithActivity(activity) != null) {
// The activity has already been put in a TaskFragment. This is likely to be done by
// the server when the activity is started.
@@ -547,20 +552,20 @@
return;
}
targetContainer.addPendingAppearedActivity(activity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
activity.getActivityToken());
- mPresenter.applyTransaction(wct);
}
/**
* Starts an activity to side of the launchingActivity with the provided split config.
*/
- private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
+ @GuardedBy("mLock")
+ private void startActivityToSide(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchingActivity, @NonNull Intent intent,
@Nullable Bundle options, @NonNull SplitRule sideRule,
@Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
try {
- mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
+ mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
isPlaceholder);
} catch (Exception e) {
if (failureCallback != null) {
@@ -573,15 +578,17 @@
* Expands the given activity by either expanding the TaskFragment it is currently in or putting
* it into a new expanded TaskFragment.
*/
- private void expandActivity(@NonNull Activity activity) {
+ @GuardedBy("mLock")
+ private void expandActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (shouldContainerBeExpanded(container)) {
// Make sure that the existing container is expanded.
- mPresenter.expandTaskFragment(container.getTaskFragmentToken());
+ mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken());
} else {
// Put activity into a new expanded container.
final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity));
- mPresenter.expandActivity(newContainer.getTaskFragmentToken(), activity);
+ mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
}
}
@@ -667,8 +674,8 @@
* and returns {@code true}. Otherwise, returns {@code false}.
*/
@GuardedBy("mLock")
- private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity) {
+ private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
if (splitRule == null) {
return false;
@@ -686,23 +693,23 @@
return true;
}
secondaryContainer.addPendingAppearedActivity(secondaryActivity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
secondaryActivity, null /* secondaryIntent */)
!= RESULT_EXPAND_FAILED_NO_TF_INFO) {
wct.reparentActivityToTaskFragment(
secondaryContainer.getTaskFragmentToken(),
secondaryActivity.getActivityToken());
- mPresenter.applyTransaction(wct);
return true;
}
}
// Create new split pair.
- mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule);
+ mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
return true;
}
- private void onActivityConfigurationChanged(@NonNull Activity activity) {
+ @GuardedBy("mLock")
+ private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
if (activity.isFinishing()) {
// Do nothing if the activity is currently finishing.
return;
@@ -721,7 +728,7 @@
}
// Check if activity requires a placeholder
- launchPlaceholderIfNecessary(activity, false /* isOnCreated */);
+ launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
}
@VisibleForTesting
@@ -741,7 +748,22 @@
* creation.
*/
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ synchronized (mLock) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ onTaskFragmentAppearEmptyTimeout(wct, container);
+ mPresenter.applyTransaction(wct);
+ }
+ }
+
+ /**
+ * Called when we have been waiting too long for the TaskFragment to become non-empty after
+ * creation.
+ */
+ void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ synchronized (mLock) {
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ }
}
/**
@@ -971,6 +993,7 @@
}
/** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+ @GuardedBy("mLock")
private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
final TaskContainer taskContainer = container.getTaskContainer();
@@ -1084,9 +1107,10 @@
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
+ @GuardedBy("mLock")
void updateContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
- if (launchPlaceholderIfNecessary(container)) {
+ if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
return;
@@ -1111,7 +1135,7 @@
// Skip position update - one or both containers are finished.
return;
}
- if (dismissPlaceholderIfNecessary(splitContainer)) {
+ if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
// Placeholder was finished, the positions will be updated when its container is emptied
return;
}
@@ -1173,16 +1197,20 @@
/**
* Checks if the container requires a placeholder and launches it if necessary.
*/
- private boolean launchPlaceholderIfNecessary(@NonNull TaskFragmentContainer container) {
+ @GuardedBy("mLock")
+ private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
final Activity topActivity = container.getTopNonFinishingActivity();
if (topActivity == null) {
return false;
}
- return launchPlaceholderIfNecessary(topActivity, false /* isOnCreated */);
+ return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
}
- boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) {
+ @GuardedBy("mLock")
+ boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, boolean isOnCreated) {
if (activity.isFinishing()) {
return false;
}
@@ -1216,7 +1244,7 @@
// TODO(b/190433398): Handle failed request
final Bundle options = getPlaceholderOptions(activity, isOnCreated);
- startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), options,
+ startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
return true;
}
@@ -1243,7 +1271,9 @@
}
@VisibleForTesting
- boolean dismissPlaceholderIfNecessary(@NonNull SplitContainer splitContainer) {
+ @GuardedBy("mLock")
+ boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull SplitContainer splitContainer) {
if (!splitContainer.isPlaceholderContainer()) {
return false;
}
@@ -1257,7 +1287,7 @@
return false;
}
- mPresenter.cleanupContainer(splitContainer.getSecondaryContainer(),
+ mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
}
@@ -1523,7 +1553,8 @@
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
- public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityPreCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
@@ -1552,25 +1583,30 @@
}
@Override
- public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityPostCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
// launched to side.
synchronized (mLock) {
- SplitController.this.onActivityCreated(activity);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ SplitController.this.onActivityCreated(wct, activity);
+ mPresenter.applyTransaction(wct);
}
}
@Override
- public void onActivityConfigurationChanged(Activity activity) {
+ public void onActivityConfigurationChanged(@NonNull Activity activity) {
synchronized (mLock) {
- SplitController.this.onActivityConfigurationChanged(activity);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ SplitController.this.onActivityConfigurationChanged(wct, activity);
+ mPresenter.applyTransaction(wct);
}
}
@Override
- public void onActivityPostDestroyed(Activity activity) {
+ public void onActivityPostDestroyed(@NonNull Activity activity) {
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1582,7 +1618,7 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
- public void execute(Runnable r) {
+ public void execute(@NonNull Runnable r) {
mHandler.post(r);
}
}
@@ -1662,7 +1698,7 @@
* If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
* there is any.
*/
- private static boolean canReuseContainer(SplitRule rule1, SplitRule rule2) {
+ private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
return false;
}
@@ -1670,7 +1706,8 @@
}
/** Whether the two rules have the same presentation. */
- private static boolean haveSamePresentation(SplitPairRule rule1, SplitPairRule rule2) {
+ private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+ @NonNull SplitPairRule rule2) {
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
return rule1.getSplitRatio() == rule2.getSplitRatio()
&& rule1.getLayoutDirection() == rule2.getLayoutDirection()
@@ -1684,7 +1721,7 @@
* Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given
* rule.
*/
- private static boolean isContainerReusableRule(SplitRule rule) {
+ private static boolean isContainerReusableRule(@NonNull SplitRule rule) {
// We don't expect to reuse the placeholder rule.
if (!(rule instanceof SplitPairRule)) {
return false;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index a89847a..2b069d7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -102,37 +102,18 @@
private final SplitController mController;
- SplitPresenter(@NonNull Executor executor, SplitController controller) {
+ SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
super(executor, controller);
mController = controller;
registerOrganizer();
}
/**
- * Updates the presentation of the provided container.
- */
- void updateContainer(@NonNull TaskFragmentContainer container) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mController.updateContainer(wct, container);
- applyTransaction(wct);
- }
-
- /**
* Deletes the specified container and all other associated and dependent containers in the same
* transaction.
*/
- void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- cleanupContainer(container, shouldFinishDependent, wct);
- applyTransaction(wct);
- }
-
- /**
- * Deletes the specified container and all other associated and dependent containers in the same
- * transaction.
- */
- void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent,
- @NonNull WindowContainerTransaction wct) {
+ void cleanupContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
container.finish(shouldFinishDependent, this, wct, mController);
final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
@@ -190,10 +171,9 @@
* created and the activity will be re-parented to it.
* @param rule The split rule to be applied to the container.
*/
- void createNewSplitContainer(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
+ void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
+ @NonNull SplitPairRule rule) {
final Rect parentBounds = getParentContainerBounds(primaryActivity);
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
secondaryActivity);
@@ -219,8 +199,6 @@
minDimensionsPair);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
-
- applyTransaction(wct);
}
/**
@@ -262,7 +240,8 @@
* @param rule The split rule to be applied to the container.
* @param isPlaceholder Whether the launch is a placeholder.
*/
- void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
+ void startActivityToSide(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchingActivity, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
final Rect parentBounds = getParentContainerBounds(launchingActivity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
@@ -284,7 +263,6 @@
launchingActivity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(primaryRectBounds);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -294,7 +272,6 @@
// When placeholder is launched in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- applyTransaction(wct);
}
/**
@@ -502,14 +479,14 @@
}
@NonNull
- static Pair<Size, Size> getActivitiesMinDimensionsPair(Activity primaryActivity,
- Activity secondaryActivity) {
+ static Pair<Size, Size> getActivitiesMinDimensionsPair(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
}
@NonNull
- static Pair<Size, Size> getActivityIntentMinDimensionsPair(Activity primaryActivity,
- Intent secondaryIntent) {
+ static Pair<Size, Size> getActivityIntentMinDimensionsPair(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent));
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 0ea5603..77e26c0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -21,8 +21,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -31,6 +29,9 @@
import android.util.ArraySet;
import android.window.TaskFragmentInfo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index f721341..ee2e139 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -30,6 +30,8 @@
import android.view.RemoteAnimationDefinition;
import android.window.TaskFragmentOrganizer;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
/** Controls the TaskFragment remote animations. */
@@ -45,7 +47,7 @@
/** Task Ids that we have registered for remote animation. */
private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
- TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
+ TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
mDefinition = new RemoteAnimationDefinition();
final RemoteAnimationAdapter animationAdapter =
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index c4f3709..8af2d9c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -112,6 +112,7 @@
}
/** Creates the animator given the transition type and windows. */
+ @NonNull
private Animator createAnimator(@WindowManager.TransitionOldType int transit,
@NonNull RemoteAnimationTarget[] targets,
@NonNull IRemoteAnimationFinishedCallback finishedCallback) {
@@ -161,6 +162,7 @@
}
/** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+ @NonNull
private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
@WindowManager.TransitionOldType int transit,
@NonNull RemoteAnimationTarget[] targets) {
@@ -180,12 +182,14 @@
}
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
mAnimationSpec::loadOpenAnimation);
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
@@ -196,6 +200,7 @@
* Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
* @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
*/
+ @NonNull
private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets, boolean isOpening,
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
@@ -238,6 +243,7 @@
return adapters;
}
+ @NonNull
private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull RemoteAnimationTarget target,
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
@@ -259,6 +265,7 @@
return new TaskFragmentAnimationAdapter(animation, target);
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 5cc496a..97d42391b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.provider.Settings;
import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
@@ -68,16 +69,14 @@
// The transition animation should be adjusted based on the developer option.
final ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
new SettingsObserver(handler));
}
/** For target that doesn't need to be animated. */
+ @NonNull
static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
// Noop but just keep the target showing/hiding.
final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
@@ -85,6 +84,7 @@
}
/** Animation for target that is opening in a change transition. */
+ @NonNull
Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
final Rect bounds = target.localBounds;
// The target will be animated in from left or right depends on its position.
@@ -101,6 +101,7 @@
}
/** Animation for target that is closing in a change transition. */
+ @NonNull
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
final Rect bounds = target.localBounds;
// The target will be animated out to left or right depends on its position.
@@ -121,6 +122,7 @@
* @return the return array always has two elements. The first one is for the start leash, and
* the second one is for the end leash.
*/
+ @NonNull
Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
// Both start bounds and end bounds are in screen coordinates. We will post translate
// to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
@@ -177,6 +179,7 @@
return new Animation[]{startSet, endSet};
}
+ @NonNull
Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
@@ -198,6 +201,7 @@
return animation;
}
+ @NonNull
Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
@@ -217,6 +221,12 @@
return animation;
}
+ private float getTransitionAnimationScaleSetting() {
+ return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
private class SettingsObserver extends ContentObserver {
SettingsObserver(@NonNull Handler handler) {
super(handler);
@@ -224,9 +234,7 @@
@Override
public void onChange(boolean selfChange) {
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 37f5b6d..11c0db3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -18,8 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -30,6 +28,9 @@
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -175,6 +176,7 @@
&& mInfo.getActivities().size() == collectNonFinishingActivities().size();
}
+ @NonNull
ActivityStack toActivityStack() {
return new ActivityStack(collectNonFinishingActivities(), isEmpty());
}
@@ -249,19 +251,22 @@
return mInfo;
}
- void setInfo(@NonNull TaskFragmentInfo info) {
+ void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
if (!mIsFinished && mInfo == null && info.isEmpty()) {
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
// pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
// it is still empty after timeout.
- mAppearEmptyTimeout = () -> {
- mAppearEmptyTimeout = null;
- mController.onTaskFragmentAppearEmptyTimeout(this);
- };
if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
+ mAppearEmptyTimeout = () -> {
+ mAppearEmptyTimeout = null;
+ // Call without the pass-in wct when timeout. We need to applyWct directly
+ // in this case.
+ mController.onTaskFragmentAppearEmptyTimeout(this);
+ };
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
} else {
- mAppearEmptyTimeout.run();
+ mAppearEmptyTimeout = null;
+ mController.onTaskFragmentAppearEmptyTimeout(wct, this);
}
} else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 4d25952..21cf7a6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -56,6 +56,8 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:JetpackTaskFragmentOrganizerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -119,7 +121,7 @@
new Intent(), taskContainer, mSplitController);
final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 4bc5033..07758d2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -89,6 +89,8 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:SplitControllerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -158,14 +160,14 @@
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(true).when(info).isEmpty();
- tf1.setInfo(info);
+ tf1.setInfo(mTransaction, info);
assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
+ " creation.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
doReturn(false).when(info).isEmpty();
- tf1.setInfo(info);
+ tf1.setInfo(mTransaction, info);
assertWithMessage("Must return null because tf1 becomes empty.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
@@ -177,7 +179,7 @@
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
// The TaskFragment has been removed in the server, we only need to cleanup the reference.
- mSplitController.onTaskFragmentVanished(mInfo);
+ mSplitController.onTaskFragmentVanished(mTransaction, mInfo);
verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
verify(mSplitController).removeContainer(tf);
@@ -187,9 +189,10 @@
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
- mSplitController.onTaskFragmentAppearEmptyTimeout(tf);
+ mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
- verify(mSplitPresenter).cleanupContainer(tf, false /* shouldFinishDependent */);
+ verify(mSplitPresenter).cleanupContainer(mTransaction, tf,
+ false /* shouldFinishDependent */);
}
@Test
@@ -229,8 +232,8 @@
spyOn(tf);
doReturn(mActivity).when(tf).getTopNonFinishingActivity();
doReturn(true).when(tf).isEmpty();
- doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mActivity,
- false /* isOnCreated */);
+ doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction,
+ mActivity, false /* isOnCreated */);
doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any());
mSplitController.updateContainer(mTransaction, tf);
@@ -250,7 +253,7 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if tf is not in the top splitContainer,
final SplitContainer splitContainer = mock(SplitContainer.class);
@@ -264,7 +267,7 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if one or both containers in the top SplitContainer are finished,
// dismissPlaceholder() won't be called.
@@ -273,12 +276,12 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if placeholder should be dismissed, updateSplitContainer() won't be called.
doReturn(false).when(tf).isFinished();
doReturn(true).when(mSplitController)
- .dismissPlaceholderIfNecessary(splitContainer);
+ .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf);
@@ -286,7 +289,7 @@
// Verify if the top active split is updated if both of its containers are not finished.
doReturn(false).when(mSplitController)
- .dismissPlaceholderIfNecessary(splitContainer);
+ .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf);
@@ -315,34 +318,36 @@
@Test
public void testOnActivityCreated() {
- mSplitController.onActivityCreated(mActivity);
+ mSplitController.onActivityCreated(mTransaction, mActivity);
// Disallow to split as primary because we want the new launch to be always on top.
- verify(mSplitController).resolveActivityToContainer(mActivity, false /* isOnReparent */);
+ verify(mSplitController).resolveActivityToContainer(mTransaction, mActivity,
+ false /* isOnReparent */);
}
@Test
- public void testOnActivityReparentToTask_sameProcess() {
- mSplitController.onActivityReparentToTask(TASK_ID, new Intent(),
+ public void testOnActivityReparentedToTask_sameProcess() {
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(),
mActivity.getActivityToken());
// Treated as on activity created, but allow to split as primary.
- verify(mSplitController).resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ verify(mSplitController).resolveActivityToContainer(mTransaction,
+ mActivity, true /* isOnReparent */);
// Try to place the activity to the top TaskFragment when there is no matched rule.
- verify(mSplitController).placeActivityInTopContainer(mActivity);
+ verify(mSplitController).placeActivityInTopContainer(mTransaction, mActivity);
}
@Test
- public void testOnActivityReparentToTask_diffProcess() {
+ public void testOnActivityReparentedToTask_diffProcess() {
// Create an empty TaskFragment to initialize for the Task.
mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
final IBinder activityToken = new Binder();
final Intent intent = new Intent();
- mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken);
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken);
// Treated as starting new intent
- verify(mSplitController, never()).resolveActivityToContainer(any(), anyBoolean());
+ verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean());
verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent),
isNull());
}
@@ -504,26 +509,29 @@
@Test
public void testPlaceActivityInTopContainer() {
- mSplitController.placeActivityInTopContainer(mActivity);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter, never()).applyTransaction(any());
+ verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
- mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
- mSplitController.placeActivityInTopContainer(mActivity);
+ // Place in the top container if there is no other rule matched.
+ final TaskFragmentContainer topContainer = mSplitController
+ .newContainer(new Intent(), mActivity, TASK_ID);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter).applyTransaction(any());
+ verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(),
+ mActivity.getActivityToken());
// Not reparent if activity is in a TaskFragment.
- clearInvocations(mSplitPresenter);
+ clearInvocations(mTransaction);
mSplitController.newContainer(mActivity, TASK_ID);
- mSplitController.placeActivityInTopContainer(mActivity);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter, never()).applyTransaction(any());
+ verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
}
@Test
public void testResolveActivityToContainer_noRuleMatched() {
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
@@ -535,7 +543,7 @@
setupExpandRule(mActivity);
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -543,7 +551,8 @@
assertTrue(result);
assertNotNull(container);
verify(mSplitController).newContainer(mActivity, TASK_ID);
- verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+ verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+ mActivity);
}
@Test
@@ -552,11 +561,11 @@
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).expandTaskFragment(container.getTaskFragmentToken());
+ verify(mSplitPresenter).expandTaskFragment(mTransaction, container.getTaskFragmentToken());
}
@Test
@@ -566,14 +575,15 @@
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
final Activity activity = createMockActivity();
addSplitTaskFragments(activity, mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
assertTrue(result);
assertNotNull(container);
- verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+ verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+ mActivity);
}
@Test
@@ -583,11 +593,11 @@
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
// Launch placeholder if the activity is not in any TaskFragment.
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -600,11 +610,11 @@
final Activity activity = createMockActivity();
mSplitController.newContainer(mActivity, TASK_ID);
mSplitController.newContainer(activity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+ verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean());
}
@@ -616,11 +626,11 @@
// Launch placeholder if the activity is in the topmost expanded TaskFragment.
mSplitController.newContainer(mActivity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -632,11 +642,11 @@
// Don't launch placeholder if the activity is in primary split.
final Activity secondaryActivity = createMockActivity();
addSplitTaskFragments(mActivity, secondaryActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+ verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean());
}
@@ -649,11 +659,11 @@
// Launch placeholder if the activity is in secondary split.
final Activity primaryActivity = createMockActivity();
addSplitTaskFragments(primaryActivity, mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -676,7 +686,7 @@
secondaryContainer,
splitRule);
clearInvocations(mSplitController);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -705,7 +715,7 @@
final Activity launchedActivity = createMockActivity();
primaryContainer.addPendingAppearedActivity(launchedActivity);
- assertFalse(mSplitController.resolveActivityToContainer(launchedActivity,
+ assertFalse(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity,
false /* isOnReparent */));
}
@@ -717,7 +727,7 @@
// Activity is already in secondary split, no need to create new split.
addSplitTaskFragments(primaryActivity, mActivity);
clearInvocations(mSplitController);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -735,7 +745,7 @@
addSplitTaskFragments(primaryActivity, secondaryActivity);
mSplitController.getContainerWithActivity(secondaryActivity)
.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
@@ -760,7 +770,7 @@
mActivity,
secondaryContainer,
placeholderRule);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -774,7 +784,7 @@
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -790,14 +800,15 @@
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
assertEquals(container, mSplitController.getContainerWithActivity(mActivity));
// Allow to split as primary.
- result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, activityBelow);
@@ -815,7 +826,7 @@
final TaskFragmentContainer secondaryContainer = mSplitController.getContainerWithActivity(
activityBelow);
secondaryContainer.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -836,14 +847,15 @@
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
primaryActivity);
primaryContainer.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity));
- result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, primaryActivity);
@@ -861,7 +873,7 @@
container.addPendingAppearedActivity(mActivity);
// Allow to split as primary.
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
true /* isOnReparent */);
assertTrue(result);
@@ -879,15 +891,13 @@
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
}
- // Suppress GuardedBy warning on unit tests
- @SuppressWarnings("GuardedBy")
@Test
public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
final Activity primaryActivity = createMockActivity();
@@ -899,14 +909,14 @@
doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity));
clearInvocations(mSplitPresenter);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
mSplitController.getContainerWithActivity(mActivity));
- verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any());
+ verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any());
}
@Test
@@ -914,7 +924,7 @@
doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
// No need to handle when the new launched activity is in an unknown TaskFragment.
- assertTrue(mSplitController.resolveActivityToContainer(mActivity,
+ assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */));
}
@@ -993,7 +1003,7 @@
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity) {
final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index d7931966..3fdf8e5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -78,6 +78,8 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:SplitPresenterTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -226,8 +228,9 @@
mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */));
- primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity));
- secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
+ primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
+ secondaryTf.setInfo(mTransaction,
+ createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 44c7e6c..6cbecff 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -19,6 +19,8 @@
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -36,7 +38,6 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
@@ -62,25 +63,27 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:TaskFragmentContainerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskFragmentContainerTest {
@Mock
private SplitPresenter mPresenter;
- @Mock
private SplitController mController;
@Mock
private TaskFragmentInfo mInfo;
@Mock
- private Handler mHandler;
+ private WindowContainerTransaction mTransaction;
private Activity mActivity;
private Intent mIntent;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- doReturn(mHandler).when(mController).getHandler();
+ mController = new SplitController();
+ spyOn(mController);
mActivity = createMockActivity();
mIntent = new Intent();
}
@@ -123,7 +126,7 @@
// Remove all references after the container has appeared in server.
doReturn(new ArrayList<>()).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
verify(mActivity, never()).finish();
@@ -137,7 +140,7 @@
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
- container0.setInfo(info);
+ container0.setInfo(mTransaction, info);
// Request to reparent the activity to a new TaskFragment.
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
@@ -163,7 +166,7 @@
final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
mActivity);
- pendingActivityContainer.setInfo(info0);
+ pendingActivityContainer.setInfo(mTransaction, info0);
assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty());
@@ -175,7 +178,7 @@
final TaskFragmentInfo info1 = createMockTaskFragmentInfo(pendingIntentContainer,
mActivity);
- pendingIntentContainer.setInfo(info1);
+ pendingIntentContainer.setInfo(mTransaction, info1);
assertNull(pendingIntentContainer.getPendingAppearedIntent());
}
@@ -191,18 +194,19 @@
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertTrue(container.isWaitingActivityAppear());
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertFalse(container.isWaitingActivityAppear());
}
@Test
public void testAppearEmptyTimeout() {
+ doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
@@ -213,20 +217,20 @@
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
container.mInfo = null;
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNotNull(container.mAppearEmptyTimeout);
// Not set if it is not appeared empty.
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNull(container.mAppearEmptyTimeout);
// Remove timeout after the container becomes non-empty.
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNull(container.mAppearEmptyTimeout);
@@ -234,7 +238,7 @@
container.mInfo = null;
container.setPendingAppearedIntent(mIntent);
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
container.mAppearEmptyTimeout.run();
assertNull(container.mAppearEmptyTimeout);
@@ -260,7 +264,7 @@
final List<IBinder> runningActivities = Lists.newArrayList(activity0.getActivityToken(),
activity1.getActivityToken());
doReturn(runningActivities).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
activities = container.collectNonFinishingActivities();
assertEquals(3, activities.size());
@@ -295,7 +299,7 @@
final Activity activity = createMockActivity();
final List<IBinder> runningActivities = Lists.newArrayList(activity.getActivityToken());
doReturn(runningActivities).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
assertEquals(activity, container.getBottomMostActivity());
}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 918e514..e9a1721 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 8533a59..b0dab90 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,92 +16,98 @@
-->
<!-- Layout for TvPipMenuView -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tv_pip_menu"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center|top">
+ android:id="@+id/tv_pip_menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center|top">
<!-- Matches the PiP app content -->
- <View
+ <FrameLayout
android:id="@+id/tv_pip"
android:layout_width="0dp"
android:layout_height="0dp"
- android:alpha="0"
- android:background="@color/tv_pip_menu_background"
android:layout_marginTop="@dimen/pip_menu_outer_space"
android:layout_marginStart="@dimen/pip_menu_outer_space"
- android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
+ android:layout_marginEnd="@dimen/pip_menu_outer_space">
- <ScrollView
- android:id="@+id/tv_pip_menu_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignTop="@+id/tv_pip"
- android:layout_alignStart="@+id/tv_pip"
- android:layout_alignEnd="@+id/tv_pip"
- android:layout_alignBottom="@+id/tv_pip"
- android:scrollbars="none"
- android:visibility="gone"/>
+ <View
+ android:id="@+id/tv_pip_menu_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tv_pip_menu_background"
+ android:alpha="0"/>
- <HorizontalScrollView
- android:id="@+id/tv_pip_menu_horizontal_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignTop="@+id/tv_pip"
- android:layout_alignStart="@+id/tv_pip"
- android:layout_alignEnd="@+id/tv_pip"
- android:layout_alignBottom="@+id/tv_pip"
- android:scrollbars="none">
+ <View
+ android:id="@+id/tv_pip_menu_dim_layer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/tv_pip_menu_dim_layer"
+ android:alpha="0"/>
- <LinearLayout
- android:id="@+id/tv_pip_menu_action_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:alpha="0">
+ <ScrollView
+ android:id="@+id/tv_pip_menu_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="none"
+ android:visibility="gone"/>
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+ <HorizontalScrollView
+ android:id="@+id/tv_pip_menu_horizontal_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="none">
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_fullscreen_button"
+ <LinearLayout
+ android:id="@+id/tv_pip_menu_action_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_fullscreen_white"
- android:text="@string/pip_fullscreen" />
+ android:orientation="horizontal"
+ android:alpha="0">
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_close_white"
- android:text="@string/pip_close" />
+ <Space
+ android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+ android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
- <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_fullscreen_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_fullscreen_white"
+ android:text="@string/pip_fullscreen" />
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_move_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_move_white"
- android:text="@string/pip_move" />
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_close_white"
+ android:text="@string/pip_close" />
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_collapse"
- android:visibility="gone"
- android:text="@string/pip_collapse" />
+ <!-- More TvPipMenuActionButtons may be added here at runtime. -->
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_move_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_move_white"
+ android:text="@string/pip_move" />
- </LinearLayout>
- </HorizontalScrollView>
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ android:id="@+id/tv_pip_menu_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/pip_ic_collapse"
+ android:visibility="gone"
+ android:text="@string/pip_collapse" />
+ <Space
+ android:layout_width="@dimen/pip_menu_button_wrapper_margin"
+ android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
+
+ </LinearLayout>
+ </HorizontalScrollView>
+ </FrameLayout>
+
+ <!-- Frame around the content, just overlapping the corners to make them round -->
<View
android:id="@+id/tv_pip_border"
android:layout_width="0dp"
@@ -111,6 +117,7 @@
android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
+ <!-- Temporarily extending the background to show an edu text hint for opening the menu -->
<FrameLayout
android:id="@+id/tv_pip_menu_edu_text_container"
android:layout_width="match_parent"
@@ -138,6 +145,7 @@
android:textAppearance="@style/TvPipEduText"/>
</FrameLayout>
+ <!-- Frame around the PiP content + edu text hint - used to highlight open menu -->
<View
android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent"
@@ -145,7 +153,8 @@
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
- <com.android.wm.shell.common.TvWindowMenuActionButton
+ <!-- Move menu -->
+ <com.android.wm.shell.pip.tv.TvPipMenuActionButton
android:id="@+id/tv_pip_menu_done_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 3e71c10..e6933ca 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -25,6 +25,7 @@
<color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color>
<color name="tv_pip_menu_focus_border">#E8EAED</color>
+ <color name="tv_pip_menu_dim_layer">#990E0E0F</color>
<color name="tv_pip_menu_background">#1E232C</color>
<color name="tv_pip_edu_text">#99D2E3FC</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 764e650..b085b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -16,14 +16,20 @@
package com.android.wm.shell;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.app.WindowConfiguration;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
@@ -102,10 +108,44 @@
mDisplayAreasInfo.put(displayId, displayAreaInfo);
}
+ /**
+ * Create a {@link WindowContainerTransaction} to update display windowing mode.
+ *
+ * @param displayId display id to update windowing mode for
+ * @param windowingMode target {@link WindowConfiguration.WindowingMode}
+ * @return {@link WindowContainerTransaction} with pending operation to set windowing mode
+ */
+ public WindowContainerTransaction prepareWindowingModeChange(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+ if (displayAreaInfo == null) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE,
+ "unable to update windowing mode for display %d display not found", displayId);
+ return wct;
+ }
+
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
+ displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
+ windowingMode);
+
+ wct.setWindowingMode(displayAreaInfo.token, windowingMode);
+ return wct;
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
+
+ for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
+ int displayId = mDisplayAreasInfo.keyAt(i);
+ DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+ int windowingMode =
+ displayAreaInfo.configuration.windowConfiguration.getWindowingMode();
+ pw.println(innerPrefix + "# displayId=" + displayId + " wmMode=" + windowingMode);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 6ae0f9b..d5d4935 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -46,6 +47,7 @@
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
+import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -690,6 +692,49 @@
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
+ /**
+ * Create a {@link WindowContainerTransaction} to clear task bounds.
+ *
+ * @param displayId display id for tasks that will have bounds cleared
+ * @return {@link WindowContainerTransaction} with pending operations to clear bounds
+ */
+ public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ /**
+ * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
+ *
+ * @param displayId display id for tasks that will have windowing mode reset to {@link
+ * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
+ * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
+ */
+ public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ return wct;
+ }
+
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -816,7 +861,14 @@
final int key = mTasks.keyAt(i);
final TaskAppearedInfo info = mTasks.valueAt(i);
final TaskListener listener = getTaskListener(info.getTaskInfo());
- pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener);
+ final int windowingMode = info.getTaskInfo().getWindowingMode();
+ String pkg = "";
+ if (info.getTaskInfo().baseActivity != null) {
+ pkg = info.getTaskInfo().baseActivity.getPackageName();
+ }
+ Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds();
+ pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener
+ + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds);
}
pw.println();
@@ -826,6 +878,7 @@
final TaskListener listener = mLaunchCookieToListener.valueAt(i);
pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
}
+
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index d5875c0..e270edb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -221,8 +221,7 @@
}
final Display display = mDisplayController.getDisplay(mDisplayId);
SurfaceControlViewHost viewRoot =
- new SurfaceControlViewHost(
- view.getContext(), display, wwm, true /* useSfChoreographer */);
+ new SurfaceControlViewHost(view.getContext(), display, wwm);
attrs.flags |= FLAG_HARDWARE_ACCELERATED;
viewRoot.setView(view, attrs);
mViewRoots.put(view, viewRoot);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 85c8ebf..83ba909 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -80,7 +80,10 @@
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
- private static final int FLING_ANIMATION_DURATION = 250;
+ private static final int FLING_RESIZE_DURATION = 250;
+ private static final int FLING_SWITCH_DURATION = 350;
+ private static final int FLING_ENTER_DURATION = 350;
+ private static final int FLING_EXIT_DURATION = 350;
private final int mDividerWindowWidth;
private final int mDividerInsets;
@@ -93,6 +96,9 @@
private final Rect mBounds1 = new Rect();
// Bounds2 final position should be always at bottom or right
private final Rect mBounds2 = new Rect();
+ // The temp bounds outside of display bounds for side stage when split screen inactive to avoid
+ // flicker next time active split screen.
+ private final Rect mInvisibleBounds = new Rect();
private final Rect mWinBounds1 = new Rect();
private final Rect mWinBounds2 = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
@@ -141,6 +147,10 @@
resetDividerPosition();
mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+
+ mInvisibleBounds.set(mRootBounds);
+ mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
+ isLandscape() ? 0 : mRootBounds.bottom);
}
private int getDividerInsets(Resources resources, Display display) {
@@ -239,6 +249,12 @@
rect.offset(-mRootBounds.left, -mRootBounds.top);
}
+ /** Gets bounds size equal to root bounds but outside of screen, used for position side stage
+ * when split inactive to avoid flicker when next time active. */
+ public void getInvisibleBounds(Rect rect) {
+ rect.set(mInvisibleBounds);
+ }
+
/** Returns leash of the current divider bar. */
@Nullable
public SurfaceControl getDividerLeash() {
@@ -284,6 +300,10 @@
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
initDividerPosition(mTempRect);
+ mInvisibleBounds.set(mRootBounds);
+ mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
+ isLandscape() ? 0 : mRootBounds.bottom);
+
return true;
}
@@ -405,6 +425,13 @@
mFreezeDividerWindow = freezeDividerWindow;
}
+ /** Update current layout as divider put on start or end position. */
+ public void setDividerAtBorder(boolean start) {
+ final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
+ : mDividerSnapAlgorithm.getDismissEndTarget().position;
+ setDividePosition(pos, false /* applyLayoutChange */);
+ }
+
/**
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
@@ -449,17 +476,17 @@
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
switch (snapTarget.flag) {
case FLAG_DISMISS_START:
- flingDividePosition(currentPosition, snapTarget.position,
+ flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
case FLAG_DISMISS_END:
- flingDividePosition(currentPosition, snapTarget.position,
+ flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
default:
- flingDividePosition(currentPosition, snapTarget.position,
+ flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
() -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
@@ -516,12 +543,20 @@
public void flingDividerToDismiss(boolean toEnd, int reason) {
final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
: mDividerSnapAlgorithm.getDismissStartTarget().position;
- flingDividePosition(getDividePosition(), target,
+ flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
() -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
}
+ /** Fling divider from current position to center position. */
+ public void flingDividerToCenter() {
+ final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
+ flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
+ () -> setDividePosition(pos, true /* applyLayoutChange */));
+ }
+
@VisibleForTesting
- void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
+ void flingDividePosition(int from, int to, int duration,
+ @Nullable Runnable flingFinishedCallback) {
if (from == to) {
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
@@ -531,7 +566,7 @@
}
ValueAnimator animator = ValueAnimator
.ofInt(from, to)
- .setDuration(FLING_ANIMATION_DURATION);
+ .setDuration(duration);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
@@ -588,7 +623,7 @@
AnimatorSet set = new AnimatorSet();
set.playTogether(animator1, animator2, animator3);
- set.setDuration(FLING_ANIMATION_DURATION);
+ set.setDuration(FLING_SWITCH_DURATION);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 35a309a..0cc545a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -20,7 +20,6 @@
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
-import android.animation.AnimationHandler;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -31,11 +30,9 @@
import androidx.annotation.Nullable;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -195,30 +192,6 @@
}
/**
- * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
- * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
- * the Shell main-thread with the SF vsync.
- */
- @WMSingleton
- @Provides
- @ChoreographerSfVsync
- public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
- @ShellMainThread ShellExecutor mainExecutor) {
- try {
- AnimationHandler handler = new AnimationHandler();
- mainExecutor.executeBlocking(() -> {
- // This is called on the animation thread since it calls
- // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
- // that uses the SF vsync
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- });
- return handler;
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
- }
- }
-
- /**
* Provides a Shell background thread Handler for low priority background tasks.
*/
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2ca9c3b..2bcc134 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,6 +27,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -48,6 +49,8 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
+import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -574,6 +577,27 @@
}
//
+ // Desktop mode (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopModeController> provideDesktopModeController(
+ Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ @ShellMainThread Handler mainHandler
+ ) {
+ if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+ return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ rootDisplayAreaOrganizer,
+ mainHandler));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ //
// Misc
//
@@ -583,7 +607,8 @@
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
- SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
+ SplitscreenPipMixedHandler splitscreenPipMixedHandler,
+ Optional<DesktopModeController> desktopModeController) {
return new Object();
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
similarity index 61%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
index 8dde897..e62a63a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
@@ -14,10 +14,18 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.wm.shell.desktopmode;
-import android.content.pm.ApplicationInfo
+import android.os.SystemProperties;
-interface AppRecord {
- val app: ApplicationInfo
+/**
+ * Constants for desktop mode feature
+ */
+public class DesktopModeConstants {
+
+ /**
+ * Flag to indicate whether desktop mode is available on the device
+ */
+ public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
new file mode 100644
index 0000000..5849e16
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Handles windowing changes when desktop mode system setting changes
+ */
+public class DesktopModeController {
+
+ private final Context mContext;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ private final SettingsObserver mSettingsObserver;
+
+ public DesktopModeController(Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ @ShellMainThread Handler mainHandler) {
+ mContext = context;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+ mSettingsObserver = new SettingsObserver(mContext, mainHandler);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mSettingsObserver.observe();
+ }
+
+ @VisibleForTesting
+ void updateDesktopModeEnabled(boolean enabled) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeState: enabled=%s", enabled);
+
+ int displayId = mContext.getDisplayId();
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Reset freeform windowing mode that is set per task level (tasks should inherit
+ // container value)
+ wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
+ int targetWindowingMode;
+ if (enabled) {
+ targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ } else {
+ targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ // Clear any resized bounds
+ wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
+ true /* transfer */);
+ }
+ wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
+ targetWindowingMode), true /* transfer */);
+ mRootDisplayAreaOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
+ */
+ private final class SettingsObserver extends ContentObserver {
+
+ private final Uri mDesktopModeSetting = Settings.System.getUriFor(
+ Settings.System.DESKTOP_MODE);
+
+ private final Context mContext;
+
+ SettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mContext = context;
+ }
+
+ public void observe() {
+ // TODO(b/242867463): listen for setting change for all users
+ mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
+ false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ if (mDesktopModeSetting.equals(uri)) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
+ desktopModeSettingChanged();
+ }
+ }
+
+ private void desktopModeSettingChanged() {
+ boolean enabled = isDesktopModeEnabled();
+ updateDesktopModeEnabled(enabled);
+ }
+
+ private boolean isDesktopModeEnabled() {
+ try {
+ int result = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
+ return result != 0;
+ } catch (Settings.SettingNotFoundException e) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
+ return false;
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 504dc02..a0a8f9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -31,8 +31,6 @@
import android.util.Size;
import android.view.MotionEvent;
import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowManagerGlobal;
import com.android.internal.protolog.common.ProtoLog;
@@ -42,6 +40,7 @@
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -115,6 +114,10 @@
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
+ private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ mSurfaceControlTransactionFactory;
+ private final float[] mTmpTransform = new float[9];
+
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
private final Optional<SplitScreenController> mSplitScreenController;
@@ -124,7 +127,6 @@
private RemoteAction mCloseAction;
private List<RemoteAction> mMediaActions;
- private SyncRtSurfaceTransactionApplier mApplier;
private int mMenuState;
private PipMenuView mPipMenuView;
@@ -150,6 +152,9 @@
mMainHandler = mainHandler;
mSplitScreenController = splitScreenOptional;
mPipUiEventLogger = pipUiEventLogger;
+
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
}
public boolean isMenuVisible() {
@@ -194,7 +199,6 @@
return;
}
- mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
}
@@ -289,7 +293,7 @@
willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, " "));
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
@@ -312,7 +316,7 @@
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
@@ -328,18 +332,15 @@
mTmpSourceRectF.set(mTmpSourceBounds);
mTmpDestinationRectF.set(destinationBounds);
mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
- SurfaceControl surfaceControl = getSurfaceControl();
- SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
- .withMatrix(mMoveTransform)
- .build();
+ final SurfaceControl surfaceControl = getSurfaceControl();
+ final SurfaceControl.Transaction menuTx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
if (pipLeash != null && t != null) {
- SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(t)
- .build();
- mApplier.scheduleApply(params, pipParams);
- } else {
- mApplier.scheduleApply(params);
+ // Merge the two transactions, vsyncId has been set on menuTx.
+ menuTx.merge(t);
}
+ menuTx.apply();
}
/**
@@ -353,36 +354,29 @@
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
- SurfaceControl surfaceControl = getSurfaceControl();
- SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
- .withWindowCrop(destinationBounds)
- .build();
+ final SurfaceControl surfaceControl = getSurfaceControl();
+ final SurfaceControl.Transaction menuTx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ menuTx.setCrop(surfaceControl, destinationBounds);
if (pipLeash != null && t != null) {
- SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(t)
- .build();
- mApplier.scheduleApply(params, pipParams);
- } else {
- mApplier.scheduleApply(params);
+ // Merge the two transactions, vsyncId has been set on menuTx.
+ menuTx.merge(t);
}
+ menuTx.apply();
}
- private boolean maybeCreateSyncApplier() {
+ private boolean checkPipMenuState() {
if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Not going to move PiP, either menu or its parent is not created.", TAG);
return false;
}
- if (mApplier == null) {
- mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
- }
-
- return mApplier != null;
+ return true;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 0f3ff36..8e3376f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -146,11 +146,8 @@
"%s: Failed to create input consumer, %s", TAG, e);
}
mMainExecutor.execute(() -> {
- // Choreographer.getSfInstance() must be called on the thread that the input event
- // receiver should be receiving events
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
mInputEventReceiver = new InputEventReceiver(inputChannel,
- Looper.myLooper(), Choreographer.getSfInstance());
+ Looper.myLooper(), Choreographer.getInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index abf1a95..89d85e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -625,8 +625,7 @@
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
- super(channel, looper, Choreographer.getSfInstance());
+ super(channel, looper, Choreographer.getInstance());
}
public void onInputEvent(InputEvent event) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 4d7c846..97e017a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -97,6 +97,9 @@
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
+ private final View mPipBackground;
+ private final View mDimLayer;
+
private final ScrollView mScrollView;
private final HorizontalScrollView mHorizontalScrollView;
private View mFocusedButton;
@@ -148,6 +151,9 @@
mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
mExpandButton.setOnClickListener(this);
+ mPipBackground = findViewById(R.id.tv_pip_menu_background);
+ mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
mScrollView = findViewById(R.id.tv_pip_menu_scroll);
mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
@@ -231,7 +237,7 @@
mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
finishBounds.width() / (float) finishBounds.height());
if (ratioChanged) {
- mPipView.animate()
+ mPipBackground.animate()
.alpha(1f)
.setInterpolator(TvPipInterpolators.EXIT)
.setDuration(mResizeAnimationDuration / 2)
@@ -272,7 +278,7 @@
"%s: onPipTransitionFinished()", TAG);
// Fade in content by fading out view on top.
- mPipView.animate()
+ mPipBackground.animate()
.alpha(0f)
.setDuration(mResizeAnimationDuration / 2)
.setInterpolator(TvPipInterpolators.ENTER)
@@ -770,6 +776,7 @@
refocusPreviousButton();
}
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
+ animateAlphaTo(show ? 1 : 0, mDimLayer);
}
private void setFrameHighlighted(boolean highlighted) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index b296151..93c7529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -46,6 +46,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7e83d2f..21fc01e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -25,6 +25,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -488,13 +489,6 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
- // If split still not active, apply windows bounds first to avoid surface reset to
- // wrong pos by SurfaceAnimator from wms.
- // TODO(b/223325631): check is it still necessary after improve enter transition done.
- if (!mMainStage.isActive()) {
- updateWindowBounds(mSplitLayout, wct);
- }
-
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -641,7 +635,7 @@
wct.startTask(sideTaskId, sideOptions);
}
// Using legacy transitions, so we can't use blast sync since it conflicts.
- mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
@@ -893,10 +887,13 @@
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = false;
+ mSplitLayout.getInvisibleBounds(mTempRect1);
if (childrenToTop == null) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
+ wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
// Expand to top side split as full screen for fading out decor animation and dismiss
@@ -907,27 +904,32 @@
? mSideStage : mMainStage;
tempFullStage.resetBounds(wct);
wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token,
- mRootTaskInfo.configuration.smallestScreenWidthDp);
+ SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
dismissStage.dismiss(wct, false /* toTop */);
}
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
t.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null);
- t.setPosition(mMainStage.mRootLeash, 0, 0)
- .setPosition(mSideStage.mRootLeash, 0, 0);
t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer);
setDividerVisibility(false, t);
- // In this case, exit still under progress, fade out the split decor after first WCT
- // done and do remaining WCT after animation finished.
- if (childrenToTop != null) {
+ if (childrenToTop == null) {
+ t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
+ } else {
+ // In this case, exit still under progress, fade out the split decor after first WCT
+ // done and do remaining WCT after animation finished.
childrenToTop.fadeOutDecor(() -> {
WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
mIsExiting = false;
childrenToTop.dismiss(finishedWCT, true /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- mTaskOrganizer.applyTransaction(finishedWCT);
+ finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
+ finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+ mSyncQueue.queue(finishedWCT);
+ mSyncQueue.runInSync(at -> {
+ at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
+ });
onTransitionAnimationComplete();
});
}
@@ -996,6 +998,7 @@
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -1221,7 +1224,13 @@
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- mTaskOrganizer.applyTransaction(wct);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
+ mSplitLayout.getInvisibleBounds(mTempRect1);
+ wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
+ });
}
private void onRootTaskVanished() {
@@ -1377,10 +1386,17 @@
// TODO (b/238697912) : Add the validation to prevent entering non-recovered status
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
- prepareEnterSplitScreen(wct);
+ mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ mMainStage.activate(wct, true /* includingTopTask */);
+ updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t ->
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+
+ mSplitLayout.flingDividerToCenter();
+ });
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
@@ -1822,6 +1838,7 @@
// properly for the animation itself.
mSplitLayout.release();
mSplitLayout.resetDividerPosition();
+ mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 45b69f1..6388ca1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -86,8 +86,6 @@
private final float[] mTmpFloats = new float[9];
/** The leash of the changing window container. */
private final SurfaceControl mSurfaceControl;
- private final Rect mStartBounds = new Rect();
- private final Rect mEndBounds = new Rect();
private final int mAnimHint;
private final int mStartWidth;
@@ -105,8 +103,7 @@
*/
private SurfaceControl mBackColorSurface;
/** The leash using to animate screenshot layer. */
- private SurfaceControl mAnimLeash;
- private Transaction mTransaction;
+ private final SurfaceControl mAnimLeash;
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
@@ -134,9 +131,6 @@
mStartRotation = change.getStartRotation();
mEndRotation = change.getEndRotation();
- mStartBounds.set(change.getStartAbsBounds());
- mEndBounds.set(change.getEndAbsBounds());
-
mAnimLeash = new SurfaceControl.Builder(session)
.setParent(rootLeash)
.setEffectLayer()
@@ -169,6 +163,8 @@
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.show(mAnimLeash);
+ // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
+ t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
@@ -306,7 +302,6 @@
mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
- mTransaction = mTransactionPool.acquire();
if (customRotate) {
mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
@@ -386,22 +381,16 @@
}
public void kill() {
- Transaction t = mTransaction != null ? mTransaction : mTransactionPool.acquire();
+ final Transaction t = mTransactionPool.acquire();
if (mAnimLeash.isValid()) {
t.remove(mAnimLeash);
}
- if (mScreenshotLayer != null) {
- if (mScreenshotLayer.isValid()) {
- t.remove(mScreenshotLayer);
- }
- mScreenshotLayer = null;
+ if (mScreenshotLayer != null && mScreenshotLayer.isValid()) {
+ t.remove(mScreenshotLayer);
}
- if (mBackColorSurface != null) {
- if (mBackColorSurface.isValid()) {
- t.remove(mBackColorSurface);
- }
- mBackColorSurface = null;
+ if (mBackColorSurface != null && mBackColorSurface.isValid()) {
+ t.remove(mBackColorSurface);
}
t.apply();
mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 9335438..26d0ec6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -167,10 +168,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
resolver.registerContentObserver(
@@ -185,6 +183,12 @@
}
}
+ private float getTransitionAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
public ShellTransitions asRemoteTransitions() {
return mImpl;
}
@@ -963,9 +967,7 @@
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 98b5ee9..dc3deb1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -34,6 +34,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -163,7 +164,13 @@
View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
View maximize = caption.findViewById(R.id.maximize_window);
- maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+ // Hide maximize button when desktop mode is available
+ maximize.setVisibility(View.GONE);
+ } else {
+ maximize.setVisibility(View.VISIBLE);
+ maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ }
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
View minimize = caption.findViewById(R.id.minimize_window);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 506a4c0..5e64a06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -248,7 +248,7 @@
lp.setTrustedOverlay();
if (mViewHost == null) {
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
- mCaptionWindowManager, true);
+ mCaptionWindowManager);
mViewHost.setView(outResult.mRootView, lp);
} else {
mViewHost.relayout(lp);
@@ -345,9 +345,8 @@
}
interface SurfaceControlViewHostFactory {
- default SurfaceControlViewHost create(
- Context c, Display d, WindowlessWindowManager wmm, boolean useSfChoreographer) {
- return new SurfaceControlViewHost(c, d, wmm, useSfChoreographer);
+ default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
+ return new SurfaceControlViewHost(c, d, wmm);
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f865649..b29c436 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,9 +16,11 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -30,6 +32,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -38,9 +42,11 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
+import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -53,6 +59,8 @@
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -628,6 +636,71 @@
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
+ @Test
+ public void testPrepareClearBoundsForTasks() {
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED);
+ task1.displayId = 1;
+ MockToken token1 = new MockToken();
+ task1.token = token1.token();
+ mOrganizer.onTaskAppeared(task1, null);
+
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED);
+ task2.displayId = 1;
+ MockToken token2 = new MockToken();
+ task2.token = token2.token();
+ mOrganizer.onTaskAppeared(task2, null);
+
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED);
+ otherDisplayTask.displayId = 2;
+ MockToken otherDisplayToken = new MockToken();
+ otherDisplayTask.token = otherDisplayToken.token();
+ mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+ WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1);
+
+ assertEquals(wct.getChanges().size(), 2);
+ Change boundsChange1 = wct.getChanges().get(token1.binder());
+ assertNotNull(boundsChange1);
+ assertNotEquals(
+ (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+ assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
+
+ Change boundsChange2 = wct.getChanges().get(token2.binder());
+ assertNotNull(boundsChange2);
+ assertNotEquals(
+ (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+ assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
+ }
+
+ @Test
+ public void testPrepareClearFreeformForTasks() {
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM);
+ task1.displayId = 1;
+ MockToken token1 = new MockToken();
+ task1.token = token1.token();
+ mOrganizer.onTaskAppeared(task1, null);
+
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ task2.displayId = 1;
+ MockToken token2 = new MockToken();
+ task2.token = token2.token();
+ mOrganizer.onTaskAppeared(task2, null);
+
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM);
+ otherDisplayTask.displayId = 2;
+ MockToken otherDisplayToken = new MockToken();
+ otherDisplayTask.token = otherDisplayToken.token();
+ mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+ WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1);
+
+ // Only task with freeform windowing mode and the right display should be updated
+ assertEquals(wct.getChanges().size(), 1);
+ Change wmModeChange1 = wct.getChanges().get(token1.binder());
+ assertNotNull(wmModeChange1);
+ assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
@@ -635,4 +708,22 @@
return taskInfo;
}
+ private static class MockToken {
+ private final WindowContainerToken mToken;
+ private final IBinder mBinder;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ mBinder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(mBinder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+
+ IBinder binder() {
+ return mBinder;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 95725bb..695550d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -159,7 +159,8 @@
}
private void waitDividerFlingFinished() {
- verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
+ verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
+ mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
new file mode 100644
index 0000000..58f20da
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.WindowConfiguration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DesktopModeControllerTest extends ShellTestCase {
+
+ @Mock
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ @Mock
+ private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ @Mock
+ private ShellExecutor mTestExecutor;
+ @Mock
+ private Handler mMockHandler;
+
+ private DesktopModeController mController;
+ private ShellInit mShellInit;
+
+ @Before
+ public void setUp() {
+ mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+
+ mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
+ mRootDisplayAreaOrganizer, mMockHandler);
+
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
+ // Create a fake WCT to simulate setting task windowing mode to undefined
+ WindowContainerTransaction taskWct = new WindowContainerTransaction();
+ MockToken taskMockToken = new MockToken();
+ taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
+ when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+ taskWct);
+
+ // Create a fake WCT to simulate setting display windowing mode to freeform
+ WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ MockToken displayMockToken = new MockToken();
+ displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM);
+ when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+ WINDOWING_MODE_FREEFORM)).thenReturn(displayWct);
+
+ // The test
+ mController.updateDesktopModeEnabled(true);
+
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+ // WCT should have 2 changes - clear task wm mode and set display wm mode
+ WindowContainerTransaction wct = arg.getValue();
+ assertThat(wct.getChanges()).hasSize(2);
+
+ // Verify executed WCT has a change for setting task windowing mode to undefined
+ Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
+ assertThat(taskWmModeChange).isNotNull();
+ assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+ // Verify executed WCT has a change for setting display windowing mode to freeform
+ Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ assertThat(displayWmModeChange).isNotNull();
+ assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
+ // Create a fake WCT to simulate setting task windowing mode to undefined
+ WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
+ MockToken taskWmMockToken = new MockToken();
+ taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
+ when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+ taskWmWct);
+
+ // Create a fake WCT to simulate clearing task bounds
+ WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
+ MockToken taskBoundsMockToken = new MockToken();
+ taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
+ when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn(
+ taskBoundsWct);
+
+ // Create a fake WCT to simulate setting display windowing mode to fullscreen
+ WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ MockToken displayMockToken = new MockToken();
+ displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN);
+ when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+ WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct);
+
+ // The test
+ mController.updateDesktopModeEnabled(false);
+
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+ // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
+ WindowContainerTransaction wct = arg.getValue();
+ assertThat(wct.getChanges()).hasSize(3);
+
+ // Verify executed WCT has a change for setting task windowing mode to undefined
+ Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder());
+ assertThat(taskWmModeChange).isNotNull();
+ assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+ // Verify executed WCT has a change for clearing task bounds
+ Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder());
+ assertThat(taskBoundsChange).isNotNull();
+ assertThat(taskBoundsChange.getWindowSetMask()
+ & WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
+ assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty())
+ .isTrue();
+
+ // Verify executed WCT has a change for setting display windowing mode to fullscreen
+ Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ assertThat(displayWmModeChange).isNotNull();
+ assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
+
+ private static class MockToken {
+ private final WindowContainerToken mToken;
+ private final IBinder mBinder;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ mBinder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(mBinder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+
+ IBinder binder() {
+ return mBinder;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 226843e..e11be31 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -24,7 +24,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -107,7 +106,7 @@
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
- .create(any(), any(), any(), anyBoolean());
+ .create(any(), any(), any());
}
@Test
@@ -148,8 +147,7 @@
verify(decorContainerSurfaceBuilder, never()).build();
verify(taskBackgroundSurfaceBuilder, never()).build();
- verify(mMockSurfaceControlViewHostFactory, never())
- .create(any(), any(), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
verify(mMockSurfaceControlFinishT).hide(taskSurface);
@@ -207,8 +205,7 @@
verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
- verify(mMockSurfaceControlViewHostFactory)
- .create(any(), eq(defaultDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
@@ -326,8 +323,7 @@
verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
- verify(mMockSurfaceControlViewHostFactory)
- .create(any(), eq(mockDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 9a4bda2..3c67edc 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -125,9 +125,14 @@
}
Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
- int weight, int italic) {
+ int weight, int italic, const Typeface* fallback) {
Typeface* result = new Typeface;
- result->fFontCollection = minikin::FontCollection::create(families);
+ if (fallback == nullptr) {
+ result->fFontCollection = minikin::FontCollection::create(std::move(families));
+ } else {
+ result->fFontCollection =
+ fallback->fFontCollection->createCollectionWithFamilies(std::move(families));
+ }
if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
int weightFromFont;
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 0c3ef01..565136e 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -78,7 +78,8 @@
Typeface* src, const std::vector<minikin::FontVariation>& variations);
static Typeface* createFromFamilies(
- std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic);
+ std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
+ const Typeface* fallback);
static void setDefault(const Typeface* face);
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index f5ed568..209b35c 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -109,27 +109,14 @@
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
jlong fallbackPtr, int weight, int italic) {
ScopedLongArrayRO families(env, familyArray);
- std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
Typeface* typeface = (fallbackPtr == 0) ? nullptr : toTypeface(fallbackPtr);
- if (typeface != nullptr) {
- const std::shared_ptr<minikin::FontCollection>& fallbackCollection =
- toTypeface(fallbackPtr)->fFontCollection;
- familyVec.reserve(families.size() + fallbackCollection->getFamilyCount());
- for (size_t i = 0; i < families.size(); i++) {
- FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
- familyVec.emplace_back(family->family);
- }
- for (size_t i = 0; i < fallbackCollection->getFamilyCount(); i++) {
- familyVec.emplace_back(fallbackCollection->getFamilyAt(i));
- }
- } else {
- familyVec.reserve(families.size());
- for (size_t i = 0; i < families.size(); i++) {
- FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
- familyVec.emplace_back(family->family);
- }
+ std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
+ familyVec.reserve(families.size());
+ for (size_t i = 0; i < families.size(); i++) {
+ FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
+ familyVec.emplace_back(family->family);
}
- return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic));
+ return toJLong(Typeface::createFromFamilies(std::move(familyVec), weight, italic, typeface));
}
// CriticalNative
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 81bcb4e..c5196ee 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -54,8 +54,6 @@
#include <SkColorSpace.h>
#include <SkRefCnt.h>
-class GrVkExtensions;
-
namespace android {
namespace uirenderer {
namespace renderthread {
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index a0bc5aa..2aeb42c 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -16,7 +16,8 @@
#include "TestSceneBase.h"
-#include <SkColorMatrixFilter.h>
+#include <SkColorFilter.h>
+#include <SkColorMatrix.h>
#include <SkGradientShader.h>
class SimpleColorMatrixAnimation;
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 25cc8ca..499afa0 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -73,7 +73,8 @@
TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+ nullptr /* fallback */));
EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get()));
// Keep the original to restore it later.
@@ -351,24 +352,24 @@
TEST(TypefaceTest, createFromFamilies_Single) {
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
- std::unique_ptr<Typeface> regular(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false));
+ std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 400, false, nullptr /* fallback */));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
- std::unique_ptr<Typeface> bold(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false));
+ std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 700, false, nullptr /* fallback */));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
- std::unique_ptr<Typeface> italic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true));
+ std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 400, true, nullptr /* fallback */));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -376,8 +377,8 @@
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
- std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true));
+ std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 700, true, nullptr /* fallback */));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -385,8 +386,8 @@
// In Java,
// new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
- std::unique_ptr<Typeface> over1000(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false));
+ std::unique_ptr<Typeface> over1000(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kRobotoVariable), 1100, false, nullptr /* fallback */));
EXPECT_EQ(1000, over1000->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
@@ -394,30 +395,33 @@
TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
// In Java, new Typeface.Builder("Family-Regular.ttf").build();
- std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
// In Java, new Typeface.Builder("Family-Bold.ttf").build();
- std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
- makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> bold(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
// In Java, new Typeface.Builder("Family-Italic.ttf").build();
- std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
- makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> italic(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
// In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
- std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont),
- RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> boldItalic(Typeface::createFromFamilies(
+ makeSingleFamlyVector(kBoldItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
+ nullptr /* fallback */));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
@@ -427,8 +431,9 @@
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont),
buildFamily(kBoldItalicFont)};
- std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
- std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> typeface(
+ Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(400, typeface->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
}
@@ -436,10 +441,24 @@
TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
- std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
- std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ std::unique_ptr<Typeface> typeface(
+ Typeface::createFromFamilies(std::move(families), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
EXPECT_EQ(700, typeface->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, typeface->fStyle.slant());
}
+TEST(TypefaceTest, createFromFamilies_Family_withFallback) {
+ std::vector<std::shared_ptr<minikin::FontFamily>> fallbackFamilies = {
+ buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
+ std::unique_ptr<Typeface> fallback(
+ Typeface::createFromFamilies(std::move(fallbackFamilies), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, nullptr /* fallback */));
+ std::unique_ptr<Typeface> regular(
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE,
+ RESOLVE_BY_FONT_TABLE, fallback.get()));
+ EXPECT_EQ(400, regular->fStyle.weight());
+ EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
+}
+
} // namespace
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index ade0ea4..2f8d92b 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -124,15 +124,22 @@
packageName, attributionTag, listenerId);
}
+ // in some tests these constants are loaded too early leading to an "incorrect" view of the
+ // current pid and uid. load lazily to prevent this problem in tests.
+ private static class Loader {
+ private static final int MY_UID = Process.myUid();
+ private static final int MY_PID = Process.myPid();
+ }
+
private final int mUid;
private final int mPid;
private final String mPackageName;
- private final @Nullable String mAttributionTag;
+ @Nullable private final String mAttributionTag;
- private final @Nullable String mListenerId;
+ @Nullable private final String mListenerId;
private CallerIdentity(int uid, int pid, String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
@@ -181,6 +188,24 @@
return mUid == Process.SYSTEM_UID;
}
+ /** Returns true if this identity represents the same user this code is running in. */
+ public boolean isMyUser() {
+ return UserHandle.getUserId(mUid) == UserHandle.getUserId(Loader.MY_UID);
+ }
+
+ /** Returns true if this identity represents the same uid this code is running in. */
+ public boolean isMyUid() {
+ return mUid == Loader.MY_UID;
+ }
+
+ /**
+ * Returns true if this identity represents the same process this code is running in. Returns
+ * false if the identity process is unknown.
+ */
+ public boolean isMyProcess() {
+ return mPid == Loader.MY_PID;
+ }
+
/**
* Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
* new worksource representing this identity.
diff --git a/media/Android.bp b/media/Android.bp
index d28a21c..ec243bf 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -113,7 +113,7 @@
min_sdk_version: "29",
apex_available: [
"//apex_available:platform",
- "com.android.bluetooth",
+ "com.android.btservices",
],
},
},
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index c666140..650f360 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1299,8 +1299,8 @@
/** @hide */ public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix";
/** @hide */ public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx";
/** @hide */ public static final String DEVICE_OUT_LINE_NAME = "line";
- /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc";
- /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hmdi_earc";
+ /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hdmi_arc";
+ /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hdmi_earc";
/** @hide */ public static final String DEVICE_OUT_SPDIF_NAME = "spdif";
/** @hide */ public static final String DEVICE_OUT_FM_NAME = "fm_transmitter";
/** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line";
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index c148846..a316c21 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -126,6 +126,20 @@
}
/**
+ * Factory method for <code>BluetoothProfileConnectionInfo</code> for an LE output device
+ * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
+ * intent will not be sent.
+ * @param volume the volume index of the device, -1 if unknown or to be ignored
+ * @return an instance of BluetoothProfileConnectionInfo for the BLE output device that reflects
+ * the given parameters
+ */
+ public static @NonNull BluetoothProfileConnectionInfo createLeAudioOutputInfo(
+ boolean suppressNoisyIntent, int volume) {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent,
+ volume, /*isLeOutput*/ true);
+ }
+
+ /**
* @return The profile connection
*/
public int getProfile() {
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
index 0c20954..08c66ab 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -25,7 +25,7 @@
import java.lang.annotation.RetentionPolicy;
/**
- * An advertisement request which can be sent to TV interactive App service to inform AD status.
+ * An advertisement response which can be sent to TV interactive App service to inform AD status.
*/
public final class AdResponse implements Parcelable {
/** @hide */
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 9f92887..a72f34c 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -217,7 +217,7 @@
case DO_DISPATCH_SURFACE_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
mSessionImpl.dispatchSurfaceChanged(
- (Integer) args.arg1, (Integer) args.arg2, (Integer) args.arg3);
+ (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
args.recycle();
break;
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
new file mode 100644
index 0000000..ae162b5
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.mediaframeworktest.unit;
+
+import static org.junit.Assert.assertEquals;
+
+import android.bluetooth.BluetoothProfile;
+import android.media.BluetoothProfileConnectionInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothProfileConnectionInfoTest {
+
+ @Test
+ public void testCoverageLeAudioOutputVolume() {
+ final boolean supprNoisy = false;
+ final int volume = 1;
+ final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+ .createLeAudioOutputInfo(supprNoisy, volume);
+ assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
+ assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+ assertEquals(info.isLeOutput(), true);
+ assertEquals(info.getVolume(), volume);
+ }
+
+}
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 4df745f..30d0c35 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -245,32 +245,23 @@
std::unique_ptr<ASystemFontIterator> ite(new ASystemFontIterator());
std::unordered_set<AFont, FontHasher> fonts;
- minikin::SystemFonts::getFontMap(
- [&fonts](const std::vector<std::shared_ptr<minikin::FontCollection>>& collections) {
- for (const auto& fc : collections) {
- for (uint32_t i = 0; i < fc->getFamilyCount(); ++i) {
- const auto& family = fc->getFamilyAt(i);
- for (uint32_t j = 0; j < family->getNumFonts(); ++j) {
- const minikin::Font* font = family->getFont(j);
-
- std::optional<std::string> locale;
- uint32_t localeId = font->getLocaleListId();
- if (localeId != minikin::kEmptyLocaleListId) {
- locale.emplace(minikin::getLocaleString(localeId));
- }
- std::vector<std::pair<uint32_t, float>> axes;
- for (const auto& [tag, value] : font->typeface()->GetAxes()) {
- axes.push_back(std::make_pair(tag, value));
- }
-
- fonts.insert(
- {font->typeface()->GetFontPath(), std::move(locale),
- font->style().weight(),
- font->style().slant() == minikin::FontStyle::Slant::ITALIC,
- static_cast<uint32_t>(font->typeface()->GetFontIndex()),
- axes});
- }
+ minikin::SystemFonts::getFontSet(
+ [&fonts](const std::vector<std::shared_ptr<minikin::Font>>& fontSet) {
+ for (const auto& font : fontSet) {
+ std::optional<std::string> locale;
+ uint32_t localeId = font->getLocaleListId();
+ if (localeId != minikin::kEmptyLocaleListId) {
+ locale.emplace(minikin::getLocaleString(localeId));
}
+ std::vector<std::pair<uint32_t, float>> axes;
+ for (const auto& [tag, value] : font->typeface()->GetAxes()) {
+ axes.push_back(std::make_pair(tag, value));
+ }
+
+ fonts.insert({font->typeface()->GetFontPath(), std::move(locale),
+ font->style().weight(),
+ font->style().slant() == minikin::FontStyle::Slant::ITALIC,
+ static_cast<uint32_t>(font->typeface()->GetFontIndex()), axes});
}
});
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
index 318eeef..cb757d3 100644
--- a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
@@ -4,6 +4,9 @@
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
</JetCodeStyleSettings>
+ <XML>
+ <option name="XML_KEEP_LINE_BREAKS" value="true" />
+ </XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 914a45b..b5be7cd 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -18,7 +18,8 @@
package="com.android.settingslib.spa.gallery">
<application
- android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_label"
android:supportsRtl="true">
<activity
android:name="com.android.settingslib.spa.gallery.MainActivity"
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..623ef56
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@mipmap/ic_launcher_background" />
+ <foreground android:drawable="@mipmap/ic_launcher_foreground" />
+</adaptive-icon>
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..c3f1ab9
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..7da1549
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..187964f
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 510e6c2..0d08d68 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 The Android Open Source Project
@@ -14,6 +15,8 @@
limitations under the License.
-->
<resources>
+ <!-- Gallery App label. [DO NOT TRANSLATE] -->
+ <string name="app_label" translatable="false">Gallery</string>
<!-- Gallery App name. [DO NOT TRANSLATE] -->
<string name="app_name" translatable="false">SpaLib Gallery</string>
</resources>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 937e594..36361dd 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
-import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavType
@@ -28,12 +27,14 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+private const val TITLE = "Sample page with arguments"
private const val STRING_PARAM_NAME = "stringParam"
private const val INT_PARAM_NAME = "intParam"
object ArgumentPageProvider : SettingsPageProvider {
- override val name = Destinations.Argument
+ override val name = "Argument"
override val arguments = listOf(
navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
@@ -51,17 +52,17 @@
@Composable
fun EntryItem(stringParam: String, intParam: Int) {
Preference(object : PreferenceModel {
- override val title = "Sample page with arguments"
+ override val title = TITLE
override val summary =
"$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam".toState()
- override val onClick = navigator("${Destinations.Argument}/$stringParam/$intParam")
+ override val onClick = navigator("$name/$stringParam/$intParam")
})
}
}
@Composable
fun ArgumentPage(stringParam: String, intParam: Int) {
- Column {
+ RegularScaffold(title = TITLE) {
Preference(object : PreferenceModel {
override val title = "String param value"
override val summary = stringParam.toState()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 143c365..82005ec 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -17,12 +17,8 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
@@ -30,8 +26,11 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Footer
+private const val TITLE = "Sample Footer"
+
object FooterPageProvider : SettingsPageProvider {
override val name = "Footer"
@@ -43,7 +42,7 @@
@Composable
fun EntryItem() {
Preference(object : PreferenceModel {
- override val title = "Sample Footer"
+ override val title = TITLE
override val onClick = navigator(name)
})
}
@@ -51,7 +50,7 @@
@Composable
private fun FooterPage() {
- Column(Modifier.verticalScroll(rememberScrollState())) {
+ RegularScaffold(title = TITLE) {
Preference(remember {
object : PreferenceModel {
override val title = "Some Preference"
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
index 0d17cd2..6b7de8d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
@@ -31,7 +31,7 @@
import com.android.settingslib.spa.gallery.R
object HomePageProvider : SettingsPageProvider {
- override val name = Destinations.Home
+ override val name = "Home"
@Composable
override fun Page(arguments: Bundle?) {
@@ -54,6 +54,7 @@
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
SliderPageProvider.EntryItem()
+ SettingsPagerPageProvider.EntryItem()
FooterPageProvider.EntryItem()
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
index 8f38d82..cbfc603 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
@@ -18,14 +18,6 @@
import com.android.settingslib.spa.framework.api.SettingsPageRepository
-object Destinations {
- const val Home = "Home"
- const val Preference = "Preference"
- const val SwitchPreference = "SwitchPreference"
- const val Argument = "Argument"
- const val Slider = "Slider"
-}
-
val galleryPageRepository = SettingsPageRepository(
allPages = listOf(
HomePageProvider,
@@ -33,7 +25,8 @@
SwitchPreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
+ SettingsPagerPageProvider,
FooterPageProvider,
),
- startDestination = Destinations.Home,
+ startDestination = HomePageProvider.name,
)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
index 3077a15..0463e58 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
@@ -17,18 +17,17 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.DisabledByDefault
+import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
@@ -36,10 +35,14 @@
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.SettingsIcon
import kotlinx.coroutines.delay
+private const val TITLE = "Sample Preference"
+
object PreferencePageProvider : SettingsPageProvider {
- override val name = Destinations.Preference
+ override val name = "Preference"
@Composable
override fun Page(arguments: Bundle?) {
@@ -49,15 +52,15 @@
@Composable
fun EntryItem() {
Preference(object : PreferenceModel {
- override val title = "Sample Preference"
- override val onClick = navigator(Destinations.Preference)
+ override val title = TITLE
+ override val onClick = navigator(name)
})
}
}
@Composable
private fun PreferencePage() {
- Column(Modifier.verticalScroll(rememberScrollState())) {
+ RegularScaffold(title = TITLE) {
Preference(object : PreferenceModel {
override val title = "Preference"
})
@@ -75,14 +78,17 @@
}
})
- var count by remember { mutableStateOf(0) }
+ var count by rememberSaveable { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = "Click me"
override val summary = derivedStateOf { count.toString() }
override val onClick: (() -> Unit) = { count++ }
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.TouchApp)
+ }
})
- var ticks by remember { mutableStateOf(0) }
+ var ticks by rememberSaveable { mutableStateOf(0) }
LaunchedEffect(ticks) {
delay(1000L)
ticks++
@@ -96,6 +102,9 @@
override val title = "Disabled"
override val summary = "Disabled".toState()
override val enabled = false.toState()
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+ }
})
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
new file mode 100644
index 0000000..5351ea6
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+object SettingsPagerPageProvider : SettingsPageProvider {
+ override val name = "SettingsPager"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SettingsPagerPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample SettingsPager"
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+private fun SettingsPagerPage() {
+ SettingsPager(listOf("Personal", "Work")) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsPagerPagePreview() {
+ SettingsTheme {
+ SettingsPagerPage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 9bcac1b..04046fa 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -17,9 +17,6 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccessAlarm
import androidx.compose.material.icons.outlined.MusicNote
@@ -29,18 +26,20 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.SettingsSlider
import com.android.settingslib.spa.widget.ui.SettingsSliderModel
+private const val TITLE = "Sample Slider"
+
object SliderPageProvider : SettingsPageProvider {
- override val name = Destinations.Slider
+ override val name = "Slider"
@Composable
override fun Page(arguments: Bundle?) {
@@ -50,15 +49,15 @@
@Composable
fun EntryItem() {
Preference(object : PreferenceModel {
- override val title = "Sample Slider"
- override val onClick = navigator(Destinations.Slider)
+ override val title = TITLE
+ override val onClick = navigator(name)
})
}
}
@Composable
private fun SliderPage() {
- Column(Modifier.verticalScroll(rememberScrollState())) {
+ RegularScaffold(title = TITLE) {
SettingsSlider(object : SettingsSliderModel {
override val title = "Slider"
override val initValue = 40
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
index b6f7258..e9e5d35 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
@@ -17,15 +17,11 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
@@ -35,10 +31,13 @@
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import kotlinx.coroutines.delay
+private const val TITLE = "Sample SwitchPreference"
+
object SwitchPreferencePageProvider : SettingsPageProvider {
- override val name = Destinations.SwitchPreference
+ override val name = "SwitchPreference"
@Composable
override fun Page(arguments: Bundle?) {
@@ -48,15 +47,15 @@
@Composable
fun EntryItem() {
Preference(object : PreferenceModel {
- override val title = "Sample SwitchPreference"
- override val onClick = navigator(Destinations.SwitchPreference)
+ override val title = TITLE
+ override val onClick = navigator(name)
})
}
}
@Composable
private fun SwitchPreferencePage() {
- Column(Modifier.verticalScroll(rememberScrollState())) {
+ RegularScaffold(title = TITLE) {
SampleSwitchPreference()
SampleSwitchPreferenceWithSummary()
SampleSwitchPreferenceWithAsyncSummary()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index e1ca69b..7d3e107 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -31,4 +31,5 @@
end = itemPaddingEnd,
bottom = itemPaddingVertical,
)
+ val itemPaddingAround = 8.dp
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
similarity index 73%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index 8dde897..20020d0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spa.framework.theme
-import android.content.pm.ApplicationInfo
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.unit.dp
-interface AppRecord {
- val app: ApplicationInfo
+object SettingsShape {
+ val CornerMedium = RoundedCornerShape(12.dp)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 1a80ed2..0e6f53a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -19,6 +19,7 @@
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.android.settingslib.spa.framework.compose.stateOf
@@ -38,6 +39,14 @@
get() = stateOf("")
/**
+ * The icon of this [Preference].
+ *
+ * Default is `null` which means no icon.
+ */
+ val icon: (@Composable () -> Unit)?
+ get() = null
+
+ /**
* Indicates whether this [Preference] is enabled.
*
* Disabled [Preference] will be displayed in disabled style.
@@ -61,13 +70,16 @@
*/
@Composable
fun Preference(model: PreferenceModel) {
- val modifier = model.onClick?.let { onClick ->
- Modifier.clickable(enabled = model.enabled.value) { onClick() }
- } ?: Modifier
+ val modifier = remember(model.enabled.value, model.onClick) {
+ model.onClick?.let { onClick ->
+ Modifier.clickable(enabled = model.enabled.value, onClick = onClick)
+ } ?: Modifier
+ }
BasePreference(
title = model.title,
summary = model.summary,
modifier = modifier,
+ icon = model.icon,
enabled = model.enabled,
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index 0dab0df..b6d6936 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -100,7 +100,7 @@
) {
val checkedValue = checked.value
val indication = LocalIndication.current
- val modifier = remember(checkedValue) {
+ val modifier = remember(checkedValue, changeable.value) {
if (checkedValue != null && onCheckedChange != null) {
Modifier.toggleable(
value = checkedValue,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
new file mode 100644
index 0000000..0a41a1a
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.spa.framework.compose.LocalNavController
+
+@Composable
+internal fun NavigateUp() {
+ val navController = LocalNavController.current
+ val contentDescription = stringResource(
+ id = androidx.appcompat.R.string.abc_action_bar_up_description,
+ )
+ BackAction(contentDescription) {
+ navController.navigateUp()
+ }
+}
+
+@Composable
+private fun BackAction(contentDescription: String, onClick: () -> Unit) {
+ IconButton(onClick) {
+ Icon(
+ imageVector = Icons.Outlined.ArrowBack,
+ contentDescription = contentDescription,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
new file mode 100644
index 0000000..3325d53
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SmallTopAppBar
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+/**
+ * A [Scaffold] which content is scrollable and wrapped in a [Column].
+ *
+ * For example, this is for the pages with some preferences and is scrollable when the items out of
+ * the screen.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun RegularScaffold(
+ title: String,
+ actions: @Composable RowScope.() -> Unit = {},
+ content: @Composable () -> Unit,
+) {
+ Scaffold(
+ topBar = {
+ SmallTopAppBar(
+ title = {
+ Text(
+ text = title,
+ modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
+ )
+ },
+ navigationIcon = { NavigateUp() },
+ actions = actions,
+ colors = settingsTopAppBarColors(),
+ )
+ },
+ ) { paddingValues ->
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Spacer(Modifier.padding(paddingValues))
+ content()
+ }
+ }
+}
+
+@Composable
+internal fun settingsTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
+ containerColor = SettingsTheme.colorScheme.surfaceHeader,
+ scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
+)
+
+@Preview
+@Composable
+private fun RegularScaffoldPreview() {
+ SettingsTheme {
+ RegularScaffold(title = "Display") {}
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
new file mode 100644
index 0000000..1ec2390
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.TabRow
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit) {
+ check(titles.isNotEmpty())
+ if (titles.size == 1) {
+ content(0)
+ return
+ }
+
+ Column {
+ var currentPage by rememberSaveable { mutableStateOf(0) }
+
+ TabRow(
+ selectedTabIndex = currentPage,
+ modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd),
+ containerColor = Color.Transparent,
+ indicator = {},
+ divider = {},
+ ) {
+ titles.forEachIndexed { page, title ->
+ SettingsTab(
+ title = title,
+ selected = currentPage == page,
+ onClick = { currentPage = page },
+ )
+ }
+ }
+
+ content(currentPage)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
new file mode 100644
index 0000000..16d8dbc
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Tab
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+internal fun SettingsTab(
+ title: String,
+ selected: Boolean,
+ onClick: () -> Unit,
+) {
+ Tab(
+ selected = selected,
+ onClick = onClick,
+ modifier = Modifier
+ .height(48.dp)
+ .padding(horizontal = 4.dp, vertical = 6.dp)
+ .clip(SettingsShape.CornerMedium)
+ .background(color = when {
+ selected -> SettingsTheme.colorScheme.primaryContainer
+ else -> SettingsTheme.colorScheme.surface
+ }),
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.labelLarge,
+ color = when {
+ selected -> SettingsTheme.colorScheme.onPrimaryContainer
+ else -> SettingsTheme.colorScheme.secondaryText
+ },
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsTabPreview() {
+ SettingsTheme {
+ Column {
+ SettingsTab(title = "Personal", selected = true) {}
+ SettingsTab(title = "Work", selected = false) {}
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
index 41fd03b..296cf3b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -20,8 +20,11 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@@ -32,7 +35,12 @@
fun Footer(footerText: String) {
if (footerText.isEmpty()) return
Column(Modifier.padding(SettingsDimension.itemPadding)) {
- SettingsIcon(imageVector = Icons.Outlined.Info, contentDescription = null)
+ Icon(
+ imageVector = Icons.Outlined.Info,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
SettingsBody(footerText)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
index cb08cdb..4f28e37 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
@@ -25,13 +25,10 @@
import com.android.settingslib.spa.framework.theme.SettingsDimension
@Composable
-fun SettingsIcon(
- imageVector: ImageVector,
- contentDescription: String?,
-) {
+fun SettingsIcon(imageVector: ImageVector) {
Icon(
imageVector = imageVector,
- contentDescription = contentDescription,
+ contentDescription = null,
modifier = Modifier.size(SettingsDimension.itemIconSize),
tint = MaterialTheme.colorScheme.onSurface,
)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
new file mode 100644
index 0000000..f608e10
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotSelected
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPagerKtTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun twoPage_initialState() {
+ composeTestRule.setContent {
+ TestTwoPage()
+ }
+
+ composeTestRule.onNodeWithText("Personal").assertIsSelected()
+ composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Work").assertIsNotSelected()
+ composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+ }
+
+ @Test
+ fun twoPage_afterSwitch() {
+ composeTestRule.setContent {
+ TestTwoPage()
+ }
+
+ composeTestRule.onNodeWithText("Work").performClick()
+
+ composeTestRule.onNodeWithText("Personal").assertIsNotSelected()
+ composeTestRule.onNodeWithText("Page 0").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Work").assertIsSelected()
+ composeTestRule.onNodeWithText("Page 1").assertIsDisplayed()
+ }
+
+ @Test
+ fun onePage_initialState() {
+ composeTestRule.setContent {
+ SettingsPager(listOf("Personal")) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
+
+ composeTestRule.onNodeWithText("Personal").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+ }
+}
+
+@Composable
+private fun TestTwoPage() {
+ SettingsPager(listOf("Personal", "Work")) {
+ SettingsTitle(title = "Page $it")
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 48f7ff2..ecbb219 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -29,5 +29,4 @@
"androidx.compose.runtime_runtime",
],
kotlincflags: ["-Xjvm-default=all"],
- min_sdk_version: "31",
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
similarity index 96%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 3808f64..c2d85a5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
similarity index 92%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
index 8dde897..2978688 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRecord.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
import android.content.pm.ApplicationInfo
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
similarity index 96%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index a6378ef..7ffa938 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
import android.content.Context
import android.content.pm.ApplicationInfo
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
similarity index 92%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
index f675545..6e1afd9 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
import android.content.pm.ApplicationInfo
import android.os.UserHandle
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
similarity index 94%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 66b05da..0cc497a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.settingslib.spaprivileged.model.app
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
new file mode 100644
index 0000000..fab3ae8
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.spaprivileged.model.enterprise
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
+import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
+import android.content.Context
+import com.android.settingslib.spaprivileged.R
+
+class EnterpriseRepository(private val context: Context) {
+ private val resources by lazy {
+ checkNotNull(context.getSystemService(DevicePolicyManager::class.java)).resources
+ }
+
+ fun getEnterpriseString(updatableStringId: String, resId: Int): String =
+ resources.getString(updatableStringId) { context.getString(resId) }
+
+ fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) {
+ getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
+ } else {
+ getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index 5ae514c..d802b04 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -33,8 +33,8 @@
import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle
-import com.android.settingslib.spaprivileged.framework.app.PackageManagers
-import com.android.settingslib.spaprivileged.framework.app.rememberAppRepository
+import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.rememberAppRepository
@Composable
fun AppInfo(packageName: String, userId: Int) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 57e9e9a..430ac54 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -31,8 +31,8 @@
import com.android.settingslib.spa.framework.compose.rememberContext
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.framework.app.AppRecord
-import com.android.settingslib.spaprivileged.framework.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.PackageManagers
import kotlinx.coroutines.Dispatchers
private const val PERMISSION = "permission"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
index 88ad9da..3782f7c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
@@ -20,7 +20,7 @@
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import com.android.settingslib.spaprivileged.framework.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.AppRecord
interface TogglePermissionAppListModel<T : AppRecord> {
val pageTitleResId: Int
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
new file mode 100644
index 0000000..aa5ccf1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/WorkProfilePager.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.spaprivileged.template.common
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spaprivileged.model.enterprise.EnterpriseRepository
+
+@Composable
+fun WorkProfilePager(content: @Composable (userInfo: UserInfo) -> Unit) {
+ val context = LocalContext.current
+ val profiles = remember {
+ val userManager = checkNotNull(context.getSystemService(UserManager::class.java))
+ userManager.getProfiles(UserHandle.myUserId())
+ }
+ val titles = remember {
+ val enterpriseRepository = EnterpriseRepository(context)
+ profiles.map {
+ enterpriseRepository.getProfileTitle(isManagedProfile = it.isManagedProfile)
+ }
+ }
+
+ SettingsPager(titles) { page ->
+ content(profiles[page])
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 65696253..aab0d3a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1125,7 +1125,7 @@
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
- <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging temporarily limited</string>
+ <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
@@ -1138,7 +1138,7 @@
<!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging wirelessly. -->
<string name="battery_info_status_charging_wireless">Charging wirelessly</string>
<!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when the device is dock charging. -->
- <string name="battery_info_status_charging_dock">Charging Dock</string>
+ <string name="battery_info_status_charging_dock">Charging</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_discharging">Not charging</string>
<!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
new file mode 100644
index 0000000..d40f322
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
@@ -0,0 +1,2 @@
+# Default reviewers for this and subdirectories.
+shaoweishen@google.com
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 42b992f..5aae1ce 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -182,6 +182,7 @@
Settings.Secure.PEOPLE_STRIP,
Settings.Secure.MEDIA_CONTROLS_RESUME,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
@@ -212,6 +213,8 @@
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
Settings.Secure.WEAR_TALKBACK_ENABLED,
Settings.Secure.HBM_SETTING_KEY,
- Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 14b5855..67ea024 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -277,6 +277,7 @@
VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_RECOMMENDATION, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.MEDIA_CONTROLS_LOCK_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
new InclusiveIntegerRangeValidator(
Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
@@ -346,5 +347,9 @@
VALIDATORS.put(Secure.WEAR_TALKBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HBM_SETTING_KEY, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c3b645e..a2ffcf3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1828,6 +1828,12 @@
dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
SecureSettingsProto.Accessibility.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+ SecureSettingsProto.Accessibility.SoftwareCursorSettings.TRIGGER_HINTS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
+ SecureSettingsProto.Accessibility.SoftwareCursorSettings.KEYBOARD_SHIFT_ENABLED);
p.end(accessibilityToken);
final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index fd7554f..528af2e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -376,9 +376,11 @@
Setting newSetting = new Setting(name, oldSetting.getValue(), null,
oldSetting.getPackageName(), oldSetting.getTag(), false,
oldSetting.getId());
- mSettings.put(name, newSetting);
- updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
+ int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue,
newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue());
+ checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
+ mSettings.put(name, newSetting);
+ updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize);
scheduleWriteIfNeededLocked();
}
}
@@ -410,6 +412,12 @@
Setting oldState = mSettings.get(name);
String oldValue = (oldState != null) ? oldState.value : null;
String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null;
+ String newDefaultValue = makeDefault ? value : oldDefaultValue;
+
+ int newSize = getNewMemoryUsagePerPackageLocked(packageName, oldValue, value,
+ oldDefaultValue, newDefaultValue);
+ checkNewMemoryUsagePerPackageLocked(packageName, newSize);
+
Setting newState;
if (oldState != null) {
@@ -430,8 +438,7 @@
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState);
- updateMemoryUsagePerPackageLocked(packageName, oldValue, value,
- oldDefaultValue, newState.getDefaultValue());
+ updateMemoryUsagePerPackageLocked(packageName, newSize);
scheduleWriteIfNeededLocked();
@@ -552,13 +559,14 @@
}
Setting oldState = mSettings.remove(name);
+ int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
+ null, oldState.defaultValue, null);
FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, name, /* value= */ "",
/* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
FrameworkStatsLog.SETTING_CHANGED__REASON__DELETED);
- updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value,
- null, oldState.defaultValue, null);
+ updateMemoryUsagePerPackageLocked(oldState.packageName, newSize);
addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
@@ -579,16 +587,18 @@
Setting oldSetting = new Setting(setting);
String oldValue = setting.getValue();
String oldDefaultValue = setting.getDefaultValue();
+ String newValue = oldDefaultValue;
+ String newDefaultValue = oldDefaultValue;
+
+ int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, oldValue,
+ newValue, oldDefaultValue, newDefaultValue);
+ checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize);
if (!setting.reset()) {
return false;
}
- String newValue = setting.getValue();
- String newDefaultValue = setting.getDefaultValue();
-
- updateMemoryUsagePerPackageLocked(setting.packageName, oldValue,
- newValue, oldDefaultValue, newDefaultValue);
+ updateMemoryUsagePerPackageLocked(setting.packageName, newSize);
addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting);
@@ -696,38 +706,49 @@
}
@GuardedBy("mLock")
- private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue,
+ private boolean isExemptFromMemoryUsageCap(String packageName) {
+ return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED
+ || SYSTEM_PACKAGE_NAME.equals(packageName);
+ }
+
+ @GuardedBy("mLock")
+ private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize)
+ throws IllegalStateException {
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return;
+ }
+ if (newSize > mMaxBytesPerAppPackage) {
+ throw new IllegalStateException("You are adding too many system settings. "
+ + "You should stop using system settings for app specific data"
+ + " package: " + packageName);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int getNewMemoryUsagePerPackageLocked(String packageName, String oldValue,
String newValue, String oldDefaultValue, String newDefaultValue) {
- if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) {
- return;
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return 0;
}
-
- if (SYSTEM_PACKAGE_NAME.equals(packageName)) {
- return;
- }
-
+ final Integer currentSize = mPackageToMemoryUsage.get(packageName);
final int oldValueSize = (oldValue != null) ? oldValue.length() : 0;
final int newValueSize = (newValue != null) ? newValue.length() : 0;
final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0;
final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0;
final int deltaSize = newValueSize + newDefaultValueSize
- oldValueSize - oldDefaultValueSize;
+ return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0);
+ }
- Integer currentSize = mPackageToMemoryUsage.get(packageName);
- final int newSize = Math.max((currentSize != null)
- ? currentSize + deltaSize : deltaSize, 0);
-
- if (newSize > mMaxBytesPerAppPackage) {
- throw new IllegalStateException("You are adding too many system settings. "
- + "You should stop using system settings for app specific data"
- + " package: " + packageName);
+ @GuardedBy("mLock")
+ private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) {
+ if (isExemptFromMemoryUsageCap(packageName)) {
+ return;
}
-
if (DEBUG) {
Slog.i(LOG_TAG, "Settings for package: " + packageName
+ " size: " + newSize + " bytes.");
}
-
mPackageToMemoryUsage.put(packageName, newSize);
}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 69eb713..a637efa 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -20,6 +20,8 @@
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.google.common.base.Strings;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -276,4 +278,39 @@
settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
return settingsState;
}
+
+ public void testInsertSetting_memoryUsage() {
+ SettingsState settingsState = getSettingStateObject();
+ // No exception should be thrown when there is no cap
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ settingsState.deleteSettingLocked(SETTING_NAME);
+
+ settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ // System package doesn't have memory usage limit
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, SYSTEM_PACKAGE);
+ settingsState.deleteSettingLocked(SETTING_NAME);
+
+ // Should not throw if usage is under the cap
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 19999),
+ null, false, "p1");
+ settingsState.deleteSettingLocked(SETTING_NAME);
+ try {
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("p1"));
+ }
+ try {
+ settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
+ null, false, "p1");
+ fail("Should throw because it exceeded per package memory usage");
+ } catch (IllegalStateException ex) {
+ assertTrue(ex.getMessage().contains("p1"));
+ }
+ assertTrue(settingsState.getSettingLocked(SETTING_NAME).isNull());
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 634df39..a25f567 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -238,6 +238,9 @@
"com.android.systemui",
],
plugins: ["dagger2-compiler"],
+ lint: {
+ test: true,
+ },
}
// Opt-in config for optimizing the SystemUI target using R8.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6edf13a..652e281 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -394,6 +394,11 @@
android:label="@string/screenshot_scroll_label"
android:finishOnTaskLaunch="true" />
+ <service android:name=".screenshot.ScreenshotProxyService"
+ android:permission="com.android.systemui.permission.SELF"
+ android:exported="false" />
+
+
<service android:name=".screenrecord.RecordingService" />
<receiver android:name=".SysuiRestartReceiver"
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 61fac29..4a6164c 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -14,11 +14,10 @@
brycelee@google.com
ccassidy@google.com
chrisgollner@google.com
-cinek@google.com
-cwren@google.com
dupin@google.com
ethibodeau@google.com
evanlaird@google.com
+florenceyang@google.com
gwasserman@google.com
hwwang@google.com
hyunyoungs@google.com
@@ -27,36 +26,36 @@
jbolinger@google.com
jdemeulenaere@google.com
jeffdq@google.com
+jernej@google.com
+jglazier@google.com
jjaggi@google.com
jonmiranda@google.com
joshtrask@google.com
juliacr@google.com
juliatuttle@google.com
justinkoh@google.com
-kchyn@google.com
kozynski@google.com
kprevas@google.com
lynhan@google.com
madym@google.com
mankoff@google.com
-mett@google.com
mkephart@google.com
mpietal@google.com
mrcasey@google.com
mrenouf@google.com
-nesciosquid@google.com
nickchameyev@google.com
nicomazz@google.com
ogunwale@google.com
peanutbutter@google.com
+peskal@google.com
pinyaoting@google.com
pixel@google.com
+pomini@google.com
rahulbanerjee@google.com
roosa@google.com
santie@google.com
shanh@google.com
snoeberger@google.com
-sreyasr@google.com
steell@google.com
sfufa@google.com
stwu@google.com
@@ -70,15 +69,10 @@
victortulias@google.com
winsonc@google.com
wleshner@google.com
-yurilin@google.com
xuqiu@google.com
+yuandizhou@google.com
+yurilin@google.com
zakcohen@google.com
-jernej@google.com
-jglazier@google.com
-peskal@google.com
-
-#Android Auto
-hseog@google.com
#Android TV
rgl@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index cc7d23e..dc2c6356 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -165,6 +165,8 @@
* @param includeFadeIn true if the animator should also fade in the view and child views.
* @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
* [includeFadeIn] is false.
+ * @param onAnimationEnd an optional runnable that will be run once the animation
+ * finishes successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateAddition(
@@ -174,7 +176,8 @@
duration: Long = DEFAULT_DURATION,
includeMargins: Boolean = false,
includeFadeIn: Boolean = false,
- fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR
+ fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR,
+ onAnimationEnd: Runnable? = null,
): Boolean {
if (
occupiesSpace(
@@ -193,7 +196,8 @@
origin,
interpolator,
duration,
- ignorePreviousValues = !includeMargins
+ ignorePreviousValues = !includeMargins,
+ onAnimationEnd,
)
addListener(rootView, listener, recursive = true)
@@ -246,14 +250,16 @@
origin: Hotspot,
interpolator: Interpolator,
duration: Long,
- ignorePreviousValues: Boolean
+ ignorePreviousValues: Boolean,
+ onAnimationEnd: Runnable? = null,
): View.OnLayoutChangeListener {
return createListener(
interpolator,
duration,
ephemeral = true,
origin = origin,
- ignorePreviousValues = ignorePreviousValues
+ ignorePreviousValues = ignorePreviousValues,
+ onAnimationEnd,
)
}
@@ -272,7 +278,8 @@
duration: Long,
ephemeral: Boolean,
origin: Hotspot? = null,
- ignorePreviousValues: Boolean = false
+ ignorePreviousValues: Boolean = false,
+ onAnimationEnd: Runnable? = null,
): View.OnLayoutChangeListener {
return object : View.OnLayoutChangeListener {
override fun onLayoutChange(
@@ -340,7 +347,8 @@
endValues,
interpolator,
duration,
- ephemeral
+ ephemeral,
+ onAnimationEnd,
)
}
}
@@ -903,7 +911,8 @@
endValues: Map<Bound, Int>,
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
+ onAnimationEnd: Runnable? = null,
) {
val propertyValuesHolders =
buildList {
@@ -941,6 +950,9 @@
// listener.
recursivelyRemoveListener(view)
}
+ if (!cancelled) {
+ onAnimationEnd?.run()
+ }
}
override fun onAnimationCancel(animation: Animator?) {
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 40218de..325ede6 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -30,6 +30,7 @@
],
static_libs: [
+ "SystemUI-core",
"SystemUIComposeCore",
"androidx.compose.runtime_runtime",
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
index 0aea99d..eada40e 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -16,7 +16,38 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+
package="com.android.systemui.compose.features">
-
-
+ <application
+ android:name="android.app.Application"
+ android:appComponentFactory="androidx.core.app.AppComponentFactory"
+ tools:replace="android:name,android:appComponentFactory">
+ <!-- Disable providers from SystemUI -->
+ <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
+ android:authorities="com.android.systemui.test.keyguard.clock.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.systemui.people.PeopleProvider"
+ android:authorities="com.android.systemui.test.people.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="androidx.core.content.FileProvider"
+ android:authorities="com.android.systemui.test.fileprovider.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove"/>
+ </application>
</manifest>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
new file mode 100644
index 0000000..2bf1937
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.people.ui.compose
+
+import android.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Divider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import kotlinx.coroutines.flow.collect
+
+/**
+ * Compose the screen associated to a [PeopleViewModel].
+ *
+ * @param viewModel the [PeopleViewModel] that should be composed.
+ * @param onResult the callback called with the result of this screen. Callers should usually finish
+ * the Activity/Fragment/View hosting this Composable once a result is available.
+ */
+@Composable
+fun PeopleScreen(
+ viewModel: PeopleViewModel,
+ onResult: (PeopleViewModel.Result) -> Unit,
+) {
+ val priorityTiles by viewModel.priorityTiles.collectAsState()
+ val recentTiles by viewModel.recentTiles.collectAsState()
+
+ // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it
+ // updates them when going back to the Activity after leaving it.
+ val lifecycleOwner = LocalLifecycleOwner.current
+ LaunchedEffect(lifecycleOwner, viewModel) {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewModel.onTileRefreshRequested()
+ }
+ }
+
+ // Call [onResult] this activity when the ViewModel tells us so.
+ LaunchedEffect(viewModel.result) {
+ viewModel.result.collect { result ->
+ if (result != null) {
+ viewModel.clearResult()
+ onResult(result)
+ }
+ }
+ }
+
+ // Make sure to use the Android colors and not the default Material3 colors to have the exact
+ // same colors as the View implementation.
+ val androidColors = LocalAndroidColorScheme.current
+ Surface(
+ color = androidColors.colorBackground,
+ contentColor = androidColors.textColorPrimary,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
+ PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked)
+ } else {
+ PeopleScreenEmpty(viewModel::onUserJourneyCancelled)
+ }
+ }
+}
+
+@Composable
+private fun PeopleScreenWithConversations(
+ priorityTiles: List<PeopleTileViewModel>,
+ recentTiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+) {
+ Column {
+ Column(
+ Modifier.fillMaxWidth().padding(PeopleSpacePadding),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ stringResource(R.string.select_conversation_title),
+ style = MaterialTheme.typography.headlineSmall,
+ textAlign = TextAlign.Center,
+ )
+
+ Spacer(Modifier.height(24.dp))
+
+ Text(
+ stringResource(R.string.select_conversation_text),
+ Modifier.padding(horizontal = 24.dp),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ )
+ }
+
+ LazyColumn(
+ Modifier.fillMaxWidth(),
+ contentPadding =
+ PaddingValues(
+ top = 16.dp,
+ bottom = PeopleSpacePadding,
+ start = 8.dp,
+ end = 8.dp,
+ )
+ ) {
+ ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked)
+ item { Spacer(Modifier.height(35.dp)) }
+ ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
+ }
+ }
+}
+
+private fun LazyListScope.ConversationList(
+ @StringRes headerTextResource: Int,
+ tiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit
+) {
+ item {
+ Text(
+ stringResource(headerTextResource),
+ Modifier.padding(start = 16.dp),
+ style = MaterialTheme.typography.labelLarge,
+ color = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ )
+
+ Spacer(Modifier.height(10.dp))
+ }
+
+ tiles.forEachIndexed { index, tile ->
+ if (index > 0) {
+ item {
+ Divider(
+ color = LocalAndroidColorScheme.current.colorBackground,
+ thickness = 2.dp,
+ )
+ }
+ }
+
+ item(tile.key.toString()) {
+ Tile(
+ tile,
+ onTileClicked,
+ withTopCornerRadius = index == 0,
+ withBottomCornerRadius = index == tiles.lastIndex,
+ )
+ }
+ }
+}
+
+@Composable
+private fun Tile(
+ tile: PeopleTileViewModel,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ withTopCornerRadius: Boolean,
+ withBottomCornerRadius: Boolean,
+) {
+ val androidColors = LocalAndroidColorScheme.current
+ val cornerRadius = dimensionResource(R.dimen.people_space_widget_radius)
+ val topCornerRadius = if (withTopCornerRadius) cornerRadius else 0.dp
+ val bottomCornerRadius = if (withBottomCornerRadius) cornerRadius else 0.dp
+
+ Surface(
+ color = androidColors.colorSurface,
+ contentColor = androidColors.textColorPrimary,
+ shape =
+ RoundedCornerShape(
+ topStart = topCornerRadius,
+ topEnd = topCornerRadius,
+ bottomStart = bottomCornerRadius,
+ bottomEnd = bottomCornerRadius,
+ ),
+ ) {
+ Row(
+ Modifier.fillMaxWidth().clickable { onTileClicked(tile) }.padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Image(
+ tile.icon.asImageBitmap(),
+ // TODO(b/238993727): Add a content description.
+ contentDescription = null,
+ Modifier.size(dimensionResource(R.dimen.avatar_size_for_medium)),
+ )
+
+ Text(
+ tile.username ?: "",
+ Modifier.padding(horizontal = 16.dp),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ }
+ }
+}
+
+/** The padding applied to the PeopleSpace screen. */
+internal val PeopleSpacePadding = 24.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
new file mode 100644
index 0000000..5c9358f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.people.ui.compose
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.systemui.R
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+
+@Composable
+internal fun PeopleScreenEmpty(
+ onGotItClicked: () -> Unit,
+) {
+ Column(
+ Modifier.fillMaxSize().padding(PeopleSpacePadding),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ stringResource(R.string.select_conversation_title),
+ style = MaterialTheme.typography.headlineSmall,
+ textAlign = TextAlign.Center,
+ )
+
+ Spacer(Modifier.height(50.dp))
+
+ Text(
+ stringResource(R.string.no_conversations_text),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ )
+
+ Spacer(Modifier.weight(1f))
+ ExampleTile()
+ Spacer(Modifier.weight(1f))
+
+ val androidColors = LocalAndroidColorScheme.current
+ Button(
+ onGotItClicked,
+ Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = androidColors.colorAccentPrimary,
+ contentColor = androidColors.textColorOnAccent,
+ )
+ ) { Text(stringResource(R.string.got_it)) }
+ }
+}
+
+@Composable
+private fun ExampleTile() {
+ val androidColors = LocalAndroidColorScheme.current
+ Surface(
+ shape = RoundedCornerShape(28.dp),
+ color = androidColors.colorSurface,
+ contentColor = androidColors.textColorPrimary,
+ ) {
+ Row(
+ Modifier.padding(vertical = 20.dp, horizontal = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ // TODO(b/238993727): Add a content description.
+ Image(
+ painterResource(R.drawable.ic_avatar_with_badge),
+ contentDescription = null,
+ Modifier.size(40.dp),
+ )
+ Spacer(Modifier.height(2.dp))
+ Text(
+ stringResource(R.string.empty_user_name),
+ style = MaterialTheme.typography.labelMedium,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+
+ Spacer(Modifier.width(24.dp))
+
+ Text(
+ stringResource(R.string.empty_status),
+ style = MaterialTheme.typography.labelMedium,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index 40504dc..b0f5cc1 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -27,6 +27,7 @@
srcs: [
"src/**/*.kt",
+ ":SystemUI-tests-utils",
],
resource_dirs: [
@@ -45,6 +46,14 @@
"androidx.navigation_navigation-compose",
"androidx.appcompat_appcompat",
+
+ // TODO(b/240431193): Remove the dependencies and depend on
+ // SystemUI-test-utils directly.
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "testables",
+ "truth-prebuilt",
+ "androidx.test.uiautomator",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
index c341867..bb98fb3 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
@@ -13,7 +13,7 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -33,6 +33,28 @@
val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
+ val PeopleEmpty =
+ ChildScreen("people_empty") { navController ->
+ EmptyPeopleScreen(onResult = { navController.popBackStack() })
+ }
+ val PeopleFew =
+ ChildScreen("people_few") { navController ->
+ FewPeopleScreen(onResult = { navController.popBackStack() })
+ }
+ val PeopleFull =
+ ChildScreen("people_full") { navController ->
+ FullPeopleScreen(onResult = { navController.popBackStack() })
+ }
+ val People =
+ ParentScreen(
+ "people",
+ mapOf(
+ "Empty" to PeopleEmpty,
+ "Few" to PeopleFew,
+ "Full" to PeopleFull,
+ )
+ )
+
val Home =
ParentScreen(
"home",
@@ -41,20 +63,21 @@
"Material colors" to MaterialColors,
"Android colors" to AndroidColors,
"Example feature" to ExampleFeature,
+ "People" to People,
)
)
}
/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */
@Composable
-private fun MainContent() {
+private fun MainContent(onControlToggleRequested: () -> Unit) {
Box(Modifier.fillMaxSize()) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = GalleryAppScreens.Home.identifier,
) {
- screen(GalleryAppScreens.Home, navController)
+ screen(GalleryAppScreens.Home, navController, onControlToggleRequested)
}
}
}
@@ -69,7 +92,7 @@
onChangeTheme: () -> Unit,
) {
val systemFontScale = LocalDensity.current.fontScale
- var fontScale: FontScale by remember {
+ var fontScale: FontScale by rememberSaveable {
mutableStateOf(
FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal
)
@@ -87,7 +110,7 @@
}
val systemLayoutDirection = LocalLayoutDirection.current
- var layoutDirection by remember { mutableStateOf(systemLayoutDirection) }
+ var layoutDirection by rememberSaveable { mutableStateOf(systemLayoutDirection) }
val onChangeLayoutDirection = {
layoutDirection =
when (layoutDirection) {
@@ -105,19 +128,24 @@
Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
- Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) {
- ConfigurationControls(
- theme,
- fontScale,
- layoutDirection,
- onChangeTheme,
- onChangeLayoutDirection,
- onChangeFontScale,
- )
+ Column(Modifier.fillMaxSize().systemBarsPadding()) {
+ var showControls by rememberSaveable { mutableStateOf(true) }
- Spacer(Modifier.height(4.dp))
+ if (showControls) {
+ ConfigurationControls(
+ theme,
+ fontScale,
+ layoutDirection,
+ onChangeTheme,
+ onChangeLayoutDirection,
+ onChangeFontScale,
+ Modifier.padding(horizontal = 16.dp),
+ )
- MainContent()
+ Spacer(Modifier.height(4.dp))
+ }
+
+ MainContent(onControlToggleRequested = { showControls = !showControls })
}
}
}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt
new file mode 100644
index 0000000..2f0df77
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.compose.gallery
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import com.android.systemui.people.emptyPeopleSpaceViewModel
+import com.android.systemui.people.fewPeopleSpaceViewModel
+import com.android.systemui.people.fullPeopleSpaceViewModel
+import com.android.systemui.people.ui.compose.PeopleScreen
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+
+@Composable
+fun EmptyPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+ val context = LocalContext.current.applicationContext
+ val viewModel = emptyPeopleSpaceViewModel(context)
+ PeopleScreen(viewModel, onResult)
+}
+
+@Composable
+fun FewPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+ val context = LocalContext.current.applicationContext
+ val viewModel = fewPeopleSpaceViewModel(context)
+ PeopleScreen(viewModel, onResult)
+}
+
+@Composable
+fun FullPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+ val context = LocalContext.current.applicationContext
+ val viewModel = fullPeopleSpaceViewModel(context)
+ PeopleScreen(viewModel, onResult)
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
index 467dac04..d7d0d72 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
@@ -52,17 +52,29 @@
) : Screen(identifier)
/** Create the navigation graph for [screen]. */
-fun NavGraphBuilder.screen(screen: Screen, navController: NavController) {
+fun NavGraphBuilder.screen(
+ screen: Screen,
+ navController: NavController,
+ onControlToggleRequested: () -> Unit,
+) {
when (screen) {
is ChildScreen -> composable(screen.identifier) { screen.content(navController) }
is ParentScreen -> {
val menuRoute = "${screen.identifier}_menu"
navigation(startDestination = menuRoute, route = screen.identifier) {
// The menu to navigate to one of the children screens.
- composable(menuRoute) { ScreenMenu(screen, navController) }
+ composable(menuRoute) {
+ ScreenMenu(screen, navController, onControlToggleRequested)
+ }
// The content of the child screens.
- screen.children.forEach { (_, child) -> screen(child, navController) }
+ screen.children.forEach { (_, child) ->
+ screen(
+ child,
+ navController,
+ onControlToggleRequested,
+ )
+ }
}
}
}
@@ -72,8 +84,27 @@
private fun ScreenMenu(
screen: ParentScreen,
navController: NavController,
+ onControlToggleRequested: () -> Unit,
) {
- LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ LazyColumn(
+ Modifier.padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ item {
+ Surface(
+ Modifier.fillMaxWidth(),
+ color = MaterialTheme.colorScheme.tertiaryContainer,
+ shape = CircleShape,
+ ) {
+ Column(
+ Modifier.clickable(onClick = onControlToggleRequested).padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text("Toggle controls")
+ }
+ }
+ }
+
screen.children.forEach { (name, child) ->
item {
Surface(
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
new file mode 100644
index 0000000..0966c32
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.people
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.drawable.Icon
+import androidx.core.graphics.drawable.toIcon
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** A [PeopleViewModel] that does not have any conversations. */
+fun emptyPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+ return fakePeopleSpaceViewModel(context, emptyList(), emptyList())
+}
+
+/** A [PeopleViewModel] that has a few conversations. */
+fun fewPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+ return fakePeopleSpaceViewModel(
+ context,
+ priorityTiles =
+ listOf(
+ fakeTile(context, id = "0", Color.RED, "Priority"),
+ fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
+ ),
+ recentTiles =
+ listOf(
+ fakeTile(context, id = "2", Color.GREEN, "Recent Important", isImportant = true),
+ fakeTile(context, id = "3", Color.CYAN, "Recent DndBlocking", isDndBlocking = true),
+ ),
+ )
+}
+
+/** A [PeopleViewModel] that has a lot of conversations. */
+fun fullPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+ return fakePeopleSpaceViewModel(
+ context,
+ priorityTiles =
+ listOf(
+ fakeTile(context, id = "0", Color.RED, "Priority"),
+ fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
+ fakeTile(context, id = "2", Color.GREEN, "Priority Important", isImportant = true),
+ fakeTile(
+ context,
+ id = "3",
+ Color.CYAN,
+ "Priority DndBlocking",
+ isDndBlocking = true,
+ ),
+ fakeTile(
+ context,
+ id = "4",
+ Color.MAGENTA,
+ "Priority NewStory Important",
+ hasNewStory = true,
+ isImportant = true,
+ ),
+ ),
+ recentTiles =
+ listOf(
+ fakeTile(
+ context,
+ id = "5",
+ Color.RED,
+ "Recent NewStory DndBlocking",
+ hasNewStory = true,
+ isDndBlocking = true,
+ ),
+ fakeTile(
+ context,
+ id = "6",
+ Color.BLUE,
+ "Recent Important DndBlocking",
+ isImportant = true,
+ isDndBlocking = true,
+ ),
+ fakeTile(
+ context,
+ id = "7",
+ Color.GREEN,
+ "Recent NewStory Important DndBlocking",
+ hasNewStory = true,
+ isImportant = true,
+ isDndBlocking = true,
+ ),
+ fakeTile(context, id = "8", Color.CYAN, "Recent"),
+ fakeTile(context, id = "9", Color.MAGENTA, "Recent"),
+ ),
+ )
+}
+
+private fun fakePeopleSpaceViewModel(
+ @Application context: Context,
+ priorityTiles: List<PeopleTileModel>,
+ recentTiles: List<PeopleTileModel>,
+): PeopleViewModel {
+ return PeopleViewModel(
+ context,
+ FakePeopleTileRepository(priorityTiles, recentTiles),
+ FakePeopleWidgetRepository(),
+ )
+}
+
+private fun fakeTile(
+ @Application context: Context,
+ id: String,
+ iconColor: Int,
+ username: String,
+ hasNewStory: Boolean = false,
+ isImportant: Boolean = false,
+ isDndBlocking: Boolean = false
+): PeopleTileModel {
+ return PeopleTileModel(
+ PeopleTileKey(id, /* userId= */ 0, /* packageName */ ""),
+ username,
+ fakeUserIcon(context, iconColor),
+ hasNewStory,
+ isImportant,
+ isDndBlocking,
+ )
+}
+
+private fun fakeUserIcon(@Application context: Context, color: Int): Icon {
+ val size = context.resources.getDimensionPixelSize(R.dimen.avatar_size_for_medium)
+ val bitmap =
+ Bitmap.createBitmap(
+ size,
+ size,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ val paint = Paint().apply { this.color = color }
+ val radius = size / 2f
+ canvas.drawCircle(/* cx= */ radius, /* cy= */ radius, /* radius= */ radius, paint)
+ return bitmap.toIcon()
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 1008481..1ac1e3a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -17,6 +17,7 @@
import android.graphics.drawable.Drawable
import android.view.View
import com.android.systemui.plugins.annotations.ProvidesInterface
+import com.android.systemui.shared.regionsampling.RegionDarkness
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
@@ -116,12 +117,3 @@
val clockId: ClockId,
val name: String
)
-
-/**
- * Enum for whether clock region is dark or light.
- */
-enum class RegionDarkness(val isDark: Boolean) {
- DEFAULT(false),
- DARK(true),
- LIGHT(false)
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt b/packages/SystemUI/plugin/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
new file mode 100644
index 0000000..344fdb8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.shared.regionsampling
+
+/**
+ * Enum for whether clock region is dark or light.
+ */
+enum class RegionDarkness(val isDark: Boolean) {
+ DEFAULT(false),
+ DARK(true),
+ LIGHT(false)
+}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index babe924..8fa2204 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -37,8 +37,8 @@
<!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's wirelessly charging. [CHAR LIMIT=50] -->
<string name="keyguard_plugged_in_wireless"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging wirelessly</string>
- <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's dock charging. [CHAR LIMIT=50] -->
- <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging Dock</string>
+ <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's charging. [CHAR LIMIT=50] -->
+ <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging</string>
<!-- When the lock screen is showing and the phone plugged in, and the battery
is not fully charged, say that it's charging. -->
@@ -53,7 +53,7 @@
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
- <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging temporarily limited</string>
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string>
<!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. -->
<string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 5f4e310..8ab3e45 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -20,10 +20,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@*android:string/config_clockFontFamily"
- android:includeFontPadding="false"
android:textColor="@android:color/white"
android:format12Hour="@string/dream_time_complication_12_hr_time_format"
android:format24Hour="@string/dream_time_complication_24_hr_time_format"
android:shadowColor="@color/keyguard_shadow_color"
android:shadowRadius="?attr/shadowRadius"
+ android:fontFeatureSettings="pnum, lnum"
+ android:letterSpacing="0.02"
android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1168378..7c1fdd5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1461,7 +1461,7 @@
<dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen>
<!-- Dream overlay complications related dimensions -->
- <dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen>
+ <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen>
<dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
<dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
<dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b844f11..bfdb170 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -884,7 +884,7 @@
<string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
<!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
- <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging Dock • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
+ <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
<!-- Related to user switcher --><skip/>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 8f1959e..a21a78b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -50,7 +50,12 @@
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- private var lastMeasureCall: CharSequence = ""
+ private var lastMeasureCall: CharSequence? = null
+ private var lastDraw: CharSequence? = null
+ private var lastTextUpdate: CharSequence? = null
+ private var lastOnTextChanged: CharSequence? = null
+ private var lastInvalidate: CharSequence? = null
+ private var lastTimeZoneChange: CharSequence? = null
private val time = Calendar.getInstance()
@@ -142,7 +147,6 @@
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
-
// Because the TextLayout may mutate under the hood as a result of the new text, we
// notify the TextAnimator that it may have changed and request a measure/layout. A
// crash will occur on the next invocation of setTextStyle if the layout is mutated
@@ -151,18 +155,19 @@
textAnimator?.updateLayout(layout)
}
requestLayout()
+ lastTextUpdate = getTimestamp()
}
}
fun onTimeZoneChanged(timeZone: TimeZone?) {
time.timeZone = timeZone
refreshFormat()
+ lastTimeZoneChange = "${getTimestamp()} timeZone=${time.timeZone}"
}
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- lastMeasureCall = DateFormat.format(descFormat, System.currentTimeMillis())
val animator = textAnimator
if (animator == null) {
textAnimator = TextAnimator(layout) { invalidate() }
@@ -171,13 +176,34 @@
} else {
animator.updateLayout(layout)
}
+ lastMeasureCall = getTimestamp()
}
override fun onDraw(canvas: Canvas) {
+ lastDraw = getTimestamp()
// intentionally doesn't call super.onDraw here or else the text will be rendered twice
textAnimator?.draw(canvas)
}
+ override fun invalidate() {
+ super.invalidate()
+ lastInvalidate = getTimestamp()
+ }
+
+ private fun getTimestamp(): CharSequence {
+ return "${DateFormat.format("HH:mm:ss", System.currentTimeMillis())} text=$text"
+ }
+
+ override fun onTextChanged(
+ text: CharSequence,
+ start: Int,
+ lengthBefore: Int,
+ lengthAfter: Int
+ ) {
+ super.onTextChanged(text, start, lengthBefore, lengthAfter)
+ lastOnTextChanged = "${getTimestamp()}"
+ }
+
fun setLineSpacingScale(scale: Float) {
lineSpacingScale = scale
setLineSpacing(0f, lineSpacingScale)
@@ -370,7 +396,12 @@
pw.println(" measuredWidth=$measuredWidth")
pw.println(" measuredHeight=$measuredHeight")
pw.println(" singleLineInternal=$isSingleLineInternal")
+ pw.println(" lastTextUpdate=$lastTextUpdate")
+ pw.println(" lastOnTextChanged=$lastOnTextChanged")
+ pw.println(" lastInvalidate=$lastInvalidate")
pw.println(" lastMeasureCall=$lastMeasureCall")
+ pw.println(" lastDraw=$lastDraw")
+ pw.println(" lastTimeZoneChange=$lastTimeZoneChange")
pw.println(" currText=$text")
pw.println(" currTimeContextDesc=$contentDescription")
pw.println(" time=$time")
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 3d72f15..6c49186 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -26,8 +26,8 @@
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.plugins.RegionDarkness
import com.android.systemui.shared.R
+import com.android.systemui.shared.regionsampling.RegionDarkness
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
index deabc27..0146795 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
@@ -18,7 +18,6 @@
import android.graphics.Rect
import android.view.View
import androidx.annotation.VisibleForTesting
-import com.android.systemui.plugins.RegionDarkness
import com.android.systemui.shared.navigationbar.RegionSamplingHelper
import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback
import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2f9bc1c..d752852 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -54,6 +54,7 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -2018,12 +2019,13 @@
// in case authenticators aren't registered yet at this point:
mAuthController.addCallback(new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
+ public void onAllAuthenticatorsRegistered(
+ @BiometricAuthenticator.Modality int modality) {
mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
}
@Override
- public void onEnrollmentsChanged() {
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE));
}
});
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 06e1828..d6974df 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
@@ -29,6 +30,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedStateListDrawable;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Process;
import android.os.VibrationAttributes;
@@ -701,13 +703,17 @@
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
- updateUdfpsConfig();
+ public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsConfig();
+ }
}
@Override
- public void onEnrollmentsChanged() {
- updateUdfpsConfig();
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsConfig();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 47ff59c..282f251 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -46,6 +46,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -156,25 +157,6 @@
}
};
- private final IFingerprintAuthenticatorsRegisteredCallback
- mFingerprintAuthenticatorsRegisteredCallback =
- new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
- @Override
- public void onAllAuthenticatorsRegistered(
- List<FingerprintSensorPropertiesInternal> sensors) {
- mHandler.post(() -> handleAllFingerprintAuthenticatorsRegistered(sensors));
- }
- };
-
- private final BiometricStateListener mBiometricStateListener =
- new BiometricStateListener() {
- @Override
- public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
- mHandler.post(
- () -> handleEnrollmentsChanged(userId, sensorId, hasEnrollments));
- }
- };
-
@VisibleForTesting
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -249,8 +231,8 @@
List<FingerprintSensorPropertiesInternal> sensors) {
mExecution.assertIsMainThread();
if (DEBUG) {
- Log.d(TAG, "handleAllAuthenticatorsRegistered | sensors: " + Arrays.toString(
- sensors.toArray()));
+ Log.d(TAG, "handleAllFingerprintAuthenticatorsRegistered | sensors: "
+ + Arrays.toString(sensors.toArray()));
}
mAllFingerprintAuthenticatorsRegistered = true;
mFpProps = sensors;
@@ -292,15 +274,42 @@
mSidefpsController = mSidefpsControllerFactory.get();
}
- mFingerprintManager.registerBiometricStateListener(mBiometricStateListener);
+ mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mHandler.post(() -> handleEnrollmentsChanged(
+ TYPE_FINGERPRINT, userId, sensorId, hasEnrollments));
+ }
+ });
updateFingerprintLocation();
for (Callback cb : mCallbacks) {
- cb.onAllAuthenticatorsRegistered();
+ cb.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
}
}
- private void handleEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ private void handleAllFaceAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+ mExecution.assertIsMainThread();
+ if (DEBUG) {
+ Log.d(TAG, "handleAllFaceAuthenticatorsRegistered | sensors: " + Arrays.toString(
+ sensors.toArray()));
+ }
+
+ mFaceManager.registerBiometricStateListener(new BiometricStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ mHandler.post(() -> handleEnrollmentsChanged(
+ TYPE_FACE, userId, sensorId, hasEnrollments));
+ }
+ });
+
+ for (Callback cb : mCallbacks) {
+ cb.onAllAuthenticatorsRegistered(TYPE_FACE);
+ }
+ }
+
+ private void handleEnrollmentsChanged(@Modality int modality, int userId, int sensorId,
+ boolean hasEnrollments) {
mExecution.assertIsMainThread();
Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId
+ ", hasEnrollments: " + hasEnrollments);
@@ -314,7 +323,7 @@
}
}
for (Callback cb : mCallbacks) {
- cb.onEnrollmentsChanged();
+ cb.onEnrollmentsChanged(modality);
}
}
@@ -700,7 +709,26 @@
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
- mFingerprintAuthenticatorsRegisteredCallback);
+ new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ mHandler.post(() ->
+ handleAllFingerprintAuthenticatorsRegistered(sensors));
+ }
+ });
+ }
+ if (mFaceManager != null) {
+ mFaceManager.addAuthenticatorsRegisteredCallback(
+ new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ mHandler.post(() ->
+ handleAllFaceAuthenticatorsRegistered(sensors));
+ }
+ }
+ );
}
mStableDisplaySize = mDisplayManager.getStableDisplaySize();
@@ -1116,13 +1144,13 @@
* Called when authenticators are registered. If authenticators are already
* registered before this call, this callback will never be triggered.
*/
- default void onAllAuthenticatorsRegistered() {}
+ default void onAllAuthenticatorsRegistered(@Modality int modality) {}
/**
- * Called when UDFPS enrollments have changed. This is called after boot and on changes to
+ * Called when enrollments have changed. This is called after boot and on changes to
* enrollment.
*/
- default void onEnrollmentsChanged() {}
+ default void onEnrollmentsChanged(@Modality int modality) {}
/**
* Called when the biometric prompt starts showing.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 38fab8f..fd3f600 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -308,7 +308,7 @@
private val authControllerCallback =
object : AuthController.Callback {
- override fun onAllAuthenticatorsRegistered() {
+ override fun onAllAuthenticatorsRegistered(modality: Int) {
updateUdfpsDependentParams()
updateSensorLocation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index cf50f7f..d1589b2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -517,8 +517,6 @@
scaledMajor);
Log.v(TAG, "onTouch | finger down: " + touchInfo);
mTouchLogTime = mSystemClock.elapsedRealtime();
- mPowerManager.userActivity(mSystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
handled = true;
} else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -846,6 +844,9 @@
return;
}
mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+ // Refresh screen timeout and boost process priority if possible.
+ mPowerManager.userActivity(mSystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
if (!mOnFingerDown) {
playStartHaptic();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 49e378e..d96476f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -99,12 +99,11 @@
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+ mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
if (!mIsAccessibilityEnabled) {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
- mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
} else {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
- mOnFirstBucketFailedColor = mHelpColor;
}
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
mCheckmarkDrawable.mutate();
@@ -167,7 +166,8 @@
}
private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
- if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
+ if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps
+ && mShowingHelp == showingHelp) {
return;
}
@@ -197,6 +197,7 @@
}
}
+ mShowingHelp = showingHelp;
mRemainingSteps = remainingSteps;
mTotalSteps = totalSteps;
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 47ea27f..0839338 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,6 +30,8 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.core.graphics.ColorUtils;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -42,7 +44,8 @@
* @hide
*/
final class WirelessChargingLayout extends FrameLayout {
- private static final long RIPPLE_ANIMATION_DURATION = 1500;
+ private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
+ private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750;
private static final int SCRIM_COLOR = 0x4C000000;
private static final int SCRIM_FADE_DURATION = 300;
private RippleView mRippleView;
@@ -131,17 +134,30 @@
"backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
- scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION);
+ scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
+ ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
+ - SCRIM_FADE_DURATION);
AnimatorSet animatorSetScrim = new AnimatorSet();
animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
mRippleView.setupShader(rippleShape);
+ if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
+ mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
+ mRippleView.setSparkleStrength(0.22f);
+ int color = Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor();
+ mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28));
+ } else {
+ mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
+ mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor());
+ }
+
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
- mRippleView.setDuration(RIPPLE_ANIMATION_DURATION);
mRippleView.startRipple();
mRippleView.removeOnAttachStateChangeListener(this);
}
@@ -232,13 +248,13 @@
int height = getMeasuredHeight();
mRippleView.setCenter(width * 0.5f, height * 0.5f);
if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
- mRippleView.setMaxSize(width * 1.5f, height * 1.5f);
+ // Those magic numbers are introduced for visual polish. This aspect ratio maps with
+ // the tablet's docking station.
+ mRippleView.setMaxSize(width * 1.36f, height * 1.46f);
} else {
float maxSize = Math.max(width, height);
mRippleView.setMaxSize(maxSize, maxSize);
}
- mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
- android.R.attr.colorAccent).getDefaultColor());
}
super.onLayout(changed, left, top, right, bottom);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 5dff4a5..4157728 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -26,6 +26,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardViewController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
@@ -61,6 +62,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -191,7 +193,9 @@
GroupMembershipManager groupManager,
VisualStabilityProvider visualStabilityProvider,
ConfigurationController configurationController,
- @Main Handler handler) {
+ @Main Handler handler,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ UiEventLogger uiEventLogger) {
return new HeadsUpManagerPhone(
context,
headsUpManagerLogger,
@@ -200,7 +204,9 @@
groupManager,
visualStabilityProvider,
configurationController,
- handler
+ handler,
+ accessibilityManagerWrapper,
+ uiEventLogger
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f32ea35..2dadf57 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -18,6 +18,7 @@
import android.app.INotificationManager;
import android.content.Context;
+import android.service.dreams.IDreamManager;
import androidx.annotation.Nullable;
@@ -213,6 +214,7 @@
ShadeController shadeController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
+ IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -230,6 +232,7 @@
shadeController,
statusBarService,
notificationManager,
+ dreamManager,
visibilityProvider,
interruptionStateProvider,
zenModeController,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index a9e310d..7da2cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,12 +16,15 @@
package com.android.systemui.doze;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
@@ -232,13 +235,17 @@
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
- updateUdfpsController();
+ public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsController();
+ }
}
@Override
- public void onEnrollmentsChanged() {
- updateUdfpsController();
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsController();
+ }
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index da6c163..997a6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
@@ -29,6 +31,7 @@
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
+import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.display.AmbientDisplayConfiguration;
import android.net.Uri;
import android.os.Handler;
@@ -835,13 +838,17 @@
private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
@Override
- public void onAllAuthenticatorsRegistered() {
- updateUdfpsEnrolled();
+ public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsEnrolled();
+ }
}
@Override
- public void onEnrollmentsChanged() {
- updateUdfpsEnrolled();
+ public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+ if (modality == TYPE_FINGERPRINT) {
+ updateUdfpsEnrolled();
+ }
}
private void updateUdfpsEnrolled() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
index 5250d44..7d9f105 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
@@ -37,7 +37,7 @@
public interface DreamClockTimeComplicationModule {
String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
String TAG_WEIGHT = "'wght' ";
- int WEIGHT = 200;
+ int WEIGHT = 400;
/**
* Provides the complication view.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 6eb77bd..1f356cb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -92,7 +92,7 @@
* Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
* one.
*/
- public static final UnreleasedFlag MODERN_BOTTOM_AREA = new UnreleasedFlag(206, true);
+ public static final ReleasedFlag MODERN_BOTTOM_AREA = new ReleasedFlag(206, true);
public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207);
@@ -151,8 +151,8 @@
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
- public static final UnreleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
- new UnreleasedFlag(603, false);
+ public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
+ new ReleasedFlag(603, false);
public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE = new UnreleasedFlag(604, true);
@@ -182,6 +182,7 @@
public static final UnreleasedFlag MEDIA_SESSION_ACTIONS = new UnreleasedFlag(901);
public static final ReleasedFlag MEDIA_NEARBY_DEVICES = new ReleasedFlag(903);
public static final ReleasedFlag MEDIA_MUTE_AWAIT = new ReleasedFlag(904);
+ public static final UnreleasedFlag MEDIA_DREAM_COMPLICATION = new UnreleasedFlag(905);
// 1000 - dock
public static final ReleasedFlag SIMULATE_DOCK_THROUGH_CHARGING =
@@ -236,6 +237,10 @@
// 1300 - screenshots
public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
+ public static final UnreleasedFlag SCREENSHOT_WORK_PROFILE_POLICY = new UnreleasedFlag(1301);
+
+ // 1400 - columbus, b/242800729
+ public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ca65d12..da5819a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -90,6 +90,8 @@
import android.widget.LinearLayout;
import android.widget.ListPopupWindow;
import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
@@ -155,6 +157,8 @@
public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
+ private static final boolean DEBUG = false;
+
private static final String TAG = "GlobalActionsDialogLite";
private static final String INTERACTION_JANK_TAG = "global_actions";
@@ -2177,6 +2181,11 @@
protected ViewGroup mContainer;
+ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+ logOnBackInvocation();
+ dismiss();
+ };
+
@VisibleForTesting
protected GestureDetector.SimpleOnGestureListener mGestureListener =
new GestureDetector.SimpleOnGestureListener() {
@@ -2221,6 +2230,16 @@
}
};
+
+ // this exists so that we can point it to a mock during Unit Testing
+ private OnBackInvokedDispatcher mOverriddenBackDispatcher;
+
+ // the following method exists so that a Unit Test can supply a `OnBackInvokedDispatcher`
+ @VisibleForTesting
+ void setBackDispatcherOverride(OnBackInvokedDispatcher mockDispatcher) {
+ mOverriddenBackDispatcher = mockDispatcher;
+ }
+
ActionsDialogLite(Context context, int themeRes, MyAdapter adapter,
MyOverflowAdapter overflowAdapter,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
@@ -2254,6 +2273,22 @@
super.onCreate(savedInstanceState);
initializeLayout();
mWindowDimAmount = getWindow().getAttributes().dimAmount;
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+ if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler registered");
+ }
+
+ @VisibleForTesting
+ @Override
+ public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+ if (mOverriddenBackDispatcher != null) return mOverriddenBackDispatcher;
+ else return super.getOnBackInvokedDispatcher();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+ if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler unregistered");
}
@Override
@@ -2453,7 +2488,12 @@
@Override
public void onBackPressed() {
super.onBackPressed();
+ logOnBackInvocation();
+ }
+
+ private void logOnBackInvocation() {
mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_BACK);
+ if (DEBUG) Log.d(TAG, "onBack invoked");
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 3eb3c80..6dfbd42 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -323,6 +323,8 @@
if (sEnableRemoteKeyguardOccludeAnimation) {
Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
// Register for occluding
+ final RemoteTransition occludeTransition = new RemoteTransition(
+ mOccludeAnimation, getIApplicationThread());
TransitionFilter f = new TransitionFilter();
f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
f.mRequirements = new TransitionFilter.Requirement[]{
@@ -337,10 +339,11 @@
f.mRequirements[1].mMustBeIndependent = false;
f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- mShellTransitions.registerRemote(f,
- new RemoteTransition(mOccludeAnimation, getIApplicationThread()));
+ mShellTransitions.registerRemote(f, occludeTransition);
// Now register for un-occlude.
+ final RemoteTransition unoccludeTransition = new RemoteTransition(
+ mUnoccludeAnimation, getIApplicationThread());
f = new TransitionFilter();
f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
f.mRequirements = new TransitionFilter.Requirement[]{
@@ -358,8 +361,23 @@
f.mRequirements[0].mMustBeIndependent = false;
f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- mShellTransitions.registerRemote(f,
- new RemoteTransition(mUnoccludeAnimation, getIApplicationThread()));
+ mShellTransitions.registerRemote(f, unoccludeTransition);
+
+ // Register for specific transition type.
+ // Above filter cannot fulfill all conditions.
+ // E.g. close top activity while screen off but next activity is occluded, this should
+ // an occluded transition, but since the activity is invisible, the condition would
+ // match unoccluded transition.
+ // But on the contrary, if we add above condition in occluded transition, then when user
+ // trying to dismiss occluded activity when unlock keyguard, the condition would match
+ // occluded transition.
+ f = new TransitionFilter();
+ f.mTypeSet = new int[]{TRANSIT_KEYGUARD_OCCLUDE};
+ mShellTransitions.registerRemote(f, occludeTransition);
+
+ f = new TransitionFilter();
+ f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE};
+ mShellTransitions.registerRemote(f, unoccludeTransition);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b4f40e2..4a7346e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2377,10 +2377,10 @@
private void handleHide() {
Trace.beginSection("KeyguardViewMediator#handleHide");
- // It's possible that the device was unlocked in a dream state. It's time to wake up.
- if (mAodShowing || mDreamOverlayShowing) {
- PowerManager pm = mContext.getSystemService(PowerManager.class);
- pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ // It's possible that the device was unlocked (via BOUNCER) while dozing. It's time to
+ // wake up.
+ if (mAodShowing) {
+ mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"com.android.systemui:BOUNCER_DOZING");
}
@@ -2409,6 +2409,13 @@
null /* nonApps */, null /* finishedCallback */);
});
}
+
+ // It's possible that the device was unlocked (via BOUNCER or Fingerprint) while
+ // dreaming. It's time to wake up.
+ if (mDreamOverlayShowing) {
+ mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+ "com.android.systemui:UNLOCK_DREAMING");
+ }
}
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c858bc3..c2a8764 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -84,6 +84,14 @@
return factory.create("LSShadeTransitionLog", 50);
}
+ /** Provides a logging buffer for Shade messages. */
+ @Provides
+ @SysUISingleton
+ @ShadeLog
+ public static LogBuffer provideShadeLogBuffer(LogBufferFactory factory) {
+ return factory.create("ShadeLog", 500, false);
+ }
+
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
@@ -262,7 +270,7 @@
@SysUISingleton
@StatusBarConnectivityLog
public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
- return factory.create("StatusBarConnectivityLog", 64);
+ return factory.create("SbConnectivity", 64);
}
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
similarity index 60%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index 8dde897..bd0d298 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -14,10 +14,20 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.systemui.log.dagger;
-import android.content.pm.ApplicationInfo
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
-interface AppRecord {
- val app: ApplicationInfo
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for Shade touch handling messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeLog {
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 237b505..32600fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -18,19 +18,25 @@
import android.content.Context
import android.content.res.Configuration
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.dagger.MediaModule.KEYGUARD
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.MediaContainerView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.LargeScreenUtils
+import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
import javax.inject.Named
@@ -43,9 +49,10 @@
@param:Named(KEYGUARD) private val mediaHost: MediaHost,
private val bypassController: KeyguardBypassController,
private val statusBarStateController: SysuiStatusBarStateController,
- private val notifLockscreenUserManager: NotificationLockscreenUserManager,
private val context: Context,
- configurationController: ConfigurationController
+ private val secureSettings: SecureSettings,
+ @Main private val handler: Handler,
+ configurationController: ConfigurationController,
) {
init {
@@ -60,6 +67,24 @@
}
})
+ val settingsObserver: ContentObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ if (uri == lockScreenMediaPlayerUri) {
+ allowMediaPlayerOnLockScreen =
+ secureSettings.getBoolForUser(
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ true,
+ UserHandle.USER_CURRENT
+ )
+ refreshMediaPosition()
+ }
+ }
+ }
+ secureSettings.registerContentObserverForUser(
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ settingsObserver,
+ UserHandle.USER_ALL)
+
// First let's set the desired state that we want for this host
mediaHost.expansion = MediaHostState.EXPANDED
mediaHost.showsOnlyActiveMedia = true
@@ -101,6 +126,13 @@
private var splitShadeContainer: ViewGroup? = null
/**
+ * Track the media player setting status on lock screen.
+ */
+ private var allowMediaPlayerOnLockScreen: Boolean = true
+ private val lockScreenMediaPlayerUri =
+ secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+
+ /**
* Attaches media container in single pane mode, situated at the top of the notifications list
*/
fun attachSinglePaneContainer(mediaView: MediaContainerView?) {
@@ -164,7 +196,7 @@
visible = mediaHost.visible &&
!bypassController.bypassEnabled &&
keyguardOrUserSwitcher &&
- notifLockscreenUserManager.shouldShowLockscreenNotifications()
+ allowMediaPlayerOnLockScreen
if (visible) {
showMediaPlayer()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 88a1b17..7b497ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media
import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
@@ -27,6 +28,7 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
@@ -57,8 +59,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
@@ -633,9 +635,14 @@
}
val mediaController = mediaControllerFactory.create(token)
val metadata = mediaController.metadata
+ val notif: Notification = sbn.notification
+
+ val appInfo = notif.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ ) ?: getAppInfoFromPackage(sbn.packageName)
// Album art
- val notif: Notification = sbn.notification
var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
if (artworkBitmap == null) {
artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
@@ -650,8 +657,7 @@
}
// App name
- val builder = Notification.Builder.recoverBuilder(context, notif)
- val app = builder.loadHeaderAppName()
+ val appName = getAppName(sbn, appInfo)
// App Icon
val smallIcon = sbn.notification.smallIcon
@@ -712,12 +718,7 @@
val currentEntry = mediaEntries.get(key)
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
- val appUid = try {
- context.packageManager.getApplicationInfo(sbn.packageName, 0)?.uid!!
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Could not get app UID for ${sbn.packageName}", e)
- Process.INVALID_UID
- }
+ val appUid = appInfo?.uid ?: Process.INVALID_UID
if (logEvent) {
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -730,7 +731,7 @@
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
val active = mediaEntries[key]?.active ?: true
- onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, app,
+ onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, appName,
smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
semanticActions, sbn.packageName, token, notif.contentIntent, device,
active, resumeAction = resumeAction, playbackLocation = playbackLocation,
@@ -740,6 +741,28 @@
}
}
+ private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+ try {
+ return context.packageManager.getApplicationInfo(packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app info for $packageName", e)
+ }
+ return null
+ }
+
+ private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+ val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+ if (name != null) {
+ return name
+ }
+
+ return if (appInfo != null) {
+ context.packageManager.getApplicationLabel(appInfo).toString()
+ } else {
+ sbn.packageName
+ }
+ }
+
/**
* Generate action buttons based on notification actions
*/
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 458ed40..ae4c7c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -22,7 +22,12 @@
import android.annotation.IntDef
import android.content.Context
import android.content.res.Configuration
+import android.database.ContentObserver
import android.graphics.Rect
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
import android.util.Log
import android.util.MathUtils
import android.view.View
@@ -33,11 +38,12 @@
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotifPanelEvents
import com.android.systemui.statusbar.CrossFadeHelper
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -46,6 +52,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.traceSection
import javax.inject.Inject
@@ -84,14 +91,31 @@
private val keyguardStateController: KeyguardStateController,
private val bypassController: KeyguardBypassController,
private val mediaCarouselController: MediaCarouselController,
- private val notifLockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardViewController: KeyguardViewController,
+ private val dreamOverlayStateController: DreamOverlayStateController,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- private val keyguardViewController: KeyguardViewController,
- private val dreamOverlayStateController: DreamOverlayStateController
+ panelEventsEvents: NotifPanelEvents,
+ private val secureSettings: SecureSettings,
+ @Main private val handler: Handler,
) {
/**
+ * Track the media player setting status on lock screen.
+ */
+ private var allowMediaPlayerOnLockScreen: Boolean = true
+ private val lockScreenMediaPlayerUri =
+ secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN)
+
+ /**
+ * Whether we "skip" QQS during panel expansion.
+ *
+ * This means that when expanding the panel we go directly to QS. Also when we are on QS and
+ * start closing the panel, it fully collapses instead of going to QQS.
+ */
+ private var skipQqsOnExpansion: Boolean = false
+
+ /**
* The root overlay of the hierarchy. This is where the media notification is attached to
* whenever the view is transitioning from one host to another. It also make sure that the
* view is always in its final state when it is attached to a view host.
@@ -504,6 +528,30 @@
mediaCarouselController.updateUserVisibility = {
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
+
+ panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
+ override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
+ skipQqsOnExpansion = isExpandImmediateEnabled
+ updateDesiredLocation()
+ }
+ })
+
+ val settingsObserver: ContentObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ if (uri == lockScreenMediaPlayerUri) {
+ allowMediaPlayerOnLockScreen =
+ secureSettings.getBoolForUser(
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ true,
+ UserHandle.USER_CURRENT
+ )
+ }
+ }
+ }
+ secureSettings.registerContentObserverForUser(
+ Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
+ settingsObserver,
+ UserHandle.USER_ALL)
}
private fun updateConfiguration() {
@@ -701,6 +749,9 @@
if (isCurrentlyInGuidedTransformation()) {
return false
}
+ if (skipQqsOnExpansion) {
+ return false
+ }
// This is an invalid transition, and can happen when using the camera gesture from the
// lock screen. Disallow.
if (previousLocation == LOCATION_LOCKSCREEN &&
@@ -852,6 +903,9 @@
* otherwise
*/
private fun getTransformationProgress(): Float {
+ if (skipQqsOnExpansion) {
+ return -1.0f
+ }
val progress = getQSTransformationProgress()
if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
return progress
@@ -1013,7 +1067,6 @@
}
val onLockscreen = (!bypassController.bypassEnabled &&
(statusbarState == StatusBarState.KEYGUARD))
- val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
val location = when {
dreamOverlayActive -> LOCATION_DREAM_OVERLAY
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
@@ -1021,7 +1074,7 @@
!hasActiveMedia -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
- onLockscreen && allowedOnLockscreen -> LOCATION_LOCKSCREEN
+ onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
// When we're on lock screen and the player is not active, we should keep it in QS.
@@ -1042,6 +1095,10 @@
// reattach it without an animation
return LOCATION_LOCKSCREEN
}
+ if (skipQqsOnExpansion) {
+ // When doing an immediate expand or collapse, we want to keep it in QS.
+ return LOCATION_QS
+ }
return location
}
@@ -1089,7 +1146,7 @@
return !statusBarStateController.isDozing &&
!keyguardViewController.isBouncerShowing &&
statusBarStateController.state == StatusBarState.KEYGUARD &&
- notifLockscreenUserManager.shouldShowLockscreenNotifications() &&
+ allowMediaPlayerOnLockScreen &&
statusBarStateController.isExpanded &&
!qsExpanded
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7e263d8..08cf57c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -296,27 +296,17 @@
mMediaOutputController.setRefreshing(true);
// Update header icon
final int iconRes = getHeaderIconRes();
- final IconCompat iconCompat = getHeaderIcon();
- final Drawable appSourceDrawable = getAppSourceIcon();
+ final IconCompat headerIcon = getHeaderIcon();
+ final IconCompat appSourceIcon = getAppSourceIcon();
boolean colorSetUpdated = false;
mCastAppLayout.setVisibility(
mMediaOutputController.shouldShowLaunchSection()
? View.VISIBLE : View.GONE);
- if (appSourceDrawable != null) {
- mAppResourceIcon.setImageDrawable(appSourceDrawable);
- mAppButton.setCompoundDrawablesWithIntrinsicBounds(resizeDrawable(appSourceDrawable,
- mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_app_tier_icon_size
- )),
- null, null, null);
- } else {
- mAppResourceIcon.setVisibility(View.GONE);
- }
if (iconRes != 0) {
mHeaderIcon.setVisibility(View.VISIBLE);
mHeaderIcon.setImageResource(iconRes);
- } else if (iconCompat != null) {
- Icon icon = iconCompat.toIcon(mContext);
+ } else if (headerIcon != null) {
+ Icon icon = headerIcon.toIcon(mContext);
if (icon.getType() != Icon.TYPE_BITMAP && icon.getType() != Icon.TYPE_ADAPTIVE_BITMAP) {
// icon doesn't support getBitmap, use default value for color scheme
updateButtonBackgroundColorFilter();
@@ -336,6 +326,18 @@
} else {
mHeaderIcon.setVisibility(View.GONE);
}
+ if (appSourceIcon != null) {
+ Icon appIcon = appSourceIcon.toIcon(mContext);
+ mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent());
+ mAppResourceIcon.setImageIcon(appIcon);
+ } else {
+ Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage();
+ if (appIconDrawable != null) {
+ mAppResourceIcon.setImageDrawable(appIconDrawable);
+ } else {
+ mAppResourceIcon.setVisibility(View.GONE);
+ }
+ }
if (mHeaderIcon.getVisibility() == View.VISIBLE) {
final int size = getHeaderIconSize();
final int padding = mContext.getResources().getDimensionPixelSize(
@@ -480,7 +482,7 @@
}
}
- abstract Drawable getAppSourceIcon();
+ abstract IconCompat getAppSourceIcon();
abstract int getHeaderIconRes();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 310469d..35baf013 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -19,7 +19,6 @@
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
@@ -116,8 +115,8 @@
}
@Override
- Drawable getAppSourceIcon() {
- return mMediaOutputController.getAppSourceIcon();
+ IconCompat getAppSourceIcon() {
+ return mMediaOutputController.getNotificationSmallIcon();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index 0fa3265..2b5d6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog
+import android.app.KeyguardManager
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
@@ -45,7 +46,8 @@
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager
+ private val powerExemptionManager: PowerExemptionManager,
+ private val keyGuardManager: KeyguardManager
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -57,7 +59,7 @@
val controller = MediaOutputController(context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager)
+ powerExemptionManager, keyGuardManager)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 27095b3..f7d80e0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -20,6 +20,7 @@
import android.annotation.CallbackExecutor;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.WallpaperColors;
import android.bluetooth.BluetoothLeBroadcast;
@@ -120,6 +121,7 @@
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
private final PowerExemptionManager mPowerExemptionManager;
+ private final KeyguardManager mKeyGuardManager;
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
@@ -154,7 +156,8 @@
DialogLaunchAnimator dialogLaunchAnimator,
Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
AudioManager audioManager,
- PowerExemptionManager powerExemptionManager) {
+ PowerExemptionManager powerExemptionManager,
+ KeyguardManager keyGuardManager) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -163,6 +166,7 @@
mNotifCollection = notifCollection;
mAudioManager = audioManager;
mPowerExemptionManager = powerExemptionManager;
+ mKeyGuardManager = keyGuardManager;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -300,7 +304,7 @@
}
}
- Drawable getAppSourceIcon() {
+ Drawable getAppSourceIconFromPackage() {
if (mPackageName.isEmpty()) {
return null;
}
@@ -411,6 +415,24 @@
|| isSelectedDeviceInGroup;
}
+ IconCompat getNotificationSmallIcon() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ return null;
+ }
+ for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification.isMediaNotification()
+ && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+ final Icon icon = notification.getSmallIcon();
+ if (icon == null) {
+ break;
+ }
+ return IconCompat.createFromIcon(icon);
+ }
+ }
+ return null;
+ }
+
IconCompat getNotificationIcon() {
if (TextUtils.isEmpty(mPackageName)) {
return null;
@@ -701,7 +723,8 @@
ActivityLaunchAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
- if (controller == null) {
+ if (controller == null || (mKeyGuardManager != null
+ && mKeyGuardManager.isKeyguardLocked())) {
mCallback.dismissDialog();
}
@@ -753,7 +776,7 @@
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
- mAudioManager, mPowerExemptionManager);
+ mAudioManager, mPowerExemptionManager, mKeyGuardManager);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 9fb96b5..cb6f5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -17,7 +17,6 @@
package com.android.systemui.media.dialog;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
@@ -81,8 +80,8 @@
}
@Override
- Drawable getAppSourceIcon() {
- return mMediaOutputController.getAppSourceIcon();
+ IconCompat getAppSourceIcon() {
+ return mMediaOutputController.getNotificationSmallIcon();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 8249a7c..543efed 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog
+import android.app.KeyguardManager
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
@@ -47,7 +48,8 @@
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager
+ private val powerExemptionManager: PowerExemptionManager,
+ private val keyGuardManager: KeyguardManager
) {
companion object {
private const val INTERACTION_JANK_TAG = "media_output"
@@ -63,7 +65,7 @@
context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager)
+ powerExemptionManager, keyGuardManager)
val dialog =
MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index e077fed..c544871 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dream;
+import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+
import android.content.Context;
import androidx.annotation.NonNull;
@@ -23,6 +25,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaData;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.SmartspaceMediaData;
@@ -34,7 +37,7 @@
* the media complication as appropriate
*/
public class MediaDreamSentinel extends CoreStartable {
- private MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
+ private final MediaDataManager.Listener mListener = new MediaDataManager.Listener() {
private boolean mAdded;
@Override
public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {
@@ -63,6 +66,10 @@
public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey,
@NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency,
boolean isSsReactivated) {
+ if (!mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)) {
+ return;
+ }
+
if (mAdded) {
return;
}
@@ -79,15 +86,18 @@
private final MediaDataManager mMediaDataManager;
private final DreamOverlayStateController mDreamOverlayStateController;
private final MediaDreamComplication mComplication;
+ private final FeatureFlags mFeatureFlags;
@Inject
public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager,
DreamOverlayStateController dreamOverlayStateController,
- MediaDreamComplication complication) {
+ MediaDreamComplication complication,
+ FeatureFlags featureFlags) {
super(context);
mMediaDataManager = mediaDataManager;
mDreamOverlayStateController = dreamOverlayStateController;
mComplication = complication;
+ mFeatureFlags = featureFlags;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 9ab83b8..2278938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -56,7 +56,7 @@
internal val logger: MediaTttLogger,
internal val windowManager: WindowManager,
private val viewUtil: ViewUtil,
- @Main internal val mainExecutor: DelayableExecutor,
+ @Main private val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index d3b5bc6..aa10f7e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,7 +18,6 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaTttSenderLogBuffer
/**
* A logger for media tap-to-transfer events.
@@ -27,7 +26,7 @@
*/
class MediaTttLogger(
private val deviceTypeTag: String,
- @MediaTttSenderLogBuffer private val buffer: LogBuffer
+ private val buffer: LogBuffer
){
/** Logs a change in the chip state for the given [mediaRouteId]. */
fun logStateChange(stateName: String, mediaRouteId: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 92d9ea8..9335489 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -160,12 +160,8 @@
duration = ANIMATION_DURATION,
includeMargins = true,
includeFadeIn = true,
- )
-
- // We can only request focus once the animation finishes.
- mainExecutor.executeDelayed(
- { chipInnerView.requestAccessibilityFocus() },
- ANIMATION_DURATION
+ // We can only request focus once the animation finishes.
+ onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
index 0834a5a..e27bfb3 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -31,7 +31,6 @@
import com.android.systemui.people.data.repository.PeopleTileRepository
import com.android.systemui.people.data.repository.PeopleWidgetRepository
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -52,7 +51,7 @@
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
*/
private val _priorityTiles = MutableStateFlow(priorityTiles())
- val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
+ val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
/**
* The list of the priority tiles/conversations.
@@ -61,7 +60,7 @@
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
*/
private val _recentTiles = MutableStateFlow(recentTiles())
- val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
+ val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
/** The ID of the widget currently being edited/added. */
private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 833573d..be44202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -39,6 +39,7 @@
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/** Controller for {@link QuickQSPanel}. */
@QSScope
@@ -52,20 +53,21 @@
}
};
- private final boolean mUsingCollapsedLandscapeMedia;
+ private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
@Inject
QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
- @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia,
+ @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+ Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
- mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia;
+ mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
@Override
@@ -80,7 +82,8 @@
int rotation = getRotation();
boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE
|| rotation == RotationUtils.ROTATION_SEASCAPE;
- if (!mUsingCollapsedLandscapeMedia || !isLandscape) {
+ boolean usingCollapsedLandscapeMedia = mUsingCollapsedLandscapeMediaProvider.get();
+ if (!usingCollapsedLandscapeMedia || !isLandscape) {
mMediaHost.setExpansion(MediaHost.EXPANDED);
} else {
mMediaHost.setExpansion(MediaHost.COLLAPSED);
@@ -126,7 +129,6 @@
super.setTiles(tiles, /* collapsedView */ true);
}
- /** */
public void setContentMargins(int marginStart, int marginEnd) {
mView.setContentMargins(marginStart, marginEnd, mMediaHost.getHostView());
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 56a1874..db7c1fd 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -67,7 +67,7 @@
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
@@ -83,7 +83,7 @@
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
@@ -99,7 +99,7 @@
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 8b01201..60c8f37 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -81,6 +81,7 @@
rippleShader.color = RIPPLE_DEFAULT_COLOR
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+ rippleShader.pixelDensity = resources.displayMetrics.density
ripplePaint.shader = rippleShader
}
@@ -124,6 +125,13 @@
rippleShader.rippleFill = rippleFill
}
+ /**
+ * Set the intensity of the sparkles.
+ */
+ fun setSparkleStrength(strength: Float) {
+ rippleShader.sparkleStrength = strength
+ }
+
override fun onDraw(canvas: Canvas?) {
if (canvas == null || !canvas.isHardwareAccelerated) {
// Drawing with the ripple shader requires hardware acceleration, so skip
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
similarity index 60%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
index 8dde897..f7c4dad 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
+/**
+ * Copyright (c) 2009, 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
+ * 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,
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.systemui.screenshot;
-import android.content.pm.ApplicationInfo
+/** Interface implemented by ScreenshotProxyService */
+interface IScreenshotProxy {
-interface AppRecord {
- val app: ApplicationInfo
-}
+ /** Is the notification shade currently exanded? */
+ boolean isNotificationShadeExpanded();
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
index 39f35a5..7779760 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
@@ -22,5 +22,5 @@
fun captureDisplay(displayId: Int, crop: Rect? = null): Bitmap?
- fun captureTask(taskId: Int): Bitmap?
+ suspend fun captureTask(taskId: Int): Bitmap?
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
index 258c436..246265b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
@@ -27,13 +27,19 @@
import android.view.SurfaceControl.DisplayCaptureArgs
import android.view.SurfaceControl.ScreenshotHardwareBuffer
import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
private const val TAG = "ImageCaptureImpl"
+@SysUISingleton
open class ImageCaptureImpl @Inject constructor(
private val displayManager: DisplayManager,
- private val atmService: IActivityTaskManager
+ private val atmService: IActivityTaskManager,
+ @Background private val bgContext: CoroutineDispatcher
) : ImageCapture {
override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
@@ -46,8 +52,8 @@
return buffer?.asBitmap()
}
- override fun captureTask(taskId: Int): Bitmap? {
- val snapshot = atmService.takeTaskSnapshot(taskId)
+ override suspend fun captureTask(taskId: Int): Bitmap? {
+ val snapshot = withContext(bgContext) { atmService.takeTaskSnapshot(taskId) } ?: return null
return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
}
@@ -67,12 +73,17 @@
}
@VisibleForTesting
- open fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect): ScreenshotHardwareBuffer? {
- val captureArgs = DisplayCaptureArgs.Builder(displayToken)
- .setSize(width, height)
- .setSourceCrop(crop)
- .build()
+ open fun captureDisplay(
+ displayToken: IBinder,
+ width: Int,
+ height: Int,
+ crop: Rect
+ ): ScreenshotHardwareBuffer? {
+ val captureArgs =
+ DisplayCaptureArgs.Builder(displayToken)
+ .setSize(width, height)
+ .setSourceCrop(crop)
+ .build()
return SurfaceControl.captureDisplay(captureArgs)
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 4397d3d..a918e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -16,51 +16,84 @@
package com.android.systemui.screenshot
-import android.net.Uri
-import android.util.Log
-import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.graphics.Insets
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Processes a screenshot request sent from {@link ScreenshotHelper}.
*/
@SysUISingleton
-internal class RequestProcessor @Inject constructor(
- private val controller: ScreenshotController,
+class RequestProcessor @Inject constructor(
+ private val capture: ImageCapture,
+ private val policy: ScreenshotPolicy,
+ private val flags: FeatureFlags,
+ /** For the Java Async version, to invoke the callback. */
+ @Application private val mainScope: CoroutineScope
) {
- fun processRequest(
- request: ScreenshotRequest,
- onSavedListener: Consumer<Uri>,
- callback: RequestCallback
- ) {
+ /**
+ * Inspects the incoming request, returning a potentially modified request depending on policy.
+ *
+ * @param request the request to process
+ */
+ suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
+ var result = request
- if (request.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
- val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)
+ // Apply work profile screenshots policy:
+ //
+ // If the focused app belongs to a work profile, transforms a full screen
+ // (or partial) screenshot request to a task snapshot (provided image) screenshot.
- controller.handleImageAsScreenshot(
- image, request.boundsInScreen, request.insets,
- request.taskId, request.userId, request.topComponent, onSavedListener, callback
- )
- return
+ // Whenever displayContentInfo is fetched, the topComponent is also populated
+ // regardless of the managed profile status.
+
+ if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
+ flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ ) {
+
+ val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+
+ result = if (policy.isManagedProfile(info.userId)) {
+ val image = capture.captureTask(info.taskId)
+ ?: error("Task snapshot returned a null Bitmap!")
+
+ // Provide the task snapshot as the screenshot
+ ScreenshotRequest(
+ TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
+ HardwareBitmapBundler.hardwareBitmapToBundle(image),
+ info.bounds, Insets.NONE, info.taskId, info.userId, info.component
+ )
+ } else {
+ // Create a new request of the same type which includes the top component
+ ScreenshotRequest(request.source, request.type, info.component)
+ }
}
- when (request.type) {
- TAKE_SCREENSHOT_FULLSCREEN ->
- controller.takeScreenshotFullscreen(null, onSavedListener, callback)
- TAKE_SCREENSHOT_SELECTED_REGION ->
- controller.takeScreenshotPartial(null, onSavedListener, callback)
- else -> Log.w(TAG, "Invalid screenshot option: ${request.type}")
- }
+ return result
}
- companion object {
- const val TAG: String = "RequestProcessor"
+ /**
+ * Note: This is for compatibility with existing Java. Prefer the suspending function when
+ * calling from a Coroutine context.
+ *
+ * @param request the request to process
+ * @param callback the callback to provide the processed request, invoked from the main thread
+ */
+ fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
+ mainScope.launch {
+ val result = process(request)
+ callback.accept(result)
+ }
}
}
+
+private const val TAG = "RequestProcessor"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
new file mode 100644
index 0000000..3580010
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.screenshot
+
+import android.annotation.UserIdInt
+import android.content.ComponentName
+import android.graphics.Rect
+import android.view.Display
+
+/**
+ * Provides policy decision-making information to screenshot request handling.
+ */
+interface ScreenshotPolicy {
+
+ /** @return true if the user is a managed profile (a.k.a. work profile) */
+ suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean
+
+ /**
+ * Requests information about the owner of display content which occupies a majority of the
+ * screenshot and/or has most recently been interacted with at the time the screenshot was
+ * requested.
+ *
+ * @param displayId the id of the display to inspect
+ * @return content info for the primary content on the display
+ */
+ suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo
+
+ data class DisplayContentInfo(
+ val component: ComponentName,
+ val bounds: Rect,
+ @UserIdInt val userId: Int,
+ val taskId: Int,
+ )
+
+ fun getDefaultDisplayId(): Int = Display.DEFAULT_DISPLAY
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
new file mode 100644
index 0000000..ba809f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.screenshot
+
+import android.annotation.UserIdInt
+import android.app.ActivityTaskManager
+import android.app.ActivityTaskManager.RootTaskInfo
+import android.app.IActivityTaskManager
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.activityTypeToString
+import android.app.WindowConfiguration.windowingModeToString
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Process
+import android.os.RemoteException
+import android.os.UserManager
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.SystemUIService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+import java.util.Arrays
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+internal class ScreenshotPolicyImpl @Inject constructor(
+ context: Context,
+ private val userMgr: UserManager,
+ private val atmService: IActivityTaskManager,
+ @Background val bgDispatcher: CoroutineDispatcher,
+) : ScreenshotPolicy {
+
+ private val systemUiContent =
+ DisplayContentInfo(
+ ComponentName(context, SystemUIService::class.java),
+ Rect(),
+ ActivityTaskManager.INVALID_TASK_ID,
+ Process.myUserHandle().identifier,
+ )
+
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface
+ )
+
+ override fun getDefaultDisplayId(): Int {
+ return DEFAULT_DISPLAY
+ }
+
+ override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
+ return withContext(bgDispatcher) { userMgr.isManagedProfile(userId) }
+ }
+
+ private fun nonPipVisibleTask(info: RootTaskInfo): Boolean {
+ return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED &&
+ info.isVisible &&
+ info.isRunning &&
+ info.numActivities > 0 &&
+ info.topActivity != null &&
+ info.childTaskIds.isNotEmpty()
+ }
+
+ /**
+ * Uses RootTaskInfo from ActivityTaskManager to guess at the primary focused task within a
+ * display. If no task is visible or the top task is covered by a system window, the info
+ * reported will reference a SystemUI component instead.
+ */
+ override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
+ // Determine if the notification shade is expanded. If so, task windows are not
+ // visible behind it, so the screenshot should instead be associated with SystemUI.
+ if (isNotificationShadeExpanded()) {
+ return systemUiContent
+ }
+
+ val taskInfoList = getAllRootTaskInfosOnDisplay(displayId)
+ if (DEBUG) {
+ debugLogRootTaskInfos(taskInfoList)
+ }
+
+ // If no visible task is located, then report SystemUI as the foreground content
+ val target = taskInfoList.firstOrNull(::nonPipVisibleTask) ?: return systemUiContent
+
+ val topActivity: ComponentName = target.topActivity ?: error("should not be null")
+ val topChildTask = target.childTaskIds.size - 1
+ val childTaskId = target.childTaskIds[topChildTask]
+ val childTaskUserId = target.childTaskUserIds[topChildTask]
+ val childTaskBounds = target.childTaskBounds[topChildTask]
+
+ return DisplayContentInfo(topActivity, childTaskBounds, childTaskId, childTaskUserId)
+ }
+
+ private fun debugLogRootTaskInfos(taskInfoList: List<RootTaskInfo>) {
+ for (info in taskInfoList) {
+ Log.d(
+ TAG,
+ "[root task info] " +
+ "taskId=${info.taskId} " +
+ "parentTaskId=${info.parentTaskId} " +
+ "position=${info.position} " +
+ "positionInParent=${info.positionInParent} " +
+ "isVisible=${info.isVisible()} " +
+ "visible=${info.visible} " +
+ "isFocused=${info.isFocused} " +
+ "isSleeping=${info.isSleeping} " +
+ "isRunning=${info.isRunning} " +
+ "windowMode=${windowingModeToString(info.windowingMode)} " +
+ "activityType=${activityTypeToString(info.activityType)} " +
+ "topActivity=${info.topActivity} " +
+ "topActivityInfo=${info.topActivityInfo} " +
+ "numActivities=${info.numActivities} " +
+ "childTaskIds=${Arrays.toString(info.childTaskIds)} " +
+ "childUserIds=${Arrays.toString(info.childTaskUserIds)} " +
+ "childTaskBounds=${Arrays.toString(info.childTaskBounds)} " +
+ "childTaskNames=${Arrays.toString(info.childTaskNames)}"
+ )
+
+ for (j in 0 until info.childTaskIds.size) {
+ Log.d(TAG, " *** [$j] ******")
+ Log.d(TAG, " *** childTaskIds[$j]: ${info.childTaskIds[j]}")
+ Log.d(TAG, " *** childTaskUserIds[$j]: ${info.childTaskUserIds[j]}")
+ Log.d(TAG, " *** childTaskBounds[$j]: ${info.childTaskBounds[j]}")
+ Log.d(TAG, " *** childTaskNames[$j]: ${info.childTaskNames[j]}")
+ }
+ }
+ }
+
+ private suspend fun getAllRootTaskInfosOnDisplay(displayId: Int): List<RootTaskInfo> =
+ withContext(bgDispatcher) {
+ try {
+ atmService.getAllRootTaskInfosOnDisplay(displayId)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "getAllRootTaskInfosOnDisplay", e)
+ listOf()
+ }
+ }
+
+ private suspend fun isNotificationShadeExpanded(): Boolean = suspendCoroutine { k ->
+ proxyConnector
+ .postForResult { it.isNotificationShadeExpanded }
+ .whenComplete { expanded, error ->
+ if (error != null) {
+ Log.e(TAG, "isNotificationShadeExpanded", error)
+ }
+ k.resume(expanded ?: false)
+ }
+ }
+
+ companion object {
+ const val TAG: String = "ScreenshotPolicyImpl"
+ const val DEBUG: Boolean = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
new file mode 100644
index 0000000..9654e03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.screenshot
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.util.Log
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+import javax.inject.Inject
+
+/**
+ * Provides state from the main SystemUI process on behalf of the Screenshot process.
+ */
+internal class ScreenshotProxyService @Inject constructor(
+ private val mExpansionMgr: PanelExpansionStateManager
+) : Service() {
+
+ private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
+ /**
+ * @return true when the notification shade is partially or fully expanded.
+ */
+ override fun isNotificationShadeExpanded(): Boolean {
+ val expanded = !mExpansionMgr.isClosed()
+ Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
+ return expanded
+ }
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ Log.d(TAG, "onBind: $intent")
+ return mBinder
+ }
+
+ companion object {
+ const val TAG = "ScreenshotProxyService"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 7bf3217..a8993bc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -22,6 +22,7 @@
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR;
+import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
@@ -97,7 +98,7 @@
};
/** Informs about coarse grained state of the Controller. */
- interface RequestCallback {
+ public interface RequestCallback {
/** Respond to the current request indicating the screenshot request failed. */
void reportError();
@@ -124,6 +125,7 @@
mBgExecutor = bgExecutor;
mFeatureFlags = featureFlags;
mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
+ mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart);
mProcessor = processor;
}
@@ -229,49 +231,57 @@
if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
Log.d(TAG, "handleMessage: Using request processor");
- mProcessor.processRequest(screenshotRequest, uriConsumer, requestCallback);
+ mProcessor.processAsync(screenshotRequest,
+ (request) -> dispatchToController(request, uriConsumer, requestCallback));
return true;
}
- switch (screenshotRequest.getType()) {
+ dispatchToController(screenshotRequest, uriConsumer, requestCallback);
+ return true;
+ }
+
+ private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
+ Consumer<Uri> uriConsumer, RequestCallback callback) {
+
+ ComponentName topComponent = request.getTopComponent();
+
+ switch (request.getType()) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
}
- mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback);
+ mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, callback);
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
}
- mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback);
+ mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, callback);
break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
}
Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
- screenshotRequest.getBitmapBundle());
- Rect screenBounds = screenshotRequest.getBoundsInScreen();
- Insets insets = screenshotRequest.getInsets();
- int taskId = screenshotRequest.getTaskId();
- int userId = screenshotRequest.getUserId();
+ request.getBitmapBundle());
+ Rect screenBounds = request.getBoundsInScreen();
+ Insets insets = request.getInsets();
+ int taskId = request.getTaskId();
+ int userId = request.getUserId();
if (screenshot == null) {
Log.e(TAG, "Got null bitmap from screenshot message");
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
- requestCallback.reportError();
+ callback.reportError();
} else {
mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
- taskId, userId, topComponent, uriConsumer, requestCallback);
+ taskId, userId, topComponent, uriConsumer, callback);
}
break;
default:
- Log.w(TAG, "Invalid screenshot option: " + msg.what);
- return false;
+ Log.w(TAG, "Invalid screenshot option: " + request.getType());
}
- return true;
}
private static void sendComplete(Messenger target) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3e442587..fdb0100 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -20,6 +20,9 @@
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.ScreenshotPolicy;
+import com.android.systemui.screenshot.ScreenshotPolicyImpl;
+import com.android.systemui.screenshot.ScreenshotProxyService;
import com.android.systemui.screenshot.TakeScreenshotService;
import dagger.Binds;
@@ -33,12 +36,20 @@
@Module
public abstract class ScreenshotModule {
- /** */
@Binds
@IntoMap
@ClassKey(TakeScreenshotService.class)
- public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+ abstract Service bindTakeScreenshotService(TakeScreenshotService service);
@Binds
- public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture);
+ @IntoMap
+ @ClassKey(ScreenshotProxyService.class)
+ abstract Service bindScreenshotProxyService(ScreenshotProxyService service);
+
+ @Binds
+ abstract ScreenshotPolicy bindScreenshotPolicyImpl(ScreenshotPolicyImpl impl);
+
+ @Binds
+ abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
index ce9d89f..4558061 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
@@ -29,11 +29,25 @@
interface Listener {
/** Invoked when the notification panel starts or stops collapsing. */
- fun onPanelCollapsingChanged(isCollapsing: Boolean)
+ @JvmDefault
+ fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
/**
* Invoked when the notification panel starts or stops launching an [android.app.Activity].
*/
- fun onLaunchingActivityChanged(isLaunchingActivity: Boolean)
+ @JvmDefault
+ fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+
+ /**
+ * Invoked when the "expand immediate" attribute changes.
+ *
+ * An example of expanding immediately is when swiping down from the top with two fingers.
+ * Instead of going to QQS, we immediately expand to full QS.
+ *
+ * Another example is when full QS is showing, and we swipe up from the bottom. Instead of
+ * going to QQS, the panel fully collapses.
+ */
+ @JvmDefault
+ fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 37a948d..7a4c877 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -442,7 +442,6 @@
*/
private boolean mQsAnimatorExpand;
private boolean mIsLaunchTransitionFinished;
- private boolean mOnlyAffordanceInThisMotion;
private ValueAnimator mQsSizeChangeAnimator;
private boolean mQsScrimEnabled = true;
@@ -720,6 +719,7 @@
AccessibilityManager accessibilityManager, @DisplayId int displayId,
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
+ ShadeLogger shadeLogger,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -786,6 +786,7 @@
panelExpansionStateManager,
ambientState,
interactionJankMonitor,
+ shadeLogger,
systemClock);
mView = view;
mVibratorHelper = vibratorHelper;
@@ -1690,12 +1691,17 @@
}
if (mQsExpanded) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
super.collapse(delayed, speedUpFactor);
}
+ private void setQsExpandImmediate(boolean expandImmediate) {
+ mQsExpandImmediate = expandImmediate;
+ mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ }
+
private void setShowShelfOnly(boolean shelfOnly) {
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
shelfOnly && !mSplitShadeEnabled);
@@ -1748,7 +1754,7 @@
public void expandWithQs() {
if (isQsExpansionEnabled()) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
if (mSplitShadeEnabled && isOnKeyguard()) {
@@ -1827,6 +1833,8 @@
}
if (mQsExpansionAnimator != null) {
mInitialHeightOnTouch = mQsExpansionHeight;
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: down action, QS tracking enabled");
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
mNotificationStackScrollLayoutController.cancelLongPress();
@@ -1854,12 +1862,16 @@
setQsExpansion(h + mInitialHeightOnTouch);
trackMovement(event);
return true;
+ } else {
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: move ignored because qs tracking disabled");
}
if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded))
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
mView.getParent().requestDisallowInterceptTouchEvent(true);
+ mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
onQsExpansionStarted();
@@ -1875,6 +1887,7 @@
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
trackMovement(event);
+ mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled");
mQsTracking = false;
break;
}
@@ -1912,7 +1925,6 @@
private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mOnlyAffordanceInThisMotion = false;
mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
mDozingOnDown = isDozing();
mDownX = event.getX();
@@ -2051,6 +2063,7 @@
&& collapsedQs && isQsExpansionEnabled();
if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) {
// Down in the empty area while fully expanded - go to QS.
+ mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled");
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
mConflictingQsExpansionGesture = true;
@@ -2065,6 +2078,8 @@
if (!mQsExpandImmediate && mQsTracking) {
onQsTouch(event);
if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
+ mShadeLog.logMotionEvent(event,
+ "handleQsTouch: not immediate expand or conflicting gesture");
return true;
}
}
@@ -2077,7 +2092,7 @@
if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
< mStatusBarMinHeight) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
requestPanelHeightUpdate();
@@ -2132,6 +2147,7 @@
event.getX(), event.getY(), -1)) {
if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown");
mFalsingCollector.onQsDown();
+ mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
mQsTracking = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -2214,6 +2230,7 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled");
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
mInitialTouchY = y;
@@ -2240,6 +2257,7 @@
case MotionEvent.ACTION_MOVE:
if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move");
+ mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
setQsExpansion(h + mInitialHeightOnTouch);
if (h >= getFalsingThreshold()) {
mQsTouchAboveFalsingThreshold = true;
@@ -2249,6 +2267,8 @@
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ mShadeLog.logMotionEvent(event,
+ "onQsTouch: up/cancel action, QS tracking disabled");
mQsTracking = false;
mTrackingPointer = -1;
trackMovement(event);
@@ -3071,8 +3091,8 @@
positionClockAndNotifications();
}
}
- if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
- && !mQsExpansionFromOverscroll) {
+ if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+ && !mQsExpansionFromOverscroll)) {
float t;
if (mKeyguardShowing) {
@@ -3279,7 +3299,7 @@
} else {
setListening(true);
}
- mQsExpandImmediate = false;
+ setQsExpandImmediate(false);
setShowShelfOnly(false);
mTwoFingerQsExpandPossible = false;
updateTrackingHeadsUp(null);
@@ -3337,7 +3357,7 @@
super.onTrackingStarted();
mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
@@ -4162,6 +4182,7 @@
|| mPulseExpansionHandler.isExpanding();
if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
// We're expanding all the other ones shouldn't get this anymore
+ mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
return true;
}
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
@@ -4169,14 +4190,10 @@
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
}
- boolean handled = false;
- if (mOnlyAffordanceInThisMotion) {
- return true;
- }
- handled |= mHeadsUpTouchHelper.onTouchEvent(event);
+ boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "handleQsTouch true");
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
@@ -4744,6 +4761,8 @@
}
} else if (!mQsExpanded && mQsExpansionAnimator == null) {
setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
+ } else {
+ mShadeLog.v("onLayoutChange: qs expansion not set");
}
updateExpandedHeight(getExpandedHeight());
updateHeader();
@@ -4898,7 +4917,7 @@
// to locked will trigger this event and we're not actually in the process of opening
// the shade, lockscreen is just always expanded
if (mSplitShadeEnabled && !isOnKeyguard()) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
}
mCentralSurfaces.makeExpandedVisible(false);
}
@@ -4965,5 +4984,11 @@
cb.onPanelCollapsingChanged(isCollapsing);
}
}
+
+ private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
+ for (NotifPanelEvents.Listener cb : mListeners) {
+ cb.onExpandImmediateChanged(expandImmediateEnabled);
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 4aad245..73eaa85 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -202,6 +202,8 @@
private final InteractionJankMonitor mInteractionJankMonitor;
protected final SystemClock mSystemClock;
+ protected final ShadeLogger mShadeLog;
+
protected abstract void onExpandingFinished();
protected void onExpandingStarted() {
@@ -242,6 +244,7 @@
PanelExpansionStateManager panelExpansionStateManager,
AmbientState ambientState,
InteractionJankMonitor interactionJankMonitor,
+ ShadeLogger shadeLogger,
SystemClock systemClock) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -254,6 +257,7 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeLog = shadeLogger;
TouchHandler touchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -1275,9 +1279,16 @@
@Override
public boolean onTouch(View v, MotionEvent event) {
- if (mInstantExpanding || (mTouchDisabled
- && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted
- && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+ if (mInstantExpanding) {
+ mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
+ return false;
+ }
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
+ return false;
+ }
+ if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
return false;
}
@@ -1287,6 +1298,7 @@
// Turn off tracking if it's on or the shade can get stuck in the down position.
onTrackingStopped(true /* expand */);
}
+ mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
new file mode 100644
index 0000000..f1e44ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.ShadeLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "systemui.shade"
+
+/** Lightweight logging utility for the Shade. */
+class ShadeLogger @Inject constructor(
+ @ShadeLog
+ private val buffer: LogBuffer
+) {
+ fun v(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, LogLevel.VERBOSE, msg)
+ }
+
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
+
+ fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
+ log(LogLevel.VERBOSE,
+ { double1 = h.toDouble() },
+ { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" })
+ }
+
+ fun logMotionEvent(event: MotionEvent, message: String) {
+ log(LogLevel.VERBOSE, {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ double1 = event.y.toDouble()
+ }, {
+ "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2"
+ })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 65ba5ad..e754d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -512,7 +512,8 @@
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
- mUpdateMonitor.awakenFromDream();
+ // Don't call awaken from Dream here. In order to avoid flickering, wait until
+ // later to awaken.
}
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
mKeyguardViewMediator.onWakeAndUnlocking();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4c9c75b..103e4f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -27,6 +27,7 @@
import androidx.collection.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -39,6 +40,7 @@
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -107,8 +109,10 @@
GroupMembershipManager groupMembershipManager,
VisualStabilityProvider visualStabilityProvider,
ConfigurationController configurationController,
- @Main Handler handler) {
- super(context, logger, handler);
+ @Main Handler handler,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ UiEventLogger uiEventLogger) {
+ super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
statusBarStateController.addCallback(mStatusBarStateListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index d058b75..53e08ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -10,6 +10,7 @@
import android.provider.Settings
import android.view.Surface
import android.view.View
+import android.view.WindowManager.fixScale
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
@@ -138,8 +139,8 @@
}
fun updateAnimatorDurationScale() {
- animatorDurationScale =
- globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+ animatorDurationScale = fixScale(
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f))
}
override fun shouldDelayKeyguardShow(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
index 6c02b0d..780a02d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.pipeline
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
import kotlinx.coroutines.flow.StateFlow
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
index 8d69422..99798f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
@@ -18,9 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilitiesRepo
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilitiesRepo
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
index 1aae250..64c47f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -20,14 +20,18 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* A processor that transforms raw connectivity information that we get from callbacks and turns it
@@ -42,12 +46,16 @@
class ConnectivityInfoProcessor @Inject constructor(
connectivityInfoCollector: ConnectivityInfoCollector,
context: Context,
+ // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's
+ // scope so we only do work when there's UI that cares about it.
@Application private val scope: CoroutineScope,
- statusBarPipelineFlags: StatusBarPipelineFlags,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
+ private val wifiViewModelProvider: Provider<WifiViewModel>,
) : CoreStartable(context) {
// Note: This flow will not start running until a client calls `collect` on it, which means that
// [connectivityInfoCollector]'s flow will also not start anything until that `collect` call
// happens.
+ // TODO(b/238425913): Delete this.
val processedInfoFlow: Flow<ProcessedConnectivityInfo> =
if (!statusBarPipelineFlags.isNewPipelineEnabled())
emptyFlow()
@@ -60,6 +68,14 @@
)
override fun start() {
+ if (!statusBarPipelineFlags.isNewPipelineEnabled()) {
+ return
+ }
+ // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can
+ // see the logs.
+ scope.launch {
+ wifiViewModelProvider.get().isActivityInVisible.collect { }
+ }
}
private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
deleted file mode 100644
index f88e9d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
+++ /dev/null
@@ -1,59 +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.statusbar.pipeline
-
-import android.net.Network
-import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.StatusBarConnectivityLog
-import javax.inject.Inject
-
-@SysUISingleton
-class ConnectivityPipelineLogger @Inject constructor(
- @StatusBarConnectivityLog private val buffer: LogBuffer,
-) {
- fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- {
- int1 = network.getNetId()
- str1 = networkCapabilities.toString()
- },
- {
- "onCapabilitiesChanged: net=$int1 capabilities=$str1"
- }
- )
- }
-
- fun logOnLost(network: Network) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- {
- int1 = network.getNetId()
- },
- {
- "onLost: net=$int1"
- }
- )
- }
-}
-
-private const val TAG = "SbConnectivityPipeline"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index c4e2b73..7abe19e7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -20,6 +20,8 @@
import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector
import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -37,4 +39,7 @@
abstract fun provideConnectivityInfoCollector(
impl: ConnectivityInfoCollectorImpl
): ConnectivityInfoCollector
+
+ @Binds
+ abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
new file mode 100644
index 0000000..a5fff5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.pipeline.shared
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onEach
+
+@SysUISingleton
+class ConnectivityPipelineLogger @Inject constructor(
+ @StatusBarConnectivityLog private val buffer: LogBuffer,
+) {
+ fun logInputChange(callbackName: String, changeInfo: String) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ str1 = callbackName
+ str2 = changeInfo
+ },
+ {
+ "Input: $str1: $str2"
+ }
+ )
+ }
+
+ fun logOutputChange(outputParamName: String, changeInfo: String) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ str1 = outputParamName
+ str2 = changeInfo
+ },
+ {
+ "Output: $str1: $str2"
+ }
+ )
+ }
+
+ fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ str1 = networkCapabilities.toString()
+ },
+ {
+ "onCapabilitiesChanged: net=$int1 capabilities=$str1"
+ }
+ )
+ }
+
+ fun logOnLost(network: Network) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ },
+ {
+ "onLost: net=$int1"
+ }
+ )
+ }
+
+ companion object {
+ const val SB_LOGGING_TAG = "SbConnectivity"
+
+ /**
+ * Log a change in one of the **outputs** to the connectivity pipeline.
+ *
+ * @param prettyPrint an optional function to transform the value into a readable string.
+ * [toString] is used if no custom function is provided.
+ */
+ fun <T : Any> Flow<T>.logOutputChange(
+ logger: ConnectivityPipelineLogger,
+ outputParamName: String,
+ prettyPrint: (T) -> String = { it.toString() }
+ ): Flow<T> {
+ return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
similarity index 64%
copy from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
index 8dde897..44c0496 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.settingslib.spaprivileged.framework.app
+package com.android.systemui.statusbar.pipeline.wifi.data.model
-import android.content.pm.ApplicationInfo
-
-interface AppRecord {
- val app: ApplicationInfo
-}
+/**
+ * Provides information on the current wifi activity.
+ */
+data class WifiActivityModel(
+ /** True if the wifi has activity in (download). */
+ val hasActivityIn: Boolean,
+ /** True if the wifi has activity out (upload). */
+ val hasActivityOut: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt
new file mode 100644
index 0000000..1b73322
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.pipeline.wifi.data.model
+
+/** Provides information about the current wifi state. */
+data class WifiModel(
+ /** See [android.net.wifi.WifiInfo.ssid]. */
+ val ssid: String? = null,
+ /** See [android.net.wifi.WifiInfo.isPasspointAp]. */
+ val isPasspointAccessPoint: Boolean = false,
+ /** See [android.net.wifi.WifiInfo.isOsuAp]. */
+ val isOnlineSignUpForPasspointAccessPoint: Boolean = false,
+ /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */
+ val passpointProviderFriendlyName: String? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt
index e5980c3..6c0a445 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.statusbar.pipeline.repository
+package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.annotation.SuppressLint
import android.net.ConnectivityManager
@@ -25,7 +25,7 @@
import android.net.NetworkRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,7 +35,11 @@
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn
-/** Repository that contains all relevant [NetworkCapabilites] for the current networks */
+/**
+ * Repository that contains all relevant [NetworkCapabilities] for the current networks.
+ *
+ * TODO(b/238425913): Figure out how to merge this with [WifiRepository].
+ */
@SysUISingleton
class NetworkCapabilitiesRepo @Inject constructor(
connectivityManager: ConnectivityManager,
@@ -88,5 +92,3 @@
val network: Network,
val capabilities: NetworkCapabilities,
)
-
-private const val TAG = "ConnectivityRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
new file mode 100644
index 0000000..012dde5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.pipeline.wifi.data.repository
+
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiManager.TrafficStateCallback
+import android.util.Log
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * Provides data related to the wifi state.
+ */
+interface WifiRepository {
+ /**
+ * Observable for the current state of wifi; `null` when there is no active wifi.
+ */
+ val wifiModel: Flow<WifiModel?>
+
+ /**
+ * Observable for the current wifi network activity.
+ */
+ val wifiActivity: Flow<WifiActivityModel>
+}
+
+/** Real implementation of [WifiRepository]. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class WifiRepositoryImpl @Inject constructor(
+ wifiManager: WifiManager?,
+ @Main mainExecutor: Executor,
+ logger: ConnectivityPipelineLogger,
+) : WifiRepository {
+
+ // TODO(b/238425913): Actually implement the wifiModel flow.
+ override val wifiModel: Flow<WifiModel?> = flowOf(WifiModel(ssid = "AB"))
+
+ override val wifiActivity: Flow<WifiActivityModel> =
+ if (wifiManager == null) {
+ Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
+ flowOf(ACTIVITY_DEFAULT)
+ } else {
+ conflatedCallbackFlow {
+ val callback = TrafficStateCallback { state ->
+ logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
+ trySend(trafficStateToWifiActivityModel(state))
+ }
+
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ trySend(ACTIVITY_DEFAULT)
+
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+ }
+ }
+
+ companion object {
+ val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+ private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel {
+ return WifiActivityModel(
+ hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN ||
+ state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
+ hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT ||
+ state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
+ )
+ }
+
+ private fun prettyPrintActivity(activity: Int): String {
+ return when (activity) {
+ TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+ TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+ TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+ TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+ else -> "INVALID"
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
new file mode 100644
index 0000000..f705399
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.pipeline.wifi.domain.interactor
+
+import android.net.wifi.WifiManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+/**
+ * The business logic layer for the wifi icon.
+ *
+ * This interactor processes information from our data layer into information that the UI layer can
+ * use.
+ */
+@SysUISingleton
+class WifiInteractor @Inject constructor(
+ repository: WifiRepository,
+) {
+ private val ssid: Flow<String?> = repository.wifiModel.map { info ->
+ when {
+ info == null -> null
+ info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
+ info.passpointProviderFriendlyName
+ info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
+ else -> null
+ }
+ }
+
+ val hasActivityIn: Flow<Boolean> = combine(repository.wifiActivity, ssid) { activity, ssid ->
+ activity.hasActivityIn && ssid != null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
new file mode 100644
index 0000000..a19d1bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.pipeline.wifi.shared
+
+import android.content.Context
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * An object storing constants that we use for calculating the wifi icon. Stored in a class for
+ * logging purposes.
+ */
+@SysUISingleton
+class WifiConstants @Inject constructor(
+ context: Context,
+ dumpManager: DumpManager,
+) : Dumpable {
+ init {
+ dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this)
+ }
+
+ /** True if we should show the activityIn/activityOut icons and false otherwise. */
+ val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.apply {
+ println("shouldShowActivityConfig=$shouldShowActivityConfig")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
new file mode 100644
index 0000000..b990eb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.pipeline.wifi.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * Models the UI state for the status bar wifi icon.
+ *
+ * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder.
+ */
+class WifiViewModel @Inject constructor(
+ private val constants: WifiConstants,
+ private val logger: ConnectivityPipelineLogger,
+ private val interactor: WifiInteractor,
+) {
+ val isActivityInVisible: Flow<Boolean>
+ get() =
+ if (!constants.shouldShowActivityConfig) {
+ flowOf(false)
+ } else {
+ interactor.hasActivityIn
+ }
+ .logOutputChange(logger, "activityInVisible")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 699414c..e4e59a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -32,7 +32,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
@@ -81,12 +80,15 @@
}
}
- public HeadsUpManager(@NonNull final Context context, HeadsUpManagerLogger logger,
- @Main Handler handler) {
+ public HeadsUpManager(@NonNull final Context context,
+ HeadsUpManagerLogger logger,
+ @Main Handler handler,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ UiEventLogger uiEventLogger) {
super(logger, handler);
mContext = context;
- mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class);
- mUiEventLogger = Dependency.get(UiEventLogger.class);
+ mAccessibilityMgr = accessibilityManagerWrapper;
+ mUiEventLogger = uiEventLogger;
Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mAutoDismissNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 094490b..adef182 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -244,7 +244,8 @@
final int currentUser = mUserTracker.getUserId();
final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
int latestWallpaperType = getLatestWallpaperType(userId);
- if ((flags & latestWallpaperType) != 0) {
+ boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
+ if (eventForLatestWallpaper) {
mCurrentColors.put(userId, wallpaperColors);
if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
}
@@ -280,14 +281,19 @@
currentUser);
boolean isDestinationBoth = (flags == (WallpaperManager.FLAG_SYSTEM
| WallpaperManager.FLAG_LOCK));
+ boolean isDestinationHomeOnly = (flags == WallpaperManager.FLAG_SYSTEM);
try {
JSONObject jsonObject = (overlayPackageJson == null) ? new JSONObject()
: new JSONObject(overlayPackageJson);
// The latest applied wallpaper should be the source of system colors when:
// There is not preset color applied and the incoming wallpaper color is not applied
- if (!COLOR_SOURCE_PRESET.equals(jsonObject.optString(OVERLAY_COLOR_SOURCE))
- && ((flags & latestWallpaperType) != 0 && !isSeedColorSet(jsonObject,
- wallpaperColors))) {
+ String wallpaperPickerColorSource = jsonObject.optString(OVERLAY_COLOR_SOURCE);
+ boolean userChosePresetColor = COLOR_SOURCE_PRESET.equals(wallpaperPickerColorSource);
+ boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
+ boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
+
+ if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+ && !isSeedColorSet(jsonObject, wallpaperColors)) {
mSkipSettingChange = true;
if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
@@ -642,7 +648,7 @@
}
if (mNeedsOverlayCreation) {
mNeedsOverlayCreation = false;
- mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] {
+ mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[]{
mSecondaryOverlay, mNeutralOverlay
}, currentUser, managedProfiles);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 0fe10cb..abffe555 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -26,6 +26,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardViewController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -63,6 +64,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -173,7 +175,9 @@
GroupMembershipManager groupManager,
VisualStabilityProvider visualStabilityProvider,
ConfigurationController configurationController,
- @Main Handler handler) {
+ @Main Handler handler,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ UiEventLogger uiEventLogger) {
return new HeadsUpManagerPhone(
context,
headsUpManagerLogger,
@@ -182,7 +186,9 @@
groupManager,
visualStabilityProvider,
configurationController,
- handler
+ handler,
+ accessibilityManagerWrapper,
+ uiEventLogger
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 8f2a432..fc20ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -138,7 +138,7 @@
ensureOverlayRemoved()
- val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
+ val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
val newView =
LightRevealScrim(context, null).apply {
revealEffect = createLightRevealEffect()
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4c76270..aeab2df 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -38,6 +38,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.dreams.IDreamManager;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
import android.util.Log;
@@ -101,6 +102,7 @@
private final ShadeController mShadeController;
private final IStatusBarService mBarService;
private final INotificationManager mNotificationManager;
+ private final IDreamManager mDreamManager;
private final NotificationVisibilityProvider mVisibilityProvider;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final NotificationLockscreenUserManager mNotifUserManager;
@@ -126,6 +128,7 @@
ShadeController shadeController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
+ IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -144,6 +147,7 @@
shadeController,
statusBarService,
notificationManager,
+ dreamManager,
visibilityProvider,
interruptionStateProvider,
zenModeController,
@@ -167,6 +171,7 @@
ShadeController shadeController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
+ IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -182,6 +187,7 @@
mNotificationShadeWindowController = notificationShadeWindowController;
mShadeController = shadeController;
mNotificationManager = notificationManager;
+ mDreamManager = dreamManager;
mVisibilityProvider = visibilityProvider;
mNotificationInterruptStateProvider = interruptionStateProvider;
mNotifUserManager = notifUserManager;
@@ -203,7 +209,7 @@
@Override
public void onKeyguardShowingChanged() {
boolean isUnlockedShade = !keyguardStateController.isShowing()
- && !keyguardStateController.isOccluded();
+ && !isDreamingOrInPreview();
bubbles.onStatusBarStateChanged(isUnlockedShade);
}
});
@@ -397,6 +403,15 @@
mBubbles.setSysuiProxy(mSysuiProxy);
}
+ private boolean isDreamingOrInPreview() {
+ try {
+ return mDreamManager.isDreamingOrInPreview();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query dream manager.", e);
+ return false;
+ }
+ }
+
private void setupNotifPipeline() {
mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 763a5cb..ba28045 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -53,7 +53,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" />
- <application android:debuggable="true" android:largeHeap="true">
+ <application android:debuggable="true" android:largeHeap="true"
+ android:enableOnBackInvokedCallback="true" >
<uses-library android:name="android.test.runner" />
<receiver android:name="com.android.systemui.SliceBroadcastRelayHandlerTest$Receiver"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c281965..3e00aec 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -1006,7 +1007,7 @@
// WHEN udfps is now enrolled
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
- callback.onEnrollmentsChanged();
+ callback.onEnrollmentsChanged(TYPE_FINGERPRINT);
// THEN isUdfspEnrolled is TRUE
assertThat(mKeyguardUpdateMonitor.isUdfpsEnrolled()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 273786d..8fc0489 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -664,6 +664,60 @@
}
@Test
+ fun animateAddition_runnableRunsWhenAnimationEnds() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ endAnimation(rootView)
+
+ assertEquals(true, runnableRun)
+ }
+
+ @Test
+ fun animateAddition_runnableDoesNotRunWhenAnimationCancelled() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ cancelAnimation(rootView)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
+ fun animationAddition_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ advanceAnimation(rootView, 0.5f)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
fun animatesViewRemovalFromStartToEnd() {
setUpRootWithChildren()
@@ -1158,6 +1212,16 @@
}
}
+ private fun cancelAnimation(rootView: View) {
+ (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
+
+ if (rootView is ViewGroup) {
+ for (i in 0 until rootView.childCount) {
+ cancelAnimation(rootView.getChildAt(i))
+ }
+ }
+ }
+
private fun endFadeInAnimation(rootView: View) {
(rootView.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.end()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index d158892..e0d1f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -60,6 +60,9 @@
import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -154,7 +157,9 @@
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
@Captor
- ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor;
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
+ @Captor
+ ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
@Captor
ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
@Captor
@@ -193,25 +198,38 @@
when(mDisplayManager.getStableDisplaySize()).thenReturn(new Point());
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFaceManager.isHardwareDetected()).thenReturn(true);
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
+ final List<ComponentInfoInternal> fpComponentInfo = List.of(
+ new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ final List<ComponentInfoInternal> faceComponentInfo = List.of(
+ new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
- FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
- 1 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequireHardwareAuthToken */);
- List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(prop);
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+ final List<FingerprintSensorPropertiesInternal> fpProps = List.of(
+ new FingerprintSensorPropertiesInternal(
+ 1 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ fpComponentInfo,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* resetLockoutRequireHardwareAuthToken */));
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(fpProps);
+
+ final List<FaceSensorPropertiesInternal> faceProps = List.of(
+ new FaceSensorPropertiesInternal(
+ 2 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ fpComponentInfo,
+ FaceSensorProperties.TYPE_RGB,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ true /* resetLockoutRequireHardwareAuthToken */));
+ when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
@@ -219,12 +237,15 @@
mAuthController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mAuthenticatorsRegisteredCaptor.capture());
+ mFpAuthenticatorsRegisteredCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
when(mStatusBarStateController.isDozing()).thenReturn(false);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
- mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props);
+ mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
// Ensures that the operations posted on the handler get executed.
mTestableLooper.processAllMessages();
@@ -237,6 +258,7 @@
throws RemoteException {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ reset(mFaceManager);
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -246,21 +268,27 @@
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mAuthenticatorsRegisteredCaptor.capture());
+ mFpAuthenticatorsRegisteredCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
mTestableLooper.processAllMessages();
verify(mFingerprintManager, never()).registerBiometricStateListener(any());
+ verify(mFaceManager, never()).registerBiometricStateListener(any());
- mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+ mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mTestableLooper.processAllMessages();
verify(mFingerprintManager).registerBiometricStateListener(any());
+ verify(mFaceManager).registerBiometricStateListener(any());
}
@Test
public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException {
// This test is sensitive to prior FingerprintManager interactions.
reset(mFingerprintManager);
+ reset(mFaceManager);
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
@@ -270,18 +298,25 @@
authController.start();
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- mAuthenticatorsRegisteredCaptor.capture());
+ mFpAuthenticatorsRegisteredCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
// Emulates a device with no authenticators (empty list).
- mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(new ArrayList<>());
+ mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
mTestableLooper.processAllMessages();
verify(mFingerprintManager).registerBiometricStateListener(
mBiometricStateCaptor.capture());
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
// Enrollments changed for an unknown sensor.
- mBiometricStateCaptor.getValue().onEnrollmentsChanged(0 /* userId */,
- 0xbeef /* sensorId */, true /* hasEnrollments */);
+ for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
+ listener.onEnrollmentsChanged(0 /* userId */,
+ 0xbeef /* sensorId */, true /* hasEnrollments */);
+ }
mTestableLooper.processAllMessages();
// Nothing should crash.
@@ -827,4 +862,3 @@
}
}
}
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 9ffc5a5..b33f9a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
@@ -412,7 +414,7 @@
// WHEN enrollment changes to TRUE
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
- mAuthControllerCallback.onEnrollmentsChanged();
+ mAuthControllerCallback.onEnrollmentsChanged(TYPE_FINGERPRINT);
// THEN mConfigured = TRUE
assertTrue(triggerSensor.mConfigured);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 141a213..b42b769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -42,8 +42,11 @@
import android.testing.TestableLooper;
import android.view.GestureDetector;
import android.view.IWindowManager;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManagerPolicyConstants;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import androidx.test.filters.SmallTest;
@@ -73,6 +76,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -117,6 +122,8 @@
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
private TestableLooper mTestableLooper;
@@ -203,6 +210,58 @@
}
@Test
+ public void testPredictiveBackCallbackRegisteredAndUnregistered() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+ dialog.setBackDispatcherOverride(mOnBackInvokedDispatcher);
+ dialog.create();
+ mTestableLooper.processAllMessages();
+ verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+ eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any());
+ dialog.onDetachedFromWindow();
+ mTestableLooper.processAllMessages();
+ verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(any());
+ }
+
+ @Test
+ public void testPredictiveBackInvocationDismissesDialog() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog();
+ dialog.create();
+ dialog.show();
+ mTestableLooper.processAllMessages();
+ dialog.getWindow().injectInputEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));
+ dialog.getWindow().injectInputEvent(
+ new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK));
+ mTestableLooper.processAllMessages();
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_BACK);
+ assertThat(dialog.isShowing()).isFalse();
+ }
+
+ @Test
public void testSingleTap_logAndDismiss() {
mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index 24d0515..5ec6bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
@@ -219,7 +221,7 @@
Pair<Float, PointF> udfps = setupUdfps();
// WHEN all authenticators are registered
- mAuthControllerCallback.onAllAuthenticatorsRegistered();
+ mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
mDelayableExecutor.runAllReady();
// THEN lock icon view location is updated with the same coordinates as auth controller vals
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
index fcfef4a4..c41fac7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt
@@ -16,19 +16,22 @@
package com.android.systemui.media
+import android.provider.Settings
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.FrameLayout
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.MediaContainerView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import org.junit.Before
@@ -37,11 +40,12 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
class KeyguardMediaControllerTest : SysuiTestCase() {
@Mock
@@ -53,31 +57,33 @@
@Mock
private lateinit var configurationController: ConfigurationController
- @Mock
- private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
@JvmField @Rule
val mockito = MockitoJUnit.rule()
private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
private val hostView = UniqueObjectHostView(context)
+ private val settings = FakeSettings()
private lateinit var keyguardMediaController: KeyguardMediaController
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var fakeHandler: FakeHandler
@Before
fun setup() {
// default state is positive, media should show up
whenever(mediaHost.visible).thenReturn(true)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
- whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
- .thenReturn(true)
whenever(mediaHost.hostView).thenReturn(hostView)
hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
+ testableLooper = TestableLooper.get(this)
+ fakeHandler = FakeHandler(testableLooper.looper)
keyguardMediaController = KeyguardMediaController(
mediaHost,
bypassController,
statusBarStateController,
- notificationLockscreenUserManager,
context,
- configurationController
+ settings,
+ fakeHandler,
+ configurationController,
)
keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
keyguardMediaController.useSplitShade = false
@@ -106,9 +112,8 @@
}
@Test
- fun testHiddenOnKeyguard_whenNotificationsAreHidden() {
- whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications())
- .thenReturn(false)
+ fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() {
+ settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0)
keyguardMediaController.refreshMediaPosition()
@@ -116,6 +121,15 @@
}
@Test
+ fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() {
+ settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1)
+
+ keyguardMediaController.refreshMediaPosition()
+
+ assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE)
+ }
+
+ @Test
fun testActivatesSplitShadeContainerInSplitShadeMode() {
val splitShadeContainer = FrameLayout(context)
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 1cce7cf..d1ed8e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -50,11 +50,10 @@
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
@@ -287,6 +286,30 @@
}
@Test
+ fun testOnNotificationAdded_hasSubstituteName_isUsed() {
+ val subName = "Substitute Name"
+ val notif = SbnBuilder().run {
+ modifyNotification(context).also {
+ it.extras = Bundle().apply {
+ putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName)
+ }
+ it.setStyle(MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ })
+ }
+ build()
+ }
+
+ mediaDataManager.onNotificationAdded(KEY, notif)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0), eq(false))
+
+ assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName)
+ }
+
+ @Test
fun testLoadMediaDataInBg_invalidTokenNoCrash() {
val bundle = Bundle()
// wrong data type
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index d65b6b3..18bfd04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.media
-import org.mockito.Mockito.`when` as whenever
import android.graphics.Rect
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.ViewGroup
@@ -30,7 +30,7 @@
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.shade.testing.FakeNotifPanelEvents
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -38,6 +38,8 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertNotNull
import org.junit.Before
@@ -50,10 +52,10 @@
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@SmallTest
@@ -61,32 +63,18 @@
@TestableLooper.RunWithLooper
class MediaHierarchyManagerTest : SysuiTestCase() {
- @Mock
- private lateinit var lockHost: MediaHost
- @Mock
- private lateinit var qsHost: MediaHost
- @Mock
- private lateinit var qqsHost: MediaHost
- @Mock
- private lateinit var bypassController: KeyguardBypassController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
- @Mock
- private lateinit var mediaCarouselController: MediaCarouselController
- @Mock
- private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
- @Mock
- private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock
- private lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var uniqueObjectHostView: UniqueObjectHostView
- @Mock
- private lateinit var dreamOverlayStateController: DreamOverlayStateController
+ @Mock private lateinit var lockHost: MediaHost
+ @Mock private lateinit var qsHost: MediaHost
+ @Mock private lateinit var qqsHost: MediaHost
+ @Mock private lateinit var bypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var mediaCarouselController: MediaCarouselController
+ @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
+ @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -94,34 +82,42 @@
@JvmField
@Rule
val mockito = MockitoJUnit.rule()
- private lateinit var mediaHiearchyManager: MediaHierarchyManager
+ private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
+ private val notifPanelEvents = FakeNotifPanelEvents()
+ private val settings = FakeSettings()
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var fakeHandler: FakeHandler
@Before
fun setup() {
context.getOrCreateTestableResources().addOverride(
R.bool.config_use_split_notification_shade, false)
mediaFrame = FrameLayout(context)
- `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
- mediaHiearchyManager = MediaHierarchyManager(
+ testableLooper = TestableLooper.get(this)
+ fakeHandler = FakeHandler(testableLooper.looper)
+ whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
+ mediaHierarchyManager = MediaHierarchyManager(
context,
statusBarStateController,
keyguardStateController,
bypassController,
mediaCarouselController,
- notificationLockscreenUserManager,
+ keyguardViewController,
+ dreamOverlayStateController,
configurationController,
wakefulnessLifecycle,
- keyguardViewController,
- dreamOverlayStateController)
+ notifPanelEvents,
+ settings,
+ fakeHandler,)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
- `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
- `when`(mediaCarouselController.mediaCarouselScrollHandler)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ whenever(mediaCarouselController.mediaCarouselScrollHandler)
.thenReturn(mediaCarouselScrollHandler)
val observer = wakefullnessObserver.value
assertNotNull("lifecycle observer wasn't registered", observer)
@@ -131,30 +127,30 @@
}
private fun setupHost(host: MediaHost, location: Int, top: Int) {
- `when`(host.location).thenReturn(location)
- `when`(host.currentBounds).thenReturn(Rect(0, top, 0, top))
- `when`(host.hostView).thenReturn(uniqueObjectHostView)
- `when`(host.visible).thenReturn(true)
- mediaHiearchyManager.register(host)
+ whenever(host.location).thenReturn(location)
+ whenever(host.currentBounds).thenReturn(Rect(0, top, 0, top))
+ whenever(host.hostView).thenReturn(uniqueObjectHostView)
+ whenever(host.visible).thenReturn(true)
+ mediaHierarchyManager.register(host)
}
@Test
fun testHostViewSetOnRegister() {
- val host = mediaHiearchyManager.register(lockHost)
+ val host = mediaHierarchyManager.register(lockHost)
verify(lockHost).hostView = eq(host)
}
@Test
fun testBlockedWhenScreenTurningOff() {
// Let's set it onto QS:
- mediaHiearchyManager.qsExpansion = 1.0f
+ mediaHierarchyManager.qsExpansion = 1.0f
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
val observer = wakefullnessObserver.value
assertNotNull("lifecycle observer wasn't registered", observer)
observer.onStartedGoingToSleep()
clearInvocations(mediaCarouselController)
- mediaHiearchyManager.qsExpansion = 0.0f
+ mediaHierarchyManager.qsExpansion = 0.0f
verify(mediaCarouselController, times(0))
.onDesiredLocationChanged(ArgumentMatchers.anyInt(),
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
@@ -163,13 +159,13 @@
@Test
fun testAllowedWhenNotTurningOff() {
// Let's set it onto QS:
- mediaHiearchyManager.qsExpansion = 1.0f
+ mediaHierarchyManager.qsExpansion = 1.0f
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
val observer = wakefullnessObserver.value
assertNotNull("lifecycle observer wasn't registered", observer)
clearInvocations(mediaCarouselController)
- mediaHiearchyManager.qsExpansion = 0.0f
+ mediaHierarchyManager.qsExpansion = 0.0f
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
}
@@ -179,7 +175,7 @@
goToLockscreen()
// Let's transition all the way to full shade
- mediaHiearchyManager.setTransitionToFullShadeAmount(100000f)
+ mediaHierarchyManager.setTransitionToFullShadeAmount(100000f)
verify(mediaCarouselController).onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_QQS),
any(MediaHostState::class.java),
@@ -189,7 +185,7 @@
clearInvocations(mediaCarouselController)
// Let's go back to the lock screen
- mediaHiearchyManager.setTransitionToFullShadeAmount(0.0f)
+ mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f)
verify(mediaCarouselController).onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
any(MediaHostState::class.java),
@@ -198,7 +194,7 @@
anyLong())
// Let's make sure alpha is set
- mediaHiearchyManager.setTransitionToFullShadeAmount(2.0f)
+ mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f)
assertThat(mediaFrame.alpha).isNotEqualTo(1.0f)
}
@@ -207,7 +203,26 @@
goToLockscreen()
expandQS()
- val transformType = mediaHiearchyManager.calculateTransformationType()
+ val transformType = mediaHierarchyManager.calculateTransformationType()
+ assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+ }
+
+ @Test
+ fun calculateTransformationType_notOnLockscreen_returnsTransition() {
+ expandQS()
+
+ val transformType = mediaHierarchyManager.calculateTransformationType()
+
+ assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION)
+ }
+
+ @Test
+ fun calculateTransformationType_onLockscreen_returnsTransition() {
+ goToLockscreen()
+ expandQS()
+
+ val transformType = mediaHierarchyManager.calculateTransformationType()
+
assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
}
@@ -216,9 +231,9 @@
enableSplitShade()
goToLockscreen()
expandQS()
- mediaHiearchyManager.setTransitionToFullShadeAmount(10000f)
+ mediaHierarchyManager.setTransitionToFullShadeAmount(10000f)
- val transformType = mediaHiearchyManager.calculateTransformationType()
+ val transformType = mediaHierarchyManager.calculateTransformationType()
assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION)
}
@@ -228,9 +243,9 @@
goToLockscreen()
expandQS()
whenever(lockHost.visible).thenReturn(false)
- mediaHiearchyManager.setTransitionToFullShadeAmount(10000f)
+ mediaHierarchyManager.setTransitionToFullShadeAmount(10000f)
- val transformType = mediaHiearchyManager.calculateTransformationType()
+ val transformType = mediaHierarchyManager.calculateTransformationType()
assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
}
@@ -240,9 +255,9 @@
goToLockscreen()
goToLockedShade()
expandQS()
- mediaHiearchyManager.setTransitionToFullShadeAmount(0f)
+ mediaHierarchyManager.setTransitionToFullShadeAmount(0f)
- val transformType = mediaHiearchyManager.calculateTransformationType()
+ val transformType = mediaHierarchyManager.calculateTransformationType()
assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
}
@@ -251,13 +266,13 @@
goToLockscreen()
goToLockedShade()
- val transformType = mediaHiearchyManager.calculateTransformationType()
+ val transformType = mediaHierarchyManager.calculateTransformationType()
assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
}
@Test
fun testCloseGutsRelayToCarousel() {
- mediaHiearchyManager.closeGuts()
+ mediaHierarchyManager.closeGuts()
verify(mediaCarouselController).closeGuts()
}
@@ -271,7 +286,7 @@
@Test
fun getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumber() {
- assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY()).isLessThan(0)
+ assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()).isLessThan(0)
}
@Test
@@ -279,7 +294,7 @@
enterGuidedTransformation()
val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
- assertThat(mediaHiearchyManager.getGuidedTransformationTranslationY())
+ assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY())
.isEqualTo(expectedTranslation)
}
@@ -291,7 +306,19 @@
whenever(qsHost.visible).thenReturn(true)
whenever(qqsHost.visible).thenReturn(true)
- assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isTrue()
+ assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue()
+ }
+
+ @Test
+ fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
+ notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+ goToLockscreen()
+ enterGuidedTransformation()
+ whenever(lockHost.visible).thenReturn(true)
+ whenever(qsHost.visible).thenReturn(true)
+ whenever(qqsHost.visible).thenReturn(true)
+
+ assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
}
@Test
@@ -302,7 +329,7 @@
whenever(qsHost.visible).thenReturn(true)
whenever(qqsHost.visible).thenReturn(true)
- assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isFalse()
+ assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
}
private fun enableSplitShade() {
@@ -314,9 +341,7 @@
private fun goToLockscreen() {
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
- whenever(notificationLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(
- true
- )
+ settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1)
statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD)
clearInvocations(mediaCarouselController)
}
@@ -330,13 +355,13 @@
}
private fun expandQS() {
- mediaHiearchyManager.qsExpansion = 1.0f
+ mediaHierarchyManager.qsExpansion = 1.0f
}
private fun enterGuidedTransformation() {
- mediaHiearchyManager.qsExpansion = 1.0f
+ mediaHierarchyManager.qsExpansion = 1.0f
goToLockscreen()
- mediaHiearchyManager.setTransitionToFullShadeAmount(123f)
+ mediaHierarchyManager.setTransitionToFullShadeAmount(123f)
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 314997d..6173692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -24,9 +24,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -88,6 +88,7 @@
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -119,7 +120,8 @@
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -276,7 +278,7 @@
}
@Override
- Drawable getAppSourceIcon() {
+ IconCompat getAppSourceIcon() {
return null;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 751c895..6dcf802 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -47,6 +48,7 @@
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
+import android.view.View;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
@@ -57,6 +59,7 @@
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -102,11 +105,16 @@
private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
private AudioManager mAudioManager = mock(AudioManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+ ActivityLaunchAnimator.Controller.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
+ private View mDialogLaunchView = mock(View.class);
+ private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
@@ -131,7 +139,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -183,7 +192,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.start(mCb);
@@ -212,7 +222,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.start(mCb);
@@ -461,7 +472,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -557,4 +569,16 @@
verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
anyLong());
}
+
+ @Test
+ public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() {
+ when(mDialogLaunchAnimator.createActivityLaunchController(mDialogLaunchView)).thenReturn(
+ mActivityLaunchAnimatorController);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mMediaOutputController.mCallback = this.mCallback;
+
+ mMediaOutputController.launchBluetoothPairing(mDialogLaunchView);
+
+ verify(mCallback).dismissDialog();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 4779d32..9557513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.session.MediaController;
@@ -85,6 +86,7 @@
NearbyMediaDevicesManager.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
@@ -104,7 +106,8 @@
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 247316a..c101b9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dream;
+import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -28,6 +30,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaData;
import com.android.systemui.media.MediaDataManager;
@@ -50,6 +53,9 @@
@Mock
MediaDreamComplication mComplication;
+ @Mock
+ FeatureFlags mFeatureFlags;
+
final String mKey = "key";
final String mOldKey = "old_key";
@@ -59,21 +65,18 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+
+ when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(true);
}
@Test
public void testComplicationAddition() {
final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
- mDreamOverlayStateController, mComplication);
+ mDreamOverlayStateController, mComplication, mFeatureFlags);
sentinel.start();
- ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
- ArgumentCaptor.forClass(MediaDataManager.Listener.class);
- verify(mMediaDataManager).addListener(listenerCaptor.capture());
-
- final MediaDataManager.Listener listener = listenerCaptor.getValue();
-
+ final MediaDataManager.Listener listener = captureMediaDataListener();
when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */ true,
/* receivedSmartspaceCardLatency= */ 0, /* isSsReactived= */ false);
@@ -92,4 +95,27 @@
verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
}
+ @Test
+ public void testMediaDreamSentinel_mediaComplicationDisabled_doNotAddComplication() {
+ when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false);
+
+ final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager,
+ mDreamOverlayStateController, mComplication, mFeatureFlags);
+
+ sentinel.start();
+
+ final MediaDataManager.Listener listener = captureMediaDataListener();
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+ listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true,
+ /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false);
+ verify(mDreamOverlayStateController, never()).addComplication(any());
+ }
+
+ private MediaDataManager.Listener captureMediaDataListener() {
+ final ArgumentCaptor<MediaDataManager.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(MediaDataManager.Listener.class);
+ verify(mMediaDataManager).addListener(listenerCaptor.capture());
+
+ return listenerCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 1f28210..e4f47fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.qs
import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -38,38 +38,32 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
+import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class QuickQSPanelControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var quickQSPanel: QuickQSPanel
- @Mock
- private lateinit var qsTileHost: QSTileHost
- @Mock
- private lateinit var qsCustomizerController: QSCustomizerController
- @Mock
- private lateinit var mediaHost: MediaHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var quickQSPanel: QuickQSPanel
+ @Mock private lateinit var qsTileHost: QSTileHost
+ @Mock private lateinit var qsCustomizerController: QSCustomizerController
+ @Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var tile: QSTile
+ @Mock private lateinit var tileLayout: TileLayout
+ @Mock private lateinit var tileView: QSTileView
+ @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
private val uiEventLogger = UiEventLoggerFake()
- @Mock
- private lateinit var qsLogger: QSLogger
private val dumpManager = DumpManager()
- @Mock
- private lateinit var tile: QSTile
- @Mock
- private lateinit var tileLayout: TileLayout
- @Mock
- private lateinit var tileView: QSTileView
- @Captor
- private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
+ private var usingCollapsedLandscapeMedia = true
private lateinit var controller: TestQuickQSPanelController
@@ -77,24 +71,24 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(quickQSPanel.tileLayout).thenReturn(tileLayout)
- `when`(quickQSPanel.isAttachedToWindow).thenReturn(true)
- `when`(quickQSPanel.dumpableTag).thenReturn("")
- `when`(quickQSPanel.resources).thenReturn(mContext.resources)
- `when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
+ whenever(quickQSPanel.tileLayout).thenReturn(tileLayout)
+ whenever(quickQSPanel.isAttachedToWindow).thenReturn(true)
+ whenever(quickQSPanel.dumpableTag).thenReturn("")
+ whenever(quickQSPanel.resources).thenReturn(mContext.resources)
+ whenever(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
- controller = TestQuickQSPanelController(
+ controller =
+ TestQuickQSPanelController(
quickQSPanel,
qsTileHost,
qsCustomizerController,
- false,
+ /* usingMediaPlayer = */ false,
mediaHost,
- true,
+ { usingCollapsedLandscapeMedia },
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
- )
+ dumpManager)
controller.init()
}
@@ -106,9 +100,9 @@
@Test
fun testTileSublistWithFewerTiles_noCrash() {
- `when`(quickQSPanel.numQuickTiles).thenReturn(3)
+ whenever(quickQSPanel.numQuickTiles).thenReturn(3)
- `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+ whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile))
controller.setTiles()
}
@@ -116,8 +110,8 @@
@Test
fun testTileSublistWithTooManyTiles() {
val limit = 3
- `when`(quickQSPanel.numQuickTiles).thenReturn(limit)
- `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+ whenever(quickQSPanel.numQuickTiles).thenReturn(limit)
+ whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
controller.setTiles()
@@ -125,39 +119,61 @@
}
@Test
- fun testMediaExpansionUpdatedWhenConfigurationChanged() {
+ fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
// times(2) because both controller and base controller are registering their listeners
verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
// verify that media starts in the expanded state by default
verify(mediaHost).expansion = MediaHostState.EXPANDED
- // Rotate device, verify media size updated
+ // Rotate device, verify media size updated to collapsed
+ usingCollapsedLandscapeMedia = true
controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
verify(mediaHost).expansion = MediaHostState.COLLAPSED
}
+ @Test
+ fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+ // times(2) because both controller and base controller are registering their listeners
+ verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+ reset(mediaHost)
+
+ usingCollapsedLandscapeMedia = false
+ controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+ captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
+
+ verify(mediaHost).expansion = MediaHostState.EXPANDED
+ }
+
class TestQuickQSPanelController(
view: QuickQSPanel,
qsTileHost: QSTileHost,
qsCustomizerController: QSCustomizerController,
usingMediaPlayer: Boolean,
mediaHost: MediaHost,
- usingCollapsedLandscapeMedia: Boolean,
+ usingCollapsedLandscapeMedia: () -> Boolean,
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
dumpManager: DumpManager
- ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer,
- mediaHost, usingCollapsedLandscapeMedia, metricsLogger, uiEventLogger, qsLogger,
- dumpManager) {
+ ) :
+ QuickQSPanelController(
+ view,
+ qsTileHost,
+ qsCustomizerController,
+ usingMediaPlayer,
+ mediaHost,
+ usingCollapsedLandscapeMedia,
+ metricsLogger,
+ uiEventLogger,
+ qsLogger,
+ dumpManager) {
private var rotation = RotationUtils.ROTATION_NONE
- @Override
- override fun getRotation(): Int = rotation
+ @Override override fun getRotation(): Int = rotation
fun setRotation(newRotation: Int) {
rotation = newRotation
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt
new file mode 100644
index 0000000..447e28c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeImageCapture.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+
+internal class FakeImageCapture : ImageCapture {
+
+ var requestedDisplayId: Int? = null
+ var requestedDisplayCrop: Rect? = null
+ var requestedTaskId: Int? = null
+
+ var image: Bitmap? = null
+
+ override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
+ requestedDisplayId = displayId
+ requestedDisplayCrop = crop
+ return image
+ }
+
+ override suspend fun captureTask(taskId: Int): Bitmap? {
+ requestedTaskId = taskId
+ return image
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
new file mode 100644
index 0000000..28d53c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScreenshotPolicy.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.screenshot
+
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+
+internal class FakeScreenshotPolicy : ScreenshotPolicy {
+
+ private val userTypes = mutableMapOf<Int, Boolean>()
+ private val contentInfo = mutableMapOf<Int, DisplayContentInfo?>()
+
+ fun setManagedProfile(userId: Int, managedUser: Boolean) {
+ userTypes[userId] = managedUser
+ }
+ override suspend fun isManagedProfile(userId: Int): Boolean {
+ return userTypes[userId] ?: error("No managedProfile value set for userId $userId")
+ }
+
+ fun setDisplayContentInfo(userId: Int, contentInfo: DisplayContentInfo) {
+ this.contentInfo[userId] = contentInfo
+ }
+
+ override suspend fun findPrimaryContent(displayId: Int): DisplayContentInfo {
+ return contentInfo[displayId] ?: error("No DisplayContentInfo set for displayId $displayId")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
index ce3f20d..00f3808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +39,10 @@
class ImageCaptureImplTest : SysuiTestCase() {
private val displayManager = mock<DisplayManager>()
private val atmService = mock<IActivityTaskManager>()
- private val capture = TestableImageCaptureImpl(displayManager, atmService)
+ private val capture = TestableImageCaptureImpl(
+ displayManager,
+ atmService,
+ Dispatchers.Unconfined)
@Test
fun captureDisplayWithCrop() {
@@ -59,9 +64,10 @@
class TestableImageCaptureImpl(
displayManager: DisplayManager,
- atmService: IActivityTaskManager
+ atmService: IActivityTaskManager,
+ bgDispatcher: CoroutineDispatcher
) :
- ImageCaptureImpl(displayManager, atmService) {
+ ImageCaptureImpl(displayManager, atmService, bgDispatcher) {
var token: IBinder? = null
var width: Int? = null
@@ -81,4 +87,4 @@
return ScreenshotHardwareBuffer(null, null, false, false)
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 024d3bd..48fbd35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -22,86 +22,253 @@
import android.graphics.Insets
import android.graphics.Rect
import android.hardware.HardwareBuffer
-import android.net.Uri
+import android.os.Bundle
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-
+import android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION
import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
+import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
-import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
import org.junit.Test
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.isNull
+
+private const val USER_ID = 1
+private const val TASK_ID = 1
class RequestProcessorTest {
- private val controller = mock<ScreenshotController>()
- private val bitmapCaptor = argumentCaptor<Bitmap>()
+ private val imageCapture = FakeImageCapture()
+ private val component = ComponentName("android.test", "android.test.Component")
+ private val bounds = Rect(25, 25, 75, 75)
+ private val scope = CoroutineScope(Dispatchers.Unconfined)
+ private val dispatcher = Dispatchers.Unconfined
+ private val policy = FakeScreenshotPolicy()
+ private val flags = FakeFeatureFlags()
+
+ /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
@Test
- fun testFullScreenshot() {
+ fun testProcessAsync() {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
- val onSavedListener = mock<Consumer<Uri>>()
- val callback = mock<RequestCallback>()
- val processor = RequestProcessor(controller)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
- processor.processRequest(request, onSavedListener, callback)
+ var result: ScreenshotRequest? = null
+ var callbackCount = 0
+ val callback: (ScreenshotRequest) -> Unit = { processedRequest: ScreenshotRequest ->
+ result = processedRequest
+ callbackCount++
+ }
- verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
- eq(onSavedListener), eq(callback))
+ // runs synchronously, using Unconfined Dispatcher
+ processor.processAsync(request, callback)
+
+ // Callback invoked once returning the same request (no changes)
+ assertThat(callbackCount).isEqualTo(1)
+ assertThat(result).isEqualTo(request)
}
@Test
- fun testSelectedRegionScreenshot() {
+ fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ val processedRequest = processor.process(request)
+
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ @Test
+ fun testFullScreenshot() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ // Indicate that the primary content belongs to a normal user
+ policy.setManagedProfile(USER_ID, false)
+ policy.setDisplayContentInfo(
+ policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ val processedRequest = processor.process(request)
+
+ // Request has topComponent added, but otherwise unchanged.
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
+ }
+
+ @Test
+ fun testFullScreenshot_managedProfile() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ // Provide a fake task bitmap when asked
+ val bitmap = makeHardwareBitmap(100, 100)
+ imageCapture.image = bitmap
+
+ // Indicate that the primary content belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+ policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ val processedRequest = processor.process(request)
+
+ // Expect a task snapshot is taken, overriding the full screen mode
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+ assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+ assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
+ assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
+ assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
+ assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+ assertThat(processedRequest.userId).isEqualTo(USER_ID)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
+ }
+
+ @Test
+ fun testSelectedRegionScreenshot_workProfilePolicyDisabled() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
- val onSavedListener = mock<Consumer<Uri>>()
- val callback = mock<RequestCallback>()
- val processor = RequestProcessor(controller)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
- processor.processRequest(request, onSavedListener, callback)
+ val processedRequest = processor.process(request)
- verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
- eq(onSavedListener), eq(callback))
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ @Test
+ fun testSelectedRegionScreenshot() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ policy.setManagedProfile(USER_ID, false)
+ policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+ val processedRequest = processor.process(request)
+
+ // Request has topComponent added, but otherwise unchanged.
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
}
@Test
- fun testProvidedImageScreenshot() {
- val taskId = 1111
- val userId = 2222
- val bounds = Rect(50, 50, 150, 150)
- val topComponent = ComponentName("test", "test")
- val processor = RequestProcessor(controller)
+ fun testSelectedRegionScreenshot_managedProfile() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
- val buffer = HardwareBuffer.create(100, 100, HardwareBuffer.RGBA_8888, 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
- val bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+ // Provide a fake task bitmap when asked
+ val bitmap = makeHardwareBitmap(100, 100)
+ imageCapture.image = bitmap
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_SELECTED_REGION, SCREENSHOT_KEY_CHORD)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ // Indicate that the primary content belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+ policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, USER_ID, TASK_ID))
+
+ val processedRequest = processor.process(request)
+
+ // Expect a task snapshot is taken, overriding the selected region mode
+ assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+ assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+ assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
+ assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
+ assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
+ assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+ assertThat(processedRequest.userId).isEqualTo(USER_ID)
+ assertThat(processedRequest.topComponent).isEqualTo(component)
+ }
+
+ @Test
+ fun testProvidedImageScreenshot_workProfilePolicyDisabled() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+ val bounds = Rect(50, 50, 150, 150)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ val bitmap = makeHardwareBitmap(100, 100)
val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, taskId, userId, topComponent)
+ bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
- val onSavedListener = mock<Consumer<Uri>>()
- val callback = mock<RequestCallback>()
+ val processedRequest = processor.process(request)
- processor.processRequest(request, onSavedListener, callback)
-
- verify(controller).handleImageAsScreenshot(
- bitmapCaptor.capture(), eq(bounds), eq(Insets.NONE), eq(taskId), eq(userId),
- eq(topComponent), eq(onSavedListener), eq(callback)
- )
-
- assertThat(bitmapCaptor.value.equalsHardwareBitmap(bitmap)).isTrue()
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
}
- private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
- return bitmap.hardwareBuffer == this.hardwareBuffer &&
- bitmap.colorSpace == this.colorSpace
+ @Test
+ fun testProvidedImageScreenshot() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ val bounds = Rect(50, 50, 150, 150)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ policy.setManagedProfile(USER_ID, false)
+
+ val bitmap = makeHardwareBitmap(100, 100)
+ val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+ bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+
+ val processedRequest = processor.process(request)
+
+ // No changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ @Test
+ fun testProvidedImageScreenshot_managedProfile() = runBlocking {
+ flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+ val bounds = Rect(50, 50, 150, 150)
+ val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+ // Indicate that the screenshot belongs to a manged profile
+ policy.setManagedProfile(USER_ID, true)
+
+ val bitmap = makeHardwareBitmap(100, 100)
+ val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+ val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
+ bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+
+ val processedRequest = processor.process(request)
+
+ // Work profile, but already a task snapshot, so no changes
+ assertThat(processedRequest).isEqualTo(request)
+ }
+
+ private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
+ val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+ return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
+ }
+
+ private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
+ val provided = bundleToHardwareBitmap(bundle)
+ return provided.hardwareBuffer == this.hardwareBuffer &&
+ provided.colorSpace == this.colorSpace
}
}
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 3f4e2a9..95211c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -238,6 +238,8 @@
@Mock
private DozeLog mDozeLog;
@Mock
+ private ShadeLogger mShadeLog;
+ @Mock
private CommandQueue mCommandQueue;
@Mock
private VibratorHelper mVibratorHelper;
@@ -524,7 +526,9 @@
mNotificationShadeWindowController,
mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
- mMetricsLogger, mConfigurationController,
+ mMetricsLogger,
+ mShadeLog,
+ mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHiearchyManager,
mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
new file mode 100644
index 0000000..d052138
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.testing
+
+import com.android.systemui.shade.NotifPanelEvents
+
+/** Fake implementation of [NotifPanelEvents] for testing. */
+class FakeNotifPanelEvents : NotifPanelEvents {
+
+ private val listeners = mutableListOf<NotifPanelEvents.Listener>()
+
+ override fun registerListener(listener: NotifPanelEvents.Listener) {
+ listeners.add(listener)
+ }
+
+ override fun unregisterListener(listener: NotifPanelEvents.Listener) {
+ listeners.remove(listener)
+ }
+
+ fun changeExpandImmediate(expandImmediate: Boolean) {
+ listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
index 64d0256..214ba16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
@@ -98,7 +98,7 @@
@Test
fun testPrepareDialogForApp_onlyDefaultChannel() {
- group.addChannel(channelDefault)
+ group.channels = listOf(channelDefault)
controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
setOf(channelDefault), appIcon, clickListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index c25737d..cc4cbbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -48,6 +48,7 @@
import android.widget.RemoteViews;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
@@ -75,6 +76,7 @@
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
@@ -145,7 +147,9 @@
mock(GroupMembershipManager.class),
mock(VisualStabilityProvider.class),
mock(ConfigurationControllerImpl.class),
- new Handler(mTestLooper.getLooper())
+ new Handler(mTestLooper.getLooper()),
+ mock(AccessibilityManagerWrapper.class),
+ mock(UiEventLogger.class)
);
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index ac3d0c2..e252401 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
@@ -65,6 +66,8 @@
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private KeyguardBypassController mBypassController;
@Mock private ConfigurationControllerImpl mConfigurationController;
+ @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
+ @Mock private UiEventLogger mUiEventLogger;
private boolean mLivesPastNormalTime;
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
@@ -76,7 +79,9 @@
StatusBarStateController statusBarStateController,
KeyguardBypassController keyguardBypassController,
ConfigurationController configurationController,
- Handler handler
+ Handler handler,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ UiEventLogger uiEventLogger
) {
super(
context,
@@ -86,7 +91,9 @@
groupManager,
visualStabilityProvider,
configurationController,
- handler
+ handler,
+ accessibilityManagerWrapper,
+ uiEventLogger
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
@@ -116,7 +123,9 @@
mStatusBarStateController,
mBypassController,
mConfigurationController,
- mTestHandler
+ mTestHandler,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
index 515a7c9..7b492cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
@@ -20,7 +20,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -59,6 +59,7 @@
context,
scope,
statusBarPipelineFlags,
+ mock(),
)
var mostRecentValue: ProcessedConnectivityInfo? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 2915ae8..36be1be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline
+package com.android.systemui.statusbar.pipeline.shared
import android.net.Network
import android.net.NetworkCapabilities
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
new file mode 100644
index 0000000..df389bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.pipeline.wifi.data.repository
+
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
+class FakeWifiRepository : WifiRepository {
+ private val _wifiModel: MutableStateFlow<WifiModel?> = MutableStateFlow(null)
+ override val wifiModel: Flow<WifiModel?> = _wifiModel
+
+ private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
+ override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
+
+ fun setWifiModel(wifiModel: WifiModel?) {
+ _wifiModel.value = wifiModel
+ }
+
+ fun setWifiActivity(activity: WifiActivityModel) {
+ _wifiActivity.value = activity
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt
index 40f8fbf..6edf76c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.repository
+package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
@@ -28,7 +28,7 @@
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
new file mode 100644
index 0000000..8b61364
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.pipeline.wifi.data.repository
+
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiManager.TrafficStateCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: WifiRepositoryImpl
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var wifiManager: WifiManager
+ private lateinit var executor: Executor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+ }
+
+ @Test
+ fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager = null,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(ACTIVITY_DEFAULT)
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
+
+ assertThat(latest).isEqualTo(
+ WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
+
+ assertThat(latest).isEqualTo(
+ WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
+
+ assertThat(latest).isEqualTo(
+ WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
+
+ assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ job.cancel()
+ }
+
+ private fun getTrafficStateCallback(): TrafficStateCallback {
+ val callbackCaptor = argumentCaptor<TrafficStateCallback>()
+ verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
new file mode 100644
index 0000000..c52f347
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.pipeline.wifi.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: WifiInteractor
+
+ private lateinit var repository: FakeWifiRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeWifiRepository()
+ underTest = WifiInteractor(repository)
+ }
+
+ @Test
+ fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = null))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Conduct a series of changes and verify we catch each of them in succession
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+ assertThat(latest).isTrue()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+ yield()
+ assertThat(latest).isFalse()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+ yield()
+ assertThat(latest).isTrue()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+ assertThat(latest).isTrue()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+ yield()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
new file mode 100644
index 0000000..e9259b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.pipeline.wifi.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiViewModelTest : SysuiTestCase() {
+
+ private lateinit var underTest: WifiViewModel
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var constants: WifiConstants
+ private lateinit var repository: FakeWifiRepository
+ private lateinit var interactor: WifiInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ repository = FakeWifiRepository()
+ interactor = WifiInteractor(repository)
+
+ underTest = WifiViewModel(
+ constants,
+ logger,
+ interactor
+ )
+
+ // Set up with a valid SSID
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ }
+
+ @Test
+ fun activityInVisible_showActivityConfigFalse_receivesFalse() = runBlocking(IMMEDIATE) {
+ whenever(constants.shouldShowActivityConfig).thenReturn(false)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .isActivityInVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Verify that on launch, we receive a false.
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
+ whenever(constants.shouldShowActivityConfig).thenReturn(false)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .isActivityInVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Update the repo to have activityIn
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+
+ // Verify that we didn't update to activityIn=true (because our config is false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityInVisible_showActivityConfigTrue_receivesUpdate() = runBlocking(IMMEDIATE) {
+ whenever(constants.shouldShowActivityConfig).thenReturn(true)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .isActivityInVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Update the repo to have activityIn
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+
+ // Verify that we updated to activityIn=true
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index e2b9a9e..9764b8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -63,7 +63,6 @@
private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
- private AccessibilityManagerWrapper mAccessibilityMgr;
private HeadsUpManager mHeadsUpManager;
private boolean mLivesPastNormalTime;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@@ -72,10 +71,15 @@
@Mock private StatusBarNotification mSbn;
@Mock private Notification mNotification;
@Mock private HeadsUpManagerLogger mLogger;
+ @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
private final class TestableHeadsUpManager extends HeadsUpManager {
- TestableHeadsUpManager(Context context, HeadsUpManagerLogger logger, Handler handler) {
- super(context, logger, handler);
+ TestableHeadsUpManager(Context context,
+ HeadsUpManagerLogger logger,
+ Handler handler,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ UiEventLogger uiEventLogger) {
+ super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
}
@@ -88,13 +92,11 @@
@Before
public void setUp() {
initMocks(this);
- mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
- mDependency.injectTestDependency(UiEventLogger.class, mUiEventLoggerFake);
when(mEntry.getSbn()).thenReturn(mSbn);
when(mSbn.getNotification()).thenReturn(mNotification);
-
super.setUp();
- mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler);
+ mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
+ mAccessibilityMgr, mUiEventLoggerFake);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b7f38f1..50259b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -310,7 +310,7 @@
}
@Test
- public void onWallpaperColorsChanged_ResetThemeWithNewHomeWallpapers() {
+ public void onWallpaperColorsChanged_resetThemeWithNewHomeWallpapers() {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
@@ -345,6 +345,61 @@
}
@Test
+ public void onWallpaperColorsChanged_keepsThemeWhenSetFromLockScreen() {
+ // Should ask for a new theme when wallpaper colors change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(20);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(21);
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+ verify(mSecureSettings, never()).putStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), any(), anyInt());
+ }
+
+ @Test
+ public void onWallpaperColorsChanged_resetLockScreenThemeWhenBothSet() {
+ // Should ask for a new theme when wallpaper colors change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(20);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(21);
+
+ mColorsListener.getValue().onColorsChanged(mainColors,
+ WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+ anyInt());
+ assertThat(updatedSetting.getValue().contains(
+ "android.theme.customization.color_both\":\"1")).isTrue();
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ }
+
+ @Test
public void onSettingChanged_honorThemeStyle() {
when(mDeviceProvisionedController.isUserSetup(anyInt())).thenReturn(true);
List<Style> validStyles = Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, Style.TONAL_SPOT,
@@ -381,7 +436,7 @@
}
@Test
- public void onWallpaperColorsChanged_ResetThemeWithNewHomeAndLockWallpaper() {
+ public void onWallpaperColorsChanged_resetThemeWithNewHomeAndLockWallpaper() {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
@@ -450,7 +505,7 @@
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ "\"android.theme.customization.system_palette\":\"A16B00\","
+ "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
@@ -476,7 +531,7 @@
}
@Test
- public void onWallpaperColorsChanged_ResetThemeWhenFromLatestWallpaper() {
+ public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
// Should ask for a new theme when the colors of the last applied wallpaper change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 18acf3f..8f2b715 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -63,6 +63,7 @@
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.dreams.IDreamManager;
@@ -204,6 +205,8 @@
private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
@Captor
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
private BubblesManager mBubblesManager;
private TestableBubbleController mBubbleController;
@@ -240,6 +243,8 @@
@Mock
private IStatusBarService mStatusBarService;
@Mock
+ private IDreamManager mIDreamManager;
+ @Mock
private NotificationVisibilityProvider mVisibilityProvider;
@Mock
private LauncherApps mLauncherApps;
@@ -371,10 +376,11 @@
mContext,
mBubbleController.asBubbles(),
mNotificationShadeWindowController,
- mock(KeyguardStateController.class),
+ mKeyguardStateController,
mShadeController,
mStatusBarService,
mock(INotificationManager.class),
+ mIDreamManager,
mVisibilityProvider,
interruptionStateProvider,
mZenModeController,
@@ -391,6 +397,25 @@
verify(mNotifPipeline, atLeastOnce())
.addCollectionListener(mNotifListenerCaptor.capture());
mEntryListener = mNotifListenerCaptor.getValue();
+
+ // Get a reference to KeyguardStateController.Callback
+ verify(mKeyguardStateController, atLeastOnce())
+ .addCallback(mKeyguardStateControllerCallbackCaptor.capture());
+ }
+
+ @Test
+ public void dreamingHidesBubbles() throws RemoteException {
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertTrue(mBubbleController.hasBubbles());
+ assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE);
+
+ when(mIDreamManager.isDreamingOrInPreview()).thenReturn(true); // dreaming is happening
+ when(mKeyguardStateController.isShowing()).thenReturn(false); // device is unlocked
+ KeyguardStateController.Callback callback =
+ mKeyguardStateControllerCallbackCaptor.getValue();
+ callback.onKeyguardShowingChanged();
+
+ assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 309acdf..f539dbd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -91,6 +91,8 @@
fun capture(): T = wrapped.capture()
val value: T
get() = wrapped.value
+ val allValues: List<T>
+ get() = wrapped.allValues
}
/**
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index fe85db2..5a35474 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -231,7 +231,7 @@
sendStateToClients(/* resetClient= */ false);
}
updateRemoteAugmentedAutofillService();
- updateRemoteInlineSuggestionRenderServiceLocked();
+ getRemoteInlineSuggestionRenderServiceLocked();
return enabledChanged;
}
@@ -685,8 +685,12 @@
@GuardedBy("mLock")
void resetExtServiceLocked() {
- if (sVerbose) Slog.v(TAG, "reset autofill service.");
+ if (sVerbose) Slog.v(TAG, "reset autofill service in ExtServices.");
mFieldClassificationStrategy.reset();
+ if (mRemoteInlineSuggestionRenderService != null) {
+ mRemoteInlineSuggestionRenderService.destroy();
+ mRemoteInlineSuggestionRenderService = null;
+ }
}
@GuardedBy("mLock")
@@ -1583,18 +1587,6 @@
return mFieldClassificationStrategy.getDefaultAlgorithm();
}
- private void updateRemoteInlineSuggestionRenderServiceLocked() {
- if (mRemoteInlineSuggestionRenderService != null) {
- if (sVerbose) {
- Slog.v(TAG, "updateRemoteInlineSuggestionRenderService(): "
- + "destroying old remote service");
- }
- mRemoteInlineSuggestionRenderService = null;
- }
-
- mRemoteInlineSuggestionRenderService = getRemoteInlineSuggestionRenderServiceLocked();
- }
-
@Nullable RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderServiceLocked() {
if (mRemoteInlineSuggestionRenderService == null) {
final ComponentName componentName = RemoteInlineSuggestionRenderService
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 243a7e0..907daa3 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -160,7 +160,6 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
@@ -3401,7 +3400,8 @@
}
/**
- * Selects transport {@code transportName} and returns previously selected transport.
+ * Selects transport {@code transportName}, if it is already registered, and returns previously
+ * selected transport. Returns {@code null} if the transport is not registered.
*
* @deprecated Use {@link #selectBackupTransportAsync(ComponentName,
* ISelectBackupTransportCallback)} instead.
@@ -3414,6 +3414,17 @@
final long oldId = Binder.clearCallingIdentity();
try {
+ if (!mTransportManager.isTransportRegistered(transportName)) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Could not select transport "
+ + transportName
+ + ", as the transport is not registered."));
+ return null;
+ }
+
String previousTransportName = mTransportManager.selectTransport(transportName);
updateStateForTransport(transportName);
Slog.v(
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 43e2b88..593a63c 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -77,6 +77,14 @@
new ComponentName("android", BlockedAppStreamingActivity.class.getName());
/**
+ * For communicating when a secure window shows on the virtual display.
+ */
+ public interface SecureWindowCallback {
+ /** Called when a secure window shows on the virtual display. */
+ void onSecureWindowShown(int displayId, int uid);
+ }
+
+ /**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
*/
@@ -108,6 +116,7 @@
new ArraySet<>();
@Nullable
private final @AssociationRequest.DeviceProfile String mDeviceProfile;
+ @Nullable private final SecureWindowCallback mSecureWindowCallback;
/**
* Creates a window policy controller that is generic to the different use cases of virtual
@@ -131,6 +140,8 @@
* @param activityListener Activity listener to listen for activity changes.
* @param activityBlockedCallback Callback that is called when an activity is blocked from
* launching.
+ * @param secureWindowCallback Callback that is called when a secure window shows on the
+ * virtual display.
* @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
*/
public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@@ -142,6 +153,7 @@
@ActivityPolicy int defaultActivityPolicy,
@NonNull ActivityListener activityListener,
@NonNull ActivityBlockedCallback activityBlockedCallback,
+ @NonNull SecureWindowCallback secureWindowCallback,
@AssociationRequest.DeviceProfile String deviceProfile) {
super();
mAllowedUsers = allowedUsers;
@@ -154,6 +166,7 @@
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
mDeviceProfile = deviceProfile;
+ mSecureWindowCallback = secureWindowCallback;
}
/**
@@ -234,6 +247,12 @@
@Override
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
+ // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
+ // aware that the virtual display has a secure window on top.
+ if ((windowFlags & FLAG_SECURE) != 0) {
+ mSecureWindowCallback.onSecureWindowShown(mDisplayId, activityInfo.applicationInfo.uid);
+ }
+
if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) {
mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
return false;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5f337ab..cca3212 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -52,6 +52,7 @@
import android.hardware.input.VirtualTouchEvent;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -542,6 +543,7 @@
mParams.getDefaultActivityPolicy(),
createListenerAdapter(),
this::onActivityBlocked,
+ this::onSecureWindowShown,
mAssociationInfo.getDeviceProfile());
gwpc.registerRunningAppsChangedListener(/* listener= */ this);
return gwpc;
@@ -591,6 +593,21 @@
mContext.getUser());
}
+ private void onSecureWindowShown(int displayId, int uid) {
+ if (!mVirtualDisplayIds.contains(displayId)) {
+ return;
+ }
+
+ // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
+ // if the secure window is shown on a non-secure virtual display.
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ Display display = displayManager.getDisplay(displayId);
+ if ((display.getFlags() & FLAG_SECURE) == 0) {
+ showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
+ Toast.LENGTH_LONG, mContext.getMainLooper());
+ }
+ }
+
private ArraySet<UserHandle> getAllowedUserHandles() {
ArraySet<UserHandle> result = new ArraySet<>();
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
@@ -650,14 +667,16 @@
/**
* Shows a toast on virtual displays owned by this device which have a given uid running.
*/
- void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) {
- showToastWhereUidIsRunning(uid, mContext.getString(resId), duration);
+ void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration,
+ Looper looper) {
+ showToastWhereUidIsRunning(uid, mContext.getString(resId), duration, looper);
}
/**
* Shows a toast on virtual displays owned by this device which have a given uid running.
*/
- void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration) {
+ void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
+ Looper looper) {
synchronized (mVirtualDeviceLock) {
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final int size = mWindowPolicyControllers.size();
@@ -666,7 +685,7 @@
int displayId = mWindowPolicyControllers.keyAt(i);
Display display = displayManager.getDisplay(displayId);
if (display != null && display.isValid()) {
- Toast.makeText(mContext.createDisplayContext(display), text,
+ Toast.makeText(mContext.createDisplayContext(display), looper, text,
duration).show();
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b255188..cdddc1d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -203,7 +203,7 @@
getContext().getString(
com.android.internal.R.string.vdm_camera_access_denied,
deviceName),
- Toast.LENGTH_LONG);
+ Toast.LENGTH_LONG, Looper.myLooper());
}
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 4a3f682a..5b1f740 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -19,12 +19,10 @@
import static android.Manifest.permission.ACCESS_MTP;
import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
-import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -4376,11 +4374,7 @@
}
}
- // Determine if caller is holding runtime permission
- final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
- uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE);
-
- // We're only willing to give out installer access if they also hold
+ // We're only willing to give out installer access if they hold
// runtime permission; this is a firm CDD requirement
final boolean hasInstall = mIPackageManager.checkUidPermission(INSTALL_PACKAGES,
uid) == PERMISSION_GRANTED;
@@ -4396,7 +4390,7 @@
break;
}
}
- if ((hasInstall || hasInstallOp) && hasWrite) {
+ if (hasInstall || hasInstallOp) {
return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER;
}
return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 54a7811..297e6a2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -459,6 +459,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -4304,7 +4305,8 @@
null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), userId, false /* allowBackgroundActivityStarts */,
- null /* backgroundActivityStartsToken */, broadcastAllowList);
+ null /* backgroundActivityStartsToken */,
+ broadcastAllowList, null /* filterExtrasForReceiver */);
}
private void cleanupDisabledPackageComponentsLocked(
@@ -13357,7 +13359,8 @@
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
receivers, null, 0, null, null, false, true, true, -1, false, null,
- false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
+ false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+ null /* filterExtrasForReceiver */);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -13620,7 +13623,8 @@
excludedPermissions, excludedPackages, appOp, bOptions, ordered, sticky, callingPid,
callingUid, realCallingUid, realCallingPid, userId,
false /* allowBackgroundActivityStarts */,
- null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
+ null /* tokenNeededForBackgroundActivityStarts */,
+ null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
}
@GuardedBy("this")
@@ -13633,7 +13637,8 @@
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
- @Nullable int[] broadcastAllowList) {
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
intent = new Intent(intent);
final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
@@ -14233,7 +14238,7 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered,
sticky, false, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, timeoutExempt);
+ backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending
&& (queue.replaceParallelBroadcastLocked(r) != null);
@@ -14331,7 +14336,7 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
receivers, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, false, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, timeoutExempt);
+ backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
@@ -14523,7 +14528,8 @@
resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
null, OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
realCallingPid, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, broadcastAllowList);
+ backgroundActivityStartsToken, broadcastAllowList,
+ null /* filterExtrasForReceiver */);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -16201,6 +16207,13 @@
return mUserController.startUser(userId, /* foreground */ true, unlockListener);
}
+ @Override
+ public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
+ // Permission check done inside UserController.
+ return mUserController.startUserOnSecondaryDisplay(userId, displayId,
+ /* unlockListener= */ null);
+ }
+
/**
* Unlocks the given user.
*
@@ -17058,7 +17071,9 @@
public int broadcastIntent(Intent intent,
IIntentReceiver resultTo,
String[] requiredPermissions,
- boolean serialized, int userId, int[] appIdAllowList, @Nullable Bundle bOptions) {
+ boolean serialized, int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions) {
synchronized (ActivityManagerService.this) {
intent = verifyBroadcastLocked(intent);
@@ -17074,7 +17089,8 @@
AppOpsManager.OP_NONE, bOptions /*options*/, serialized,
false /*sticky*/, callingPid, callingUid, callingUid, callingPid,
userId, false /*allowBackgroundStarts*/,
- null /*tokenNeededForBackgroundActivityStarts*/, appIdAllowList);
+ null /*tokenNeededForBackgroundActivityStarts*/,
+ appIdAllowList, filterExtrasForReceiver);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4bbfa3e..5cb25d3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -892,6 +892,7 @@
}
}
+ // TODO(b/239982558): might need to support --displayId as well
private int runProfile(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
String profileFile = null;
@@ -2034,26 +2035,42 @@
int runStartUser(PrintWriter pw) throws RemoteException {
boolean wait = false;
String opt;
+ int displayId = Display.INVALID_DISPLAY;
while ((opt = getNextOption()) != null) {
- if ("-w".equals(opt)) {
- wait = true;
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return -1;
+ switch(opt) {
+ case "-w":
+ wait = true;
+ break;
+ case "--display":
+ displayId = getDisplayIdFromNextArg();
+ break;
+ default:
+ getErrPrintWriter().println("Error: unknown option: " + opt);
+ return -1;
}
}
int userId = Integer.parseInt(getNextArgRequired());
final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
- boolean success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+
+ boolean success;
+ String displaySuffix;
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+ displaySuffix = "";
+ } else {
+ success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+ displaySuffix = " on display " + displayId;
+ }
if (wait && success) {
success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
}
if (success) {
- pw.println("Success: user started");
+ pw.println("Success: user started" + displaySuffix);
} else {
- getErrPrintWriter().println("Error: could not start user");
+ getErrPrintWriter().println("Error: could not start user" + displaySuffix);
}
return 0;
}
@@ -2506,6 +2523,14 @@
}
}
+ private int getDisplayIdFromNextArg() {
+ int displayId = Integer.parseInt(getNextArgRequired());
+ if (displayId < 0) {
+ throw new IllegalArgumentException("--display must be a non-negative integer");
+ }
+ return displayId;
+ }
+
int runGetConfig(PrintWriter pw) throws RemoteException {
int days = -1;
int displayId = Display.DEFAULT_DISPLAY;
@@ -2524,10 +2549,7 @@
} else if (opt.equals("--device")) {
inclDevice = true;
} else if (opt.equals("--display")) {
- displayId = Integer.parseInt(getNextArgRequired());
- if (displayId < 0) {
- throw new IllegalArgumentException("--display must be a non-negative integer");
- }
+ displayId = getDisplayIdFromNextArg();
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -3714,10 +3736,13 @@
pw.println(" execution of that user if it is currently stopped.");
pw.println(" get-current-user");
pw.println(" Returns id of the current foreground user.");
- pw.println(" start-user [-w] <USER_ID>");
+ pw.println(" start-user [-w] [--display DISPLAY_ID] <USER_ID>");
pw.println(" Start USER_ID in background if it is currently stopped;");
pw.println(" use switch-user if you want to start the user in foreground.");
pw.println(" -w: wait for start-user to complete and the user to be unlocked.");
+ pw.println(" --display <DISPLAY_ID>: allows the user to launch activities in the");
+ pw.println(" given display, when supported (typically on automotive builds");
+ pw.println(" wherethe vehicle has multiple displays)");
pw.println(" unlock-user <USER_ID>");
pw.println(" Unlock the given user. This will only work if the user doesn't");
pw.println(" have an LSKF (PIN/pattern/password).");
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 9028eef..31d9f96 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -363,8 +363,8 @@
+ ": " + r);
mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
- null /* compatInfo (unused but need to keep method signature) */,
+ thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
+ r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
app.mState.getReportedProcState());
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
@@ -592,6 +592,7 @@
r.curFilter = null;
r.curReceiver = null;
r.curApp = null;
+ r.curFilteredExtras = null;
mPendingBroadcast = null;
r.resultCode = resultCode;
@@ -943,6 +944,24 @@
skip = true;
}
+ // Filter packages in the intent extras, skipping delivery if none of the packages is
+ // visible to the receiver.
+ Bundle filteredExtras = null;
+ if (!skip && r.filterExtrasForReceiver != null) {
+ final Bundle extras = r.intent.getExtras();
+ if (extras != null) {
+ filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
+ if (filteredExtras == null) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Skipping delivery to "
+ + filter.receiverList.app
+ + " : receiver is filtered by the package visibility");
+ }
+ skip = true;
+ }
+ }
+ }
+
if (skip) {
r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
return;
@@ -1000,7 +1019,7 @@
maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
- new Intent(r.intent), r.resultCode, r.resultData,
+ prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
r.resultExtras, r.ordered, r.initialSticky, r.userId,
filter.receiverList.uid, r.callingUid,
r.dispatchTime - r.enqueueTime,
@@ -1167,6 +1186,15 @@
+ ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
}
+ private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
+ @Nullable Bundle filteredExtras) {
+ final Intent intent = new Intent(originalIntent);
+ if (filteredExtras != null) {
+ intent.replaceExtras(filteredExtras);
+ }
+ return intent;
+ }
+
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
BroadcastRecord r;
@@ -1837,6 +1865,24 @@
}
}
+ // Filter packages in the intent extras, skipping delivery if none of the packages is
+ // visible to the receiver.
+ Bundle filteredExtras = null;
+ if (!skip && r.filterExtrasForReceiver != null) {
+ final Bundle extras = r.intent.getExtras();
+ if (extras != null) {
+ filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
+ if (filteredExtras == null) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Skipping delivery to "
+ + info.activityInfo.packageName + " / " + receiverUid
+ + " : receiver is filtered by the package visibility");
+ }
+ skip = true;
+ }
+ }
+ }
+
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
@@ -1855,6 +1901,7 @@
r.state = BroadcastRecord.APP_RECEIVE;
r.curComponent = component;
r.curReceiver = info.activityInfo;
+ r.curFilteredExtras = filteredExtras;
if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+ info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
@@ -1922,8 +1969,7 @@
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction(), (r.alarm ? HostingRecord.TRIGGER_TYPE_ALARM
- : HostingRecord.TRIGGER_TYPE_UNKNOWN)),
+ r.intent.getAction(), getHostingRecordTriggerType(r)),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
if (r.curApp == null) {
@@ -1946,6 +1992,16 @@
mPendingBroadcastRecvIndex = recIdx;
}
+ private String getHostingRecordTriggerType(BroadcastRecord r) {
+ if (r.alarm) {
+ return HostingRecord.TRIGGER_TYPE_ALARM;
+ } else if (r.pushMessage) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+ } else if (r.pushMessageOverQuota) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+ }
+ return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+ }
@Nullable
private String getTargetPackage(BroadcastRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ce4528b..18fbfde 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -55,6 +55,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
/**
* An active intent broadcast.
@@ -71,6 +72,8 @@
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean alarm; // originated from an alarm triggering?
+ final boolean pushMessage; // originated from a push message?
+ final boolean pushMessageOverQuota; // originated from a push message which was over quota?
final boolean initialSticky; // initial broadcast from register to sticky?
final int userId; // user id this broadcast was for
final String resolvedType; // the resolved data type
@@ -114,6 +117,11 @@
@Nullable
final IBinder mBackgroundActivityStartsToken;
+ // Filter the intent extras by using the rules of the package visibility before broadcasting
+ // the intent to the receiver.
+ @Nullable
+ final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
+
static final int IDLE = 0;
static final int APP_RECEIVE = 1;
static final int CALL_IN_RECEIVE = 2;
@@ -134,6 +142,7 @@
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // info about the receiver that is currently running.
+ Bundle curFilteredExtras; // the bundle that has been filtered by the package visibility rules
boolean mIsReceiverAppRunning; // Was the receiver's app already running.
@@ -227,6 +236,9 @@
pw.println(curReceiver.applicationInfo.sourceDir);
}
}
+ if (curFilteredExtras != null) {
+ pw.print(" filtered extras: "); pw.println(curFilteredExtras);
+ }
if (state != IDLE) {
String stateStr = " (?)";
switch (state) {
@@ -273,7 +285,8 @@
BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
- @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt) {
+ @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
@@ -309,6 +322,9 @@
mBackgroundActivityStartsToken = backgroundActivityStartsToken;
this.timeoutExempt = timeoutExempt;
alarm = options != null && options.isAlarmBroadcast();
+ pushMessage = options != null && options.isPushMessagingBroadcast();
+ pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
+ this.filterExtrasForReceiver = filterExtrasForReceiver;
}
/**
@@ -362,6 +378,9 @@
mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
timeoutExempt = from.timeoutExempt;
alarm = from.alarm;
+ pushMessage = from.pushMessage;
+ pushMessageOverQuota = from.pushMessageOverQuota;
+ filterExtrasForReceiver = from.filterExtrasForReceiver;
}
/**
@@ -397,7 +416,7 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
splitReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky,
initialSticky, userId, allowBackgroundActivityStarts,
- mBackgroundActivityStartsToken, timeoutExempt);
+ mBackgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
split.enqueueTime = this.enqueueTime;
split.enqueueRealTime = this.enqueueRealTime;
split.enqueueClockTime = this.enqueueClockTime;
@@ -476,7 +495,8 @@
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
uid2receiverList.valueAt(i), null /* _resultTo */,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
- allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
+ allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt,
+ filterExtrasForReceiver);
br.enqueueTime = this.enqueueTime;
br.enqueueRealTime = this.enqueueRealTime;
br.enqueueClockTime = this.enqueueClockTime;
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index efc2a27..2498f76 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -30,6 +30,8 @@
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
@@ -93,6 +95,8 @@
public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
public static final String TRIGGER_TYPE_ALARM = "alarm";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE = "push_message";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA = "push_message_over_quota";
@NonNull private final String mHostingType;
private final String mHostingName;
@@ -308,6 +312,10 @@
switch(triggerType) {
case TRIGGER_TYPE_ALARM:
return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+ case TRIGGER_TYPE_PUSH_MESSAGE:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+ case TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
default:
return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 262436d..eb1fd3a 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -497,6 +497,7 @@
@GuardedBy({"mService", "mProcLock"})
void setCurAdj(int curAdj) {
mCurAdj = curAdj;
+ mApp.getWindowProcessController().setCurrentAdj(curAdj);
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0c0ae7d..a1f3dae 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,9 +16,11 @@
package com.android.server.am;
+import static android.Manifest.permission.CREATE_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_USERS;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
import static android.app.ActivityManager.StopUserOnSwitch;
@@ -69,6 +71,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
@@ -100,6 +103,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -107,6 +111,7 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
@@ -1071,6 +1076,12 @@
Binder.getCallingPid(), UserHandle.USER_ALL);
});
}
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ // TODO(b/240613396) also check if multi-display is supported
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, Display.INVALID_DISPLAY);
}
private void finishUserStopping(final int userId, final UserState uss,
@@ -1357,12 +1368,14 @@
List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
for (UserInfo user : profiles) {
if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
- && user.id != currentUserId && !user.isQuietModeEnabled()) {
+ && user.id != currentUserId
+ && shouldStartWithParent(user)) {
profilesToStart.add(user);
}
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
+ // TODO(b/239982558): pass displayId
for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
@@ -1371,6 +1384,14 @@
}
}
+ private boolean shouldStartWithParent(UserInfo user) {
+ final UserProperties properties = mInjector.getUserManagerInternal()
+ .getUserProperties(user.id);
+ return (properties != null && properties.getStartWithParent())
+ && !user.isQuietModeEnabled();
+ }
+
+ // TODO(b/239982558): might need to infer the display id based on parent user
/**
* Starts a user only if it's a profile, with a more relaxed permission requirement:
* {@link android.Manifest.permission#MANAGE_USERS} or
@@ -1399,7 +1420,8 @@
return false;
}
- return startUserNoChecks(userId, /* foreground= */ false, /* unlockListener= */ null);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
+ /* unlockListener= */ null);
}
@VisibleForTesting
@@ -1445,26 +1467,70 @@
@Nullable IProgressListener unlockListener) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");
- return startUserNoChecks(userId, foreground, unlockListener);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
}
- private boolean startUserNoChecks(final @UserIdInt int userId, final boolean foreground,
+ // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are
+ // defined
+ boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId,
+ @Nullable IProgressListener unlockListener) {
+ checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
+ MANAGE_USERS, CREATE_USERS);
+
+ // DEFAULT_DISPLAY is used for "regular" start user operations
+ Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
+ "Cannot use DEFAULT_DISPLAY");
+
+ try {
+ return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
+ } catch (RuntimeException e) {
+ Slogf.w(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
+ return false;
+ }
+ }
+
+ private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("UserController.startUser-" + userId + "-" + (foreground ? "fg" : "bg"));
+ t.traceBegin("UserController.startUser-" + userId
+ + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
+ + "-" + (foreground ? "fg" : "bg"));
try {
- return startUserInternal(userId, foreground, unlockListener, t);
+ return startUserInternal(userId, displayId, foreground, unlockListener, t);
} finally {
t.traceEnd();
}
}
- private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
+ private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
if (DEBUG_MU) {
- Slogf.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
+ Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
+ foreground ? " in foreground" : "");
}
+
+ // TODO(b/239982558): move logic below to a different class (like DisplayAssignmentManager)
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ // This is called by startUserOnSecondaryDisplay()
+ if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+ // TODO(b/239824814): add CTS test and/or unit test for all exceptional cases
+ throw new UnsupportedOperationException("Not supported by device");
+ }
+
+ // TODO(b/239982558): call DisplayManagerInternal to check if display is valid instead
+ Preconditions.checkArgument(displayId > 0, "Invalid display id (%d)", displayId);
+ Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot start system user"
+ + " on secondary display (%d)", displayId);
+ Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
+ + "on secondary display (%d)", userId, displayId);
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
+ }
+
EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
final int callingUid = Binder.getCallingUid();
@@ -1519,6 +1585,7 @@
return false;
}
+ // TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
@@ -2568,15 +2635,24 @@
}
private void checkCallingPermission(String permission, String methodName) {
- if (mInjector.checkCallingPermission(permission)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission denial: " + methodName
- + "() from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + permission;
- Slogf.w(TAG, msg);
- throw new SecurityException(msg);
+ checkCallingHasOneOfThosePermissions(methodName, permission);
+ }
+
+ private void checkCallingHasOneOfThosePermissions(String methodName, String...permissions) {
+ for (String permission : permissions) {
+ if (mInjector.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
}
+ String msg = "Permission denial: " + methodName
+ + "() from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires "
+ + (permissions.length == 1
+ ? permissions[0]
+ : "one of " + Arrays.toString(permissions));
+ Slogf.w(TAG, msg);
+ throw new SecurityException(msg);
}
private void enforceShellRestriction(String restriction, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 854b818..1302e22 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -99,6 +99,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
/**
* Service to manage game related features.
@@ -119,7 +120,7 @@
static final int SET_GAME_STATE = 4;
static final int CANCEL_GAME_LOADING_MODE = 5;
static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
- static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds
+ static final int WRITE_DELAY_MILLIS = 10 * 1000; // 10 seconds
static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds
private static final String PACKAGE_NAME_MSG_KEY = "packageName";
@@ -130,8 +131,8 @@
private final Context mContext;
private final Object mLock = new Object();
private final Object mDeviceConfigLock = new Object();
- private final Object mOverrideConfigLock = new Object();
- private final Handler mHandler;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ final Handler mHandler;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
private final PowerManagerInternal mPowerManagerInternal;
@@ -143,8 +144,6 @@
private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
@GuardedBy("mDeviceConfigLock")
private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
- @GuardedBy("mOverrideConfigLock")
- private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
@Nullable
private final GameServiceController mGameServiceController;
@@ -236,7 +235,7 @@
final int userId = ActivityManager.getCurrentUser();
String[] packageList = getInstalledGamePackageNames(userId);
for (final String packageName : packageList) {
- pw.println(getInterventionList(packageName));
+ pw.println(getInterventionList(packageName, userId));
}
}
@@ -258,14 +257,13 @@
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
synchronized (mLock) {
- removeMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
}
break;
}
-
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
synchronized (mLock) {
- removeMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
GameManagerSettings userSettings = mSettings.get(userId);
userSettings.writePersistentDataLocked();
@@ -279,8 +277,8 @@
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
synchronized (mLock) {
- removeMessages(WRITE_SETTINGS, msg.obj);
- removeMessages(REMOVE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(REMOVE_SETTINGS, msg.obj);
}
break;
}
@@ -288,8 +286,8 @@
synchronized (mLock) {
// Since the user was removed, ignore previous write message
// and do write here.
- removeMessages(WRITE_SETTINGS, msg.obj);
- removeMessages(REMOVE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(REMOVE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
final GameManagerSettings userSettings = mSettings.get(userId);
mSettings.remove(userId);
@@ -299,7 +297,7 @@
break;
}
case POPULATE_GAME_MODE_SETTINGS: {
- removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
+ removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
final int userId = (int) msg.obj;
final String[] packageNames = getInstalledGamePackageNames(userId);
updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
@@ -345,13 +343,13 @@
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
synchronized (mLock) {
- removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+ removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
}
break;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
- removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+ removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
writeGameModeInterventionsToFile(userId);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
break;
@@ -446,8 +444,7 @@
/**
* GamePackageConfiguration manages all game mode config details for its associated package.
*/
- @VisibleForTesting
- public class GamePackageConfiguration {
+ public static class GamePackageConfiguration {
public static final String TAG = "GameManagerService_GamePackageConfiguration";
/**
@@ -499,12 +496,16 @@
private boolean mAllowAngle;
private boolean mAllowFpsOverride;
- GamePackageConfiguration(String packageName, int userId) {
+ GamePackageConfiguration(String packageName) {
+ mPackageName = packageName;
+ }
+
+ GamePackageConfiguration(PackageManager packageManager, String packageName, int userId) {
mPackageName = packageName;
try {
- final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
+ final ApplicationInfo ai = packageManager.getApplicationInfoAsUser(packageName,
PackageManager.GET_META_DATA, userId);
- if (!parseInterventionFromXml(ai, packageName)) {
+ if (!parseInterventionFromXml(packageManager, ai, packageName)) {
if (ai.metaData != null) {
mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
@@ -538,16 +539,17 @@
}
}
- private boolean parseInterventionFromXml(ApplicationInfo ai, String packageName) {
+ private boolean parseInterventionFromXml(PackageManager packageManager, ApplicationInfo ai,
+ String packageName) {
boolean xmlFound = false;
- try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager,
+ try (XmlResourceParser parser = ai.loadXmlMetaData(packageManager,
METADATA_GAME_MODE_CONFIG)) {
if (parser == null) {
Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
+ " meta-data found for package " + mPackageName);
} else {
xmlFound = true;
- final Resources resources = mPackageManager.getResourcesForApplication(
+ final Resources resources = packageManager.getResourcesForApplication(
packageName);
final AttributeSet attributeSet = Xml.asAttributeSet(parser);
int type;
@@ -596,7 +598,6 @@
* GameModeConfiguration contains all the values for all the interventions associated with
* a game mode.
*/
- @VisibleForTesting
public class GameModeConfiguration {
public static final String TAG = "GameManagerService_GameModeConfiguration";
public static final String MODE_KEY = "mode";
@@ -613,8 +614,8 @@
private final @GameMode int mGameMode;
private float mScaling = DEFAULT_SCALING;
private String mFps = DEFAULT_FPS;
- private final boolean mUseAngle;
- private final int mLoadingBoostDuration;
+ private boolean mUseAngle;
+ private int mLoadingBoostDuration;
GameModeConfiguration(int gameMode) {
mGameMode = gameMode;
@@ -657,11 +658,15 @@
return GameManagerService.getFpsInt(mFps);
}
- public boolean getUseAngle() {
+ synchronized String getFpsStr() {
+ return mFps;
+ }
+
+ public synchronized boolean getUseAngle() {
return mUseAngle;
}
- public int getLoadingBoostDuration() {
+ public synchronized int getLoadingBoostDuration() {
return mLoadingBoostDuration;
}
@@ -673,6 +678,14 @@
mFps = fpsStr;
}
+ public synchronized void setUseAngle(boolean useAngle) {
+ mUseAngle = useAngle;
+ }
+
+ public synchronized void setLoadingBoostDuration(int loadingBoostDuration) {
+ mLoadingBoostDuration = loadingBoostDuration;
+ }
+
public boolean isActive() {
return (mGameMode == GameManager.GAME_MODE_STANDARD
|| mGameMode == GameManager.GAME_MODE_PERFORMANCE
@@ -759,7 +772,7 @@
}
/**
- * Inserts a new GameModeConfiguration
+ * Inserts a new GameModeConfiguration.
*/
public void addModeConfig(GameModeConfiguration config) {
if (config.isActive()) {
@@ -772,6 +785,15 @@
}
}
+ /**
+ * Removes the GameModeConfiguration.
+ */
+ public void removeModeConfig(int mode) {
+ synchronized (mModeConfigLock) {
+ mModeConfigs.remove(mode);
+ }
+ }
+
public boolean isActive() {
synchronized (mModeConfigLock) {
return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
@@ -823,7 +845,8 @@
@Override
public void onUserStarting(@NonNull TargetUser user) {
- mService.onUserStarting(user);
+ mService.onUserStarting(user,
+ Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
}
@Override
@@ -860,7 +883,10 @@
}
private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
- final GamePackageConfiguration config = getConfig(packageName);
+ final GamePackageConfiguration config;
+ synchronized (mDeviceConfigLock) {
+ config = mConfigs.get(packageName);
+ }
if (config == null) {
return new int[]{};
}
@@ -987,18 +1013,11 @@
}
GameManagerSettings userSettings = mSettings.get(userId);
userSettings.setGameModeLocked(packageName, gameMode);
- final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
- msg.obj = userId;
- if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) {
- mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
- }
}
updateInterventions(packageName, gameMode, userId);
- final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
- msg.obj = userId;
- if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
- mHandler.sendMessage(msg);
- }
+ sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
+ sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+ "SET_GAME_MODE", 0 /*delayMillis*/);
}
/**
@@ -1033,7 +1052,6 @@
* the boost duration. If no configuration is available for the selected package or mode, the
* default is returned.
*/
- @VisibleForTesting
public int getLoadingBoostDuration(String packageName, int userId)
throws SecurityException {
final int gameMode = getGameMode(packageName, userId);
@@ -1165,7 +1183,7 @@
}
float getResolutionScalingFactorInternal(String packageName, int gameMode, int userId) {
- final GamePackageConfiguration packageConfig = getConfig(packageName);
+ final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
if (packageConfig == null) {
return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING;
}
@@ -1186,22 +1204,46 @@
if (mGameServiceController != null) {
mGameServiceController.onBootComplete();
}
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+ synchronized (mLock) {
+ // Note that the max wait time of broadcast is 10s (see
+ // {@ShutdownThread#MAX_BROADCAST_TIMEMAX_BROADCAST_TIME}) currently so
+ // this can be optional only if we have message delay plus processing
+ // time significant smaller to prevent data loss.
+ for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) {
+ final int userId = entry.getKey();
+ sendUserMessage(userId, WRITE_SETTINGS,
+ Intent.ACTION_SHUTDOWN, 0 /*delayMillis*/);
+ sendUserMessage(userId,
+ WRITE_GAME_MODE_INTERVENTION_LIST_FILE, Intent.ACTION_SHUTDOWN,
+ 0 /*delayMillis*/);
+ }
+ }
+ }
+ }
+ }, new IntentFilter(Intent.ACTION_SHUTDOWN));
}
- void onUserStarting(@NonNull TargetUser user) {
- final int userId = user.getUserIdentifier();
+ private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
+ Message msg = mHandler.obtainMessage(what, userId);
+ if (!mHandler.sendMessageDelayed(msg, delayMillis)) {
+ Slog.e(TAG, "Failed to send user message " + what + " on " + eventForLog);
+ }
+ }
+ void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
+ final int userId = user.getUserIdentifier();
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
- GameManagerSettings userSettings =
- new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
+ GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
mSettings.put(userId, userSettings);
userSettings.readPersistentDataLocked();
}
}
- final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
- msg.obj = userId;
- mHandler.sendMessage(msg);
+ sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_STARTING", 0 /*delayMillis*/);
if (mGameServiceController != null) {
mGameServiceController.notifyUserStarted(user);
@@ -1221,9 +1263,7 @@
if (!mSettings.containsKey(userId)) {
return;
}
- final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
- msg.obj = userId;
- mHandler.sendMessage(msg);
+ sendUserMessage(userId, REMOVE_SETTINGS, "ON_USER_STOPPING", 0 /*delayMillis*/);
}
if (mGameServiceController != null) {
@@ -1237,15 +1277,14 @@
synchronized (mLock) {
final int fromUserId = from.getUserIdentifier();
if (mSettings.containsKey(fromUserId)) {
- final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
- msg.obj = fromUserId;
- mHandler.sendMessage(msg);
+ sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
+ 0 /*delayMillis*/);
}
}
}
- final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
- msg.obj = toUserId;
- mHandler.sendMessage(msg);
+
+ sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
+ 0 /*delayMillis*/);
if (mGameServiceController != null) {
mGameServiceController.notifyNewForegroundUser(to);
@@ -1265,7 +1304,7 @@
}
}
- private int modeToBitmask(@GameMode int gameMode) {
+ private static int modeToBitmask(@GameMode int gameMode) {
return (1 << gameMode);
}
@@ -1305,7 +1344,7 @@
resetFps(packageName, userId);
return;
}
- final GamePackageConfiguration packageConfig = getConfig(packageName);
+ final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
if (packageConfig == null) {
Slog.v(TAG, "Package configuration not found for " + packageName);
return;
@@ -1318,7 +1357,7 @@
}
/**
- * Set the override Game Mode Configuration.
+ * Set the Game Mode Configuration override.
* Update the config if exists, create one if not.
*/
@VisibleForTesting
@@ -1326,95 +1365,86 @@
public void setGameModeConfigOverride(String packageName, @UserIdInt int userId,
@GameMode int gameMode, String fpsStr, String scaling) throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+ // Adding game mode config override of the given package name
+ GamePackageConfiguration configOverride;
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
return;
}
- }
- // Adding override game mode configuration of the given package name
- GamePackageConfiguration overrideConfig;
- synchronized (mOverrideConfigLock) {
- // look for the existing override GamePackageConfiguration
- overrideConfig = mOverrideConfigs.get(packageName);
- if (overrideConfig == null) {
- overrideConfig = new GamePackageConfiguration(packageName, userId);
- mOverrideConfigs.put(packageName, overrideConfig);
+ final GameManagerSettings settings = mSettings.get(userId);
+ // look for the existing GamePackageConfiguration override
+ configOverride = settings.getConfigOverride(packageName);
+ if (configOverride == null) {
+ configOverride = new GamePackageConfiguration(mPackageManager, packageName, userId);
+ settings.setConfigOverride(packageName, configOverride);
}
}
// modify GameModeConfiguration intervention settings
- GamePackageConfiguration.GameModeConfiguration overrideModeConfig =
- overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode);
+ GamePackageConfiguration.GameModeConfiguration modeConfigOverride =
+ configOverride.getOrAddDefaultGameModeConfiguration(gameMode);
if (fpsStr != null) {
- overrideModeConfig.setFpsStr(fpsStr);
+ modeConfigOverride.setFpsStr(fpsStr);
} else {
- overrideModeConfig.setFpsStr(
+ modeConfigOverride.setFpsStr(
GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS);
}
if (scaling != null) {
- overrideModeConfig.setScaling(Float.parseFloat(scaling));
+ modeConfigOverride.setScaling(Float.parseFloat(scaling));
}
Slog.i(TAG, "Package Name: " + packageName
- + " FPS: " + String.valueOf(overrideModeConfig.getFps())
- + " Scaling: " + overrideModeConfig.getScaling());
+ + " FPS: " + String.valueOf(modeConfigOverride.getFps())
+ + " Scaling: " + modeConfigOverride.getScaling());
setGameMode(packageName, gameMode, userId);
}
/**
* Reset the overridden gameModeConfiguration of the given mode.
- * Remove the override config if game mode is not specified.
+ * Remove the config override if game mode is not specified.
*/
@VisibleForTesting
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
@GameMode int gameModeToReset) throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
- synchronized (mLock) {
- if (!mSettings.containsKey(userId)) {
- return;
- }
+ final GamePackageConfiguration deviceConfig;
+ synchronized (mDeviceConfigLock) {
+ deviceConfig = mConfigs.get(packageName);
}
// resets GamePackageConfiguration of a given packageName.
// If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
- if (gameModeToReset != -1) {
- GamePackageConfiguration overrideConfig = null;
- synchronized (mOverrideConfigLock) {
- overrideConfig = mOverrideConfigs.get(packageName);
- }
-
- GamePackageConfiguration config = null;
- synchronized (mDeviceConfigLock) {
- config = mConfigs.get(packageName);
- }
-
- int[] modes = overrideConfig.getAvailableGameModes();
-
- // First check if the mode to reset exists
- boolean isGameModeExist = false;
- for (int mode : modes) {
- if (gameModeToReset == mode) {
- isGameModeExist = true;
- }
- }
- if (!isGameModeExist) {
+ synchronized (mLock) {
+ if (!mSettings.containsKey(userId)) {
return;
}
-
- // If the game mode to reset is the only mode other than standard mode,
- // the override config is removed.
- if (modes.length <= 2) {
- synchronized (mOverrideConfigLock) {
- mOverrideConfigs.remove(packageName);
+ final GameManagerSettings settings = mSettings.get(userId);
+ if (gameModeToReset != -1) {
+ final GamePackageConfiguration configOverride = settings.getConfigOverride(
+ packageName);
+ if (configOverride == null) {
+ return;
+ }
+ final int modesBitfield = configOverride.getAvailableGameModesBitfield();
+ if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) {
+ return;
+ }
+ // if the game mode to reset is the only mode other than standard mode or there
+ // is device config, the config override is removed.
+ if (Integer.bitCount(modesBitfield) <= 2 || deviceConfig == null) {
+ settings.removeConfigOverride(packageName);
+ } else {
+ final GamePackageConfiguration.GameModeConfiguration defaultModeConfig =
+ deviceConfig.getGameModeConfiguration(gameModeToReset);
+ // otherwise we reset the mode by copying the original config.
+ if (defaultModeConfig == null) {
+ configOverride.removeModeConfig(gameModeToReset);
+ } else {
+ configOverride.addModeConfig(defaultModeConfig);
+ }
}
} else {
- // otherwise we reset the mode by copying the original config.
- overrideConfig.addModeConfig(config.getGameModeConfiguration(gameModeToReset));
- }
- } else {
- synchronized (mOverrideConfigLock) {
- // remove override config if there is one
- mOverrideConfigs.remove(packageName);
+ settings.removeConfigOverride(packageName);
}
}
@@ -1422,7 +1452,7 @@
// If not, set the game mode to standard
int gameMode = getGameMode(packageName, userId);
- final GamePackageConfiguration config = getConfig(packageName);
+ final GamePackageConfiguration config = getConfig(packageName, userId);
final int newGameMode = getNewGameMode(gameMode, config);
if (gameMode != newGameMode) {
setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
@@ -1460,8 +1490,8 @@
/**
* Returns the string listing all the interventions currently set to a game.
*/
- public String getInterventionList(String packageName) {
- final GamePackageConfiguration packageConfig = getConfig(packageName);
+ public String getInterventionList(String packageName, int userId) {
+ final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
final StringBuilder listStrSb = new StringBuilder();
if (packageConfig == null) {
listStrSb.append("\n No intervention found for package ")
@@ -1487,7 +1517,7 @@
synchronized (mDeviceConfigLock) {
for (final String packageName : packageNames) {
final GamePackageConfiguration config =
- new GamePackageConfiguration(packageName, userId);
+ new GamePackageConfiguration(mPackageManager, packageName, userId);
if (config.isActive()) {
if (DEBUG) {
Slog.i(TAG, "Adding config: " + config.toString());
@@ -1526,15 +1556,11 @@
updateInterventions(packageName, gameMode, userId);
}
}
+ sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+ "UPDATE_CONFIGS_FOR_USERS", 0 /*delayMillis*/);
} catch (Exception e) {
Slog.e(TAG, "Failed to update configs for user " + userId + ": " + e);
}
-
- final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
- msg.obj = userId;
- if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
- mHandler.sendMessage(msg);
- }
}
/*
@@ -1556,7 +1582,7 @@
final StringBuilder sb = new StringBuilder();
final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId);
for (final String packageName : installedGamesList) {
- GamePackageConfiguration packageConfig = getConfig(packageName);
+ GamePackageConfiguration packageConfig = getConfig(packageName, userId);
if (packageConfig == null) {
continue;
}
@@ -1634,11 +1660,12 @@
/**
* @hide
*/
- @VisibleForTesting
- public GamePackageConfiguration getConfig(String packageName) {
+ public GamePackageConfiguration getConfig(String packageName, int userId) {
GamePackageConfiguration packageConfig = null;
- synchronized (mOverrideConfigLock) {
- packageConfig = mOverrideConfigs.get(packageName);
+ synchronized (mLock) {
+ if (mSettings.containsKey(userId)) {
+ packageConfig = mSettings.get(userId).getConfigOverride(packageName);
+ }
}
if (packageConfig == null) {
synchronized (mDeviceConfigLock) {
@@ -1679,9 +1706,6 @@
break;
case ACTION_PACKAGE_REMOVED:
if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
- synchronized (mOverrideConfigLock) {
- mOverrideConfigs.remove(packageName);
- }
synchronized (mDeviceConfigLock) {
mConfigs.remove(packageName);
}
@@ -1689,6 +1713,11 @@
if (mSettings.containsKey(userId)) {
mSettings.get(userId).removeGame(packageName);
}
+ sendUserMessage(userId, WRITE_SETTINGS,
+ Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
+ sendUserMessage(userId,
+ WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+ Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
}
}
break;
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 1455a61..1162498 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -27,6 +27,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.server.app.GameManagerService.GamePackageConfiguration;
+import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -39,10 +41,11 @@
/**
* Persists all GameService related settings.
+ *
* @hide
*/
public class GameManagerSettings {
-
+ public static final String TAG = "GameManagerService_GameManagerSettings";
// The XML file follows the below format:
// <?xml>
// <packages>
@@ -53,8 +56,14 @@
private static final String TAG_PACKAGE = "package";
private static final String TAG_PACKAGES = "packages";
+ private static final String TAG_GAME_MODE_CONFIG = "gameModeConfig";
+
private static final String ATTR_NAME = "name";
private static final String ATTR_GAME_MODE = "gameMode";
+ private static final String ATTR_SCALING = "scaling";
+ private static final String ATTR_FPS = "fps";
+ private static final String ATTR_USE_ANGLE = "useAngle";
+ private static final String ATTR_LOADING_BOOST_DURATION = "loadingBoost";
private final File mSystemDir;
@VisibleForTesting
@@ -62,6 +71,8 @@
// PackageName -> GameMode
private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
+ // PackageName -> GamePackageConfiguration
+ private final ArrayMap<String, GamePackageConfiguration> mConfigOverrides = new ArrayMap<>();
GameManagerSettings(File dataDir) {
mSystemDir = new File(dataDir, "system");
@@ -74,7 +85,7 @@
}
/**
- * Return the game mode of a given package.
+ * Returns the game mode of a given package.
* This operation must be synced with an external lock.
*/
int getGameModeLocked(String packageName) {
@@ -85,7 +96,7 @@
}
/**
- * Set the game mode of a given package.
+ * Sets the game mode of a given package.
* This operation must be synced with an external lock.
*/
void setGameModeLocked(String packageName, int gameMode) {
@@ -93,15 +104,40 @@
}
/**
- * Remove the game mode of a given package.
+ * Removes all game settings of a given package.
* This operation must be synced with an external lock.
*/
void removeGame(String packageName) {
mGameModes.remove(packageName);
+ mConfigOverrides.remove(packageName);
}
/**
- * Write all current game service settings into disk.
+ * Returns the game config override of a given package or null if absent.
+ * This operation must be synced with an external lock.
+ */
+ GamePackageConfiguration getConfigOverride(String packageName) {
+ return mConfigOverrides.get(packageName);
+ }
+
+ /**
+ * Sets the game config override of a given package.
+ * This operation must be synced with an external lock.
+ */
+ void setConfigOverride(String packageName, GamePackageConfiguration configOverride) {
+ mConfigOverrides.put(packageName, configOverride);
+ }
+
+ /**
+ * Removes the game mode config override of a given package.
+ * This operation must be synced with an external lock.
+ */
+ void removeConfigOverride(String packageName) {
+ mConfigOverrides.remove(packageName);
+ }
+
+ /**
+ * Writes all current game service settings into disk.
* This operation must be synced with an external lock.
*/
void writePersistentDataLocked() {
@@ -115,9 +151,11 @@
serializer.startTag(null, TAG_PACKAGES);
for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
+ String packageName = entry.getKey();
serializer.startTag(null, TAG_PACKAGE);
- serializer.attribute(null, ATTR_NAME, entry.getKey());
+ serializer.attribute(null, ATTR_NAME, packageName);
serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
+ writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName));
serializer.endTag(null, TAG_PACKAGE);
}
serializer.endTag(null, TAG_PACKAGES);
@@ -133,20 +171,41 @@
return;
} catch (java.io.IOException e) {
mSettingsFile.failWrite(fstr);
- Slog.wtf(GameManagerService.TAG, "Unable to write game manager service settings, "
+ Slog.wtf(TAG, "Unable to write game manager service settings, "
+ "current changes will be lost at reboot", e);
}
}
+ private void writeGameModeConfigTags(TypedXmlSerializer serializer,
+ GamePackageConfiguration config) throws IOException {
+ if (config == null) {
+ return;
+ }
+ final int[] gameModes = config.getAvailableGameModes();
+ for (final int mode : gameModes) {
+ final GameModeConfiguration modeConfig = config.getGameModeConfiguration(mode);
+ if (modeConfig != null) {
+ serializer.startTag(null, TAG_GAME_MODE_CONFIG);
+ serializer.attributeInt(null, ATTR_GAME_MODE, mode);
+ serializer.attributeBoolean(null, ATTR_USE_ANGLE, modeConfig.getUseAngle());
+ serializer.attribute(null, ATTR_FPS, modeConfig.getFpsStr());
+ serializer.attributeFloat(null, ATTR_SCALING, modeConfig.getScaling());
+ serializer.attributeInt(null, ATTR_LOADING_BOOST_DURATION,
+ modeConfig.getLoadingBoostDuration());
+ serializer.endTag(null, TAG_GAME_MODE_CONFIG);
+ }
+ }
+ }
+
/**
- * Read game service settings from the disk.
+ * Reads game service settings from the disk.
* This operation must be synced with an external lock.
*/
boolean readPersistentDataLocked() {
mGameModes.clear();
if (!mSettingsFile.exists()) {
- Slog.v(GameManagerService.TAG, "Settings file doesn't exists, skip reading");
+ Slog.v(TAG, "Settings file doesn't exist, skip reading");
return false;
}
@@ -160,8 +219,7 @@
// Do nothing
}
if (type != XmlPullParser.START_TAG) {
- Slog.wtf(GameManagerService.TAG,
- "No start tag found in package manager settings");
+ Slog.wtf(TAG, "No start tag found in package manager settings");
return false;
}
@@ -173,35 +231,107 @@
}
String tagName = parser.getName();
- if (tagName.equals(TAG_PACKAGE)) {
+ if (type == XmlPullParser.START_TAG && TAG_PACKAGE.equals(tagName)) {
readPackage(parser);
} else {
- Slog.w(GameManagerService.TAG, "Unknown element: " + parser.getName());
XmlUtils.skipCurrentTag(parser);
+ Slog.w(TAG, "Unknown element under packages tag: " + tagName + " with type: "
+ + type);
}
}
} catch (XmlPullParserException | java.io.IOException e) {
- Slog.wtf(GameManagerService.TAG, "Error reading package manager settings", e);
+ Slog.wtf(TAG, "Error reading package manager settings", e);
return false;
}
-
return true;
}
+ // this must be called on tag of type START_TAG.
private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
IOException {
- String name = null;
+ final String name = parser.getAttributeValue(null, ATTR_NAME);
+ if (name == null) {
+ Slog.wtf(TAG, "No package name found in package tag");
+ XmlUtils.skipCurrentTag(parser);
+ return;
+ }
int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
try {
- name = parser.getAttributeValue(null, ATTR_NAME);
gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
} catch (XmlPullParserException e) {
- Slog.wtf(GameManagerService.TAG, "Error reading game mode", e);
+ Slog.wtf(TAG, "Invalid game mode in package tag: "
+ + parser.getAttributeValue(null, ATTR_GAME_MODE), e);
+ return;
}
- if (name != null) {
- mGameModes.put(name, gameMode);
- } else {
- XmlUtils.skipCurrentTag(parser);
+ mGameModes.put(name, gameMode);
+ final int packageTagDepth = parser.getDepth();
+ int type;
+ final GamePackageConfiguration config = new GamePackageConfiguration(name);
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > packageTagDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ final String tagName = parser.getName();
+ if (type == XmlPullParser.START_TAG && TAG_GAME_MODE_CONFIG.equals(tagName)) {
+ readGameModeConfig(parser, config);
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ Slog.w(TAG, "Unknown element under package tag: " + tagName + " with type: "
+ + type);
+ }
+ }
+ if (config.getAvailableGameModes().length > 1) {
+ mConfigOverrides.put(name, config);
+ }
+ }
+
+ // this must be called on tag of type START_TAG.
+ private void readGameModeConfig(TypedXmlPullParser parser, GamePackageConfiguration config) {
+ final int gameMode;
+ try {
+ gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
+ } catch (XmlPullParserException e) {
+ Slog.wtf(TAG, "Invalid game mode value in config tag: " + parser.getAttributeValue(null,
+ ATTR_GAME_MODE), e);
+ return;
+ }
+
+ final GameModeConfiguration modeConfig = config.getOrAddDefaultGameModeConfiguration(
+ gameMode);
+ try {
+ final float scaling = parser.getAttributeFloat(null, ATTR_SCALING);
+ modeConfig.setScaling(scaling);
+ } catch (XmlPullParserException e) {
+ final String rawScaling = parser.getAttributeValue(null, ATTR_SCALING);
+ if (rawScaling != null) {
+ Slog.wtf(TAG, "Invalid scaling value in config tag: " + rawScaling, e);
+ }
+ }
+
+ final String fps = parser.getAttributeValue(null, ATTR_FPS);
+ modeConfig.setFpsStr(fps != null ? fps : GameModeConfiguration.DEFAULT_FPS);
+
+ try {
+ final boolean useAngle = parser.getAttributeBoolean(null, ATTR_USE_ANGLE);
+ modeConfig.setUseAngle(useAngle);
+ } catch (XmlPullParserException e) {
+ final String rawUseAngle = parser.getAttributeValue(null, ATTR_USE_ANGLE);
+ if (rawUseAngle != null) {
+ Slog.wtf(TAG, "Invalid useAngle value in config tag: " + rawUseAngle, e);
+ }
+ }
+ try {
+ final int loadingBoostDuration = parser.getAttributeInt(null,
+ ATTR_LOADING_BOOST_DURATION);
+ modeConfig.setLoadingBoostDuration(loadingBoostDuration);
+ } catch (XmlPullParserException e) {
+ final String rawLoadingBoost = parser.getAttributeValue(null,
+ ATTR_LOADING_BOOST_DURATION);
+ if (rawLoadingBoost != null) {
+ Slog.wtf(TAG, "Invalid loading boost in config tag: " + rawLoadingBoost, e);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index 6e289b1..cdbffbe 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -81,7 +81,8 @@
final GameManagerService gameManagerService = (GameManagerService)
ServiceManager.getService(Context.GAME_SERVICE);
- final String listStr = gameManagerService.getInterventionList(packageName);
+ final String listStr = gameManagerService.getInterventionList(packageName,
+ ActivityManager.getCurrentUser());
if (listStr == null) {
pw.println("No interventions found for " + packageName);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a5bcb05..248e35e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -63,7 +63,6 @@
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
-import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager._NUM_OP;
import static android.app.AppOpsManager.extractFlagsFromKey;
import static android.app.AppOpsManager.extractUidStateFromKey;
@@ -563,6 +562,7 @@
public ArrayMap<String, Ops> pkgOps;
// true indicates there is an interested observer, false there isn't but it has such an op
+ //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
public SparseBooleanArray foregroundOps;
public boolean hasForegroundWatchers;
@@ -658,48 +658,24 @@
return mode;
}
- private void evalForegroundWatchers(int op, SparseArray<ArraySet<ModeCallback>> watchers,
- SparseBooleanArray which) {
- boolean curValue = which.get(op, false);
- ArraySet<ModeCallback> callbacks = watchers.get(op);
- if (callbacks != null) {
- for (int cbi = callbacks.size() - 1; !curValue && cbi >= 0; cbi--) {
- if ((callbacks.valueAt(cbi).mFlags
- & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
- hasForegroundWatchers = true;
- curValue = true;
- }
- }
- }
- which.put(op, curValue);
- }
-
- public void evalForegroundOps(SparseArray<ArraySet<ModeCallback>> watchers) {
- SparseBooleanArray which = null;
- hasForegroundWatchers = false;
- final SparseIntArray opModes = getNonDefaultUidModes();
- for (int i = opModes.size() - 1; i >= 0; i--) {
- if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
- if (which == null) {
- which = new SparseBooleanArray();
- }
- evalForegroundWatchers(opModes.keyAt(i), watchers, which);
- }
- }
+ public void evalForegroundOps() {
+ foregroundOps = null;
+ foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
if (pkgOps != null) {
for (int i = pkgOps.size() - 1; i >= 0; i--) {
- Ops ops = pkgOps.valueAt(i);
- for (int j = ops.size() - 1; j >= 0; j--) {
- if (ops.valueAt(j).getMode() == AppOpsManager.MODE_FOREGROUND) {
- if (which == null) {
- which = new SparseBooleanArray();
- }
- evalForegroundWatchers(ops.keyAt(j), watchers, which);
- }
+ foregroundOps = mAppOpsServiceInterface
+ .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps);
+ }
+ }
+ hasForegroundWatchers = false;
+ if (foregroundOps != null) {
+ for (int i = 0; i < foregroundOps.size(); i++) {
+ if (foregroundOps.valueAt(i)) {
+ hasForegroundWatchers = true;
+ break;
}
}
}
- foregroundOps = which;
}
}
@@ -1562,33 +1538,24 @@
}
}
- final SparseArray<ArraySet<ModeCallback>> mOpModeWatchers = new SparseArray<>();
- final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>();
final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
- final class ModeCallback implements DeathRecipient {
+ final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
/** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
public static final int ALL_OPS = -2;
- final IAppOpsCallback mCallback;
- final int mWatchingUid;
- final int mFlags;
- final int mWatchedOpCode;
- final int mCallingUid;
- final int mCallingPid;
+ // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+ // Otherwise we can just use the IBinder object.
+ private final IAppOpsCallback mCallback;
- ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOp,
+ ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
int callingUid, int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mFlags = flags;
- mWatchedOpCode = watchedOp;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
+ super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+ this.mCallback = callback;
try {
mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -1596,20 +1563,16 @@
}
}
- public boolean isWatchingUid(int uid) {
- return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid;
- }
-
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("ModeCallback{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
+ UserHandle.formatUid(sb, getWatchingUid());
sb.append(" flags=0x");
- sb.append(Integer.toHexString(mFlags));
- switch (mWatchedOpCode) {
+ sb.append(Integer.toHexString(getFlags()));
+ switch (getWatchedOpCode()) {
case OP_NONE:
break;
case ALL_OPS:
@@ -1617,13 +1580,13 @@
break;
default:
sb.append(" op=");
- sb.append(opToName(mWatchedOpCode));
+ sb.append(opToName(getWatchedOpCode()));
break;
}
sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
+ UserHandle.formatUid(sb, getCallingUid());
sb.append(" pid=");
- sb.append(mCallingPid);
+ sb.append(getCallingPid());
sb.append('}');
return sb.toString();
}
@@ -1636,6 +1599,11 @@
public void binderDied() {
stopWatchingMode(mCallback);
}
+
+ @Override
+ public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+ mCallback.opChanged(op, uid, packageName);
+ }
}
final class ActiveCallback implements DeathRecipient {
@@ -1804,7 +1772,14 @@
public AppOpsService(File storagePath, Handler handler, Context context) {
mContext = context;
- mAppOpsServiceInterface = new LegacyAppOpsServiceInterfaceImpl(this, this);
+
+ for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+ int switchCode = AppOpsManager.opToSwitch(switchedCode);
+ mSwitchedOps.put(switchCode,
+ ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+ }
+ mAppOpsServiceInterface =
+ new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mFile = new AtomicFile(storagePath, "appops");
@@ -1818,12 +1793,6 @@
mHandler = handler;
mConstants = new Constants(mHandler);
readState();
-
- for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
- int switchCode = AppOpsManager.opToSwitch(switchedCode);
- mSwitchedOps.put(switchCode,
- ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
- }
}
public void publish() {
@@ -1982,20 +1951,20 @@
final String[] changedPkgs = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_PACKAGE_LIST);
for (int code : OPS_RESTRICTED_ON_SUSPEND) {
- ArraySet<ModeCallback> callbacks;
+ ArraySet<OnOpModeChangedListener> onModeChangedListeners;
synchronized (AppOpsService.this) {
- callbacks = mOpModeWatchers.get(code);
- if (callbacks == null) {
+ onModeChangedListeners =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (onModeChangedListeners == null) {
continue;
}
- callbacks = new ArraySet<>(callbacks);
}
for (int i = 0; i < changedUids.length; i++) {
final int changedUid = changedUids[i];
final String changedPkg = changedPkgs[i];
// We trust packagemanager to insert matching uid and packageNames in the
// extras
- notifyOpChanged(callbacks, code, changedUid, changedPkg);
+ notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
}
}
}
@@ -2596,7 +2565,7 @@
if (!uidState.setUidMode(code, mode)) {
return;
}
- uidState.evalForegroundOps(mOpModeWatchers);
+ uidState.evalForegroundOps();
if (mode != MODE_ERRORED && mode != previousMode) {
updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
}
@@ -2615,78 +2584,10 @@
*/
private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
@Nullable IAppOpsCallback callbackToIgnore) {
- String[] uidPackageNames = getPackagesForUid(uid);
- ArrayMap<ModeCallback, ArraySet<String>> callbackSpecs = null;
-
- synchronized (this) {
- ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
- if (callbacks != null) {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- ModeCallback callback = callbacks.valueAt(i);
- if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) {
- continue;
- }
-
- ArraySet<String> changedPackages = new ArraySet<>();
- Collections.addAll(changedPackages, uidPackageNames);
- if (callbackSpecs == null) {
- callbackSpecs = new ArrayMap<>();
- }
- callbackSpecs.put(callback, changedPackages);
- }
- }
-
- for (String uidPackageName : uidPackageNames) {
- callbacks = mPackageModeWatchers.get(uidPackageName);
- if (callbacks != null) {
- if (callbackSpecs == null) {
- callbackSpecs = new ArrayMap<>();
- }
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- ModeCallback callback = callbacks.valueAt(i);
- if (onlyForeground && (callback.mFlags & WATCH_FOREGROUND_CHANGES) == 0) {
- continue;
- }
-
- ArraySet<String> changedPackages = callbackSpecs.get(callback);
- if (changedPackages == null) {
- changedPackages = new ArraySet<>();
- callbackSpecs.put(callback, changedPackages);
- }
- changedPackages.add(uidPackageName);
- }
- }
- }
-
- if (callbackSpecs != null && callbackToIgnore != null) {
- callbackSpecs.remove(mModeWatchers.get(callbackToIgnore.asBinder()));
- }
- }
-
- if (callbackSpecs == null) {
- return;
- }
-
- for (int i = 0; i < callbackSpecs.size(); i++) {
- final ModeCallback callback = callbackSpecs.keyAt(i);
- final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
- if (reportedPackageNames == null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, (String) null));
-
- } else {
- final int reportedPackageCount = reportedPackageNames.size();
- for (int j = 0; j < reportedPackageCount; j++) {
- final String reportedPackageName = reportedPackageNames.valueAt(j);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, callback, code, uid, reportedPackageName));
- }
- }
- }
+ ModeCallback listenerToIgnore = callbackToIgnore != null
+ ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+ mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+ listenerToIgnore);
}
private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
@@ -2810,7 +2711,7 @@
return;
}
- ArraySet<ModeCallback> repCbs = null;
+ ArraySet<OnOpModeChangedListener> repCbs = null;
code = AppOpsManager.opToSwitch(code);
PackageVerificationResult pvr;
@@ -2831,16 +2732,17 @@
op.setMode(mode);
if (uidState != null) {
- uidState.evalForegroundOps(mOpModeWatchers);
+ uidState.evalForegroundOps();
}
- ArraySet<ModeCallback> cbs = mOpModeWatchers.get(code);
+ ArraySet<OnOpModeChangedListener> cbs =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
if (cbs != null) {
if (repCbs == null) {
repCbs = new ArraySet<>();
}
repCbs.addAll(cbs);
}
- cbs = mPackageModeWatchers.get(packageName);
+ cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
if (cbs != null) {
if (repCbs == null) {
repCbs = new ArraySet<>();
@@ -2871,47 +2773,17 @@
notifyOpChangedSync(code, uid, packageName, mode, previousMode);
}
- private void notifyOpChanged(ArraySet<ModeCallback> callbacks, int code,
+ private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
int uid, String packageName) {
for (int i = 0; i < callbacks.size(); i++) {
- final ModeCallback callback = callbacks.valueAt(i);
+ final OnOpModeChangedListener callback = callbacks.valueAt(i);
notifyOpChanged(callback, code, uid, packageName);
}
}
- private void notifyOpChanged(ModeCallback callback, int code,
+ private void notifyOpChanged(OnOpModeChangedListener callback, int code,
int uid, String packageName) {
- if (uid != UID_ANY && callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- return;
- }
-
- // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
- int[] switchedCodes;
- if (callback.mWatchedOpCode == ALL_OPS) {
- switchedCodes = mSwitchedOps.get(code);
- } else if (callback.mWatchedOpCode == OP_NONE) {
- switchedCodes = new int[]{code};
- } else {
- switchedCodes = new int[]{callback.mWatchedOpCode};
- }
-
- for (int switchedCode : switchedCodes) {
- // There are features watching for mode changes such as window manager
- // and location manager which are in our process. The callbacks in these
- // features may require permissions our remote caller does not have.
- final long identity = Binder.clearCallingIdentity();
- try {
- if (shouldIgnoreCallback(switchedCode, callback.mCallingPid,
- callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opChanged(switchedCode, uid, packageName);
- } catch (RemoteException e) {
- /* ignore */
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
+ mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
}
private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
@@ -2936,9 +2808,10 @@
return reports;
}
- private static HashMap<ModeCallback, ArrayList<ChangeRec>> addCallbacks(
- HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks,
- int op, int uid, String packageName, int previousMode, ArraySet<ModeCallback> cbs) {
+ private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+ int op, int uid, String packageName, int previousMode,
+ ArraySet<OnOpModeChangedListener> cbs) {
if (cbs == null) {
return callbacks;
}
@@ -2947,7 +2820,7 @@
}
final int N = cbs.size();
for (int i=0; i<N; i++) {
- ModeCallback cb = cbs.valueAt(i);
+ OnOpModeChangedListener cb = cbs.valueAt(i);
ArrayList<ChangeRec> reports = callbacks.get(cb);
ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
if (changed != reports) {
@@ -2990,7 +2863,7 @@
enforceManageAppOpsModes(callingPid, callingUid, reqUid);
- HashMap<ModeCallback, ArrayList<ChangeRec>> callbacks = null;
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
ArrayList<ChangeRec> allChanges = new ArrayList<>();
synchronized (this) {
boolean changed = false;
@@ -3007,9 +2880,11 @@
uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
for (String packageName : getPackagesForUid(uidState.uid)) {
callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
- previousMode, mOpModeWatchers.get(code));
+ previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(code));
callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
- previousMode, mPackageModeWatchers.get(packageName));
+ previousMode, mAppOpsServiceInterface
+ .getPackageModeChangedListeners(packageName));
allChanges = addChange(allChanges, code, uidState.uid,
packageName, previousMode);
@@ -3053,9 +2928,11 @@
uidChanged = true;
final int uid = curOp.uidState.uid;
callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode, mOpModeWatchers.get(curOp.op));
+ previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode, mPackageModeWatchers.get(packageName));
+ previousMode, mAppOpsServiceInterface
+ .getPackageModeChangedListeners(packageName));
allChanges = addChange(allChanges, curOp.op, uid, packageName,
previousMode);
@@ -3075,7 +2952,7 @@
mUidStates.remove(uidState.uid);
}
if (uidChanged) {
- uidState.evalForegroundOps(mOpModeWatchers);
+ uidState.evalForegroundOps();
}
}
@@ -3084,8 +2961,9 @@
}
}
if (callbacks != null) {
- for (Map.Entry<ModeCallback, ArrayList<ChangeRec>> ent : callbacks.entrySet()) {
- ModeCallback cb = ent.getKey();
+ for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+ : callbacks.entrySet()) {
+ OnOpModeChangedListener cb = ent.getKey();
ArrayList<ChangeRec> reports = ent.getValue();
for (int i=0; i<reports.size(); i++) {
ChangeRec rep = reports.get(i);
@@ -3121,7 +2999,7 @@
for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
final UidState uidState = mUidStates.valueAt(uidi);
if (uidState.foregroundOps != null) {
- uidState.evalForegroundOps(mOpModeWatchers);
+ uidState.evalForegroundOps();
}
}
}
@@ -3169,20 +3047,10 @@
mModeWatchers.put(callback.asBinder(), cb);
}
if (switchOp != AppOpsManager.OP_NONE) {
- ArraySet<ModeCallback> cbs = mOpModeWatchers.get(switchOp);
- if (cbs == null) {
- cbs = new ArraySet<>();
- mOpModeWatchers.put(switchOp, cbs);
- }
- cbs.add(cb);
+ mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
}
if (mayWatchPackageName) {
- ArraySet<ModeCallback> cbs = mPackageModeWatchers.get(packageName);
- if (cbs == null) {
- cbs = new ArraySet<>();
- mPackageModeWatchers.put(packageName, cbs);
- }
- cbs.add(cb);
+ mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
}
evalAllForegroundOpsLocked();
}
@@ -3197,21 +3065,9 @@
ModeCallback cb = mModeWatchers.remove(callback.asBinder());
if (cb != null) {
cb.unlinkToDeath();
- for (int i=mOpModeWatchers.size()-1; i>=0; i--) {
- ArraySet<ModeCallback> cbs = mOpModeWatchers.valueAt(i);
- cbs.remove(cb);
- if (cbs.size() <= 0) {
- mOpModeWatchers.removeAt(i);
- }
- }
- for (int i=mPackageModeWatchers.size()-1; i>=0; i--) {
- ArraySet<ModeCallback> cbs = mPackageModeWatchers.valueAt(i);
- cbs.remove(cb);
- if (cbs.size() <= 0) {
- mPackageModeWatchers.removeAt(i);
- }
- }
+ mAppOpsServiceInterface.removeListener(cb);
}
+
evalAllForegroundOpsLocked();
}
}
@@ -4542,12 +4398,14 @@
AppOpsService::notifyOpChangedForAllPkgsInUid,
this, code, uidState.uid, true, null));
} else if (uidState.pkgOps != null) {
- final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
- if (callbacks != null) {
- for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) {
- final ModeCallback callback = callbacks.valueAt(cbi);
- if ((callback.mFlags & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
- || !callback.isWatchingUid(uidState.uid)) {
+ final ArraySet<OnOpModeChangedListener> listenerSet =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (listenerSet != null) {
+ for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+ final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+ if ((listener.getFlags()
+ & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+ || !listener.isWatchingUid(uidState.uid)) {
continue;
}
for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
@@ -4558,7 +4416,7 @@
if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChanged,
- this, callback, code, uidState.uid,
+ this, listenerSet.valueAt(cbi), code, uidState.uid,
uidState.pkgOps.keyAt(pkgi)));
}
}
@@ -5045,7 +4903,7 @@
}
}
if (changed) {
- uidState.evalForegroundOps(mOpModeWatchers);
+ uidState.evalForegroundOps();
}
}
}
@@ -5131,7 +4989,7 @@
XmlUtils.skipCurrentTag(parser);
}
}
- uidState.evalForegroundOps(mOpModeWatchers);
+ uidState.evalForegroundOps();
}
private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
@@ -6122,62 +5980,17 @@
}
pw.println();
}
- if (mOpModeWatchers.size() > 0 && !dumpHistory) {
- boolean printedHeader = false;
- for (int i=0; i<mOpModeWatchers.size(); i++) {
- if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
- continue;
- }
- boolean printedOpHeader = false;
- ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
- for (int j=0; j<callbacks.size(); j++) {
- final ModeCallback cb = callbacks.valueAt(j);
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
- needSep = true;
- if (!printedHeader) {
- pw.println(" Op mode watchers:");
- printedHeader = true;
- }
- if (!printedOpHeader) {
- pw.print(" Op ");
- pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
- pw.println(":");
- printedOpHeader = true;
- }
- pw.print(" #"); pw.print(j); pw.print(": ");
- pw.println(cb);
- }
- }
+
+ if (!dumpHistory) {
+ needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
}
- if (mPackageModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
- boolean printedHeader = false;
- for (int i=0; i<mPackageModeWatchers.size(); i++) {
- if (dumpPackage != null && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
- continue;
- }
- needSep = true;
- if (!printedHeader) {
- pw.println(" Package mode watchers:");
- printedHeader = true;
- }
- pw.print(" Pkg "); pw.print(mPackageModeWatchers.keyAt(i));
- pw.println(":");
- ArraySet<ModeCallback> callbacks = mPackageModeWatchers.valueAt(i);
- for (int j=0; j<callbacks.size(); j++) {
- pw.print(" #"); pw.print(j); pw.print(": ");
- pw.println(callbacks.valueAt(j));
- }
- }
- }
+
if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
boolean printedHeader = false;
- for (int i=0; i<mModeWatchers.size(); i++) {
+ for (int i = 0; i < mModeWatchers.size(); i++) {
final ModeCallback cb = mModeWatchers.valueAt(i);
if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
continue;
}
needSep = true;
@@ -6729,16 +6542,15 @@
}
private void notifyWatchersOfChange(int code, int uid) {
- final ArraySet<ModeCallback> clonedCallbacks;
+ final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
synchronized (this) {
- ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code);
- if (callbacks == null) {
+ modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (modeChangedListenerSet == null) {
return;
}
- clonedCallbacks = new ArraySet<>(callbacks);
}
- notifyOpChanged(clonedCallbacks, code, uid, null);
+ notifyOpChanged(modeChangedListenerSet, code, uid, null);
}
@Override
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index cd5ea12..c707086 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -14,12 +14,20 @@
* limitations under the License.
*/
package com.android.server.appop;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager.Mode;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
/**
* Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
- * In the future this interface will also include mode callbacks and op restrictions.
+ * This interface also includes functions for added and removing op mode watchers.
+ * In the future this interface will also include op restrictions.
*/
public interface AppOpsServiceInterface {
/**
@@ -95,4 +103,93 @@
* Stop tracking app-op modes for all uid and packages.
*/
void clearAllModes();
+
+ /**
+ * Registers changedListener to listen to op's mode change.
+ * @param changedListener the listener that must be trigger on the op's mode change.
+ * @param op op representing the app-op whose mode change needs to be listened to.
+ */
+ void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+
+ /**
+ * Registers changedListener to listen to package's app-op's mode change.
+ * @param changedListener the listener that must be trigger on the mode change.
+ * @param packageName of the package whose app-op's mode change needs to be listened to.
+ */
+ void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+ @NonNull String packageName);
+
+ /**
+ * Stop the changedListener from triggering on any mode change.
+ * @param changedListener the listener that needs to be removed.
+ */
+ void removeListener(@NonNull OnOpModeChangedListener changedListener);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
+ * @param op app-op whose mode change is being listened to.
+ */
+ ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
+ * @param packageName of package whose app-op's mode change is being listened to.
+ */
+ ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed by triggering the change listener.
+ * @param changedListener the change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op
+ * @param packageName package name that is associated with the app-op
+ */
+ void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+ @Nullable String packageName);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed to all packages associated with the uid by
+ * triggering the appropriate change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op
+ * @param onlyForeground true if only watchers that
+ * @param callbackToIgnore callback that should be ignored.
+ */
+ void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+ @Nullable OnOpModeChangedListener callbackToIgnore);
+
+ /**
+ * TODO: Move hasForegroundWatchers and foregroundOps into this.
+ * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
+ * foregroundOps.
+ * @param uid for which the app-op's mode needs to be marked.
+ * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+ * @return foregroundOps.
+ */
+ SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+
+ /**
+ * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
+ * foregroundOps.
+ * @param packageName for which the app-op's mode needs to be marked.
+ * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+ * @return foregroundOps.
+ */
+ SparseBooleanArray evalForegroundPackageOps(String packageName,
+ SparseBooleanArray foregroundOps);
+
+ /**
+ * Dump op mode and package mode listeners and their details.
+ * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
+ * app-op, only the watchers for that app-op are dumped.
+ * @param dumpUid uid for which we want to dump op mode watchers.
+ * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
+ * @param printWriter writer to dump to.
+ */
+ boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+
}
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
index c27c0d3..2d498ea 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
@@ -16,15 +16,39 @@
package com.android.server.appop;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
+import static android.app.AppOpsManager.opRestrictsRead;
+
+import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+
+import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.Mode;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import libcore.util.EmptyArray;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Objects;
/**
@@ -33,8 +57,13 @@
*/
public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
- // Should be the same object that the AppOpsService is using for locking.
+ static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
+
+ // Must be the same object that the AppOpsService is using for locking.
final Object mLock;
+ final Handler mHandler;
+ final Context mContext;
+ final SparseArray<int[]> mSwitchedOps;
@GuardedBy("mLock")
@VisibleForTesting
@@ -43,13 +72,25 @@
@GuardedBy("mLock")
final ArrayMap<String, SparseIntArray> mPackageModes = new ArrayMap<>();
+ final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>();
+ final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
+ new ArrayMap<>();
+
final PersistenceScheduler mPersistenceScheduler;
+ // Constant meaning that any UID should be matched when dispatching callbacks
+ private static final int UID_ANY = -2;
+
+
LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
- @NonNull Object lock) {
+ @NonNull Object lock, Handler handler, Context context,
+ SparseArray<int[]> switchedOps) {
this.mPersistenceScheduler = persistenceScheduler;
this.mLock = lock;
+ this.mHandler = handler;
+ this.mContext = context;
+ this.mSwitchedOps = switchedOps;
}
@Override
@@ -158,7 +199,6 @@
}
}
-
@Override
public boolean areUidModesDefault(int uid) {
synchronized (mLock) {
@@ -195,4 +235,335 @@
}
}
-}
+ @Override
+ public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
+ int op) {
+ Objects.requireNonNull(changedListener);
+ synchronized (mLock) {
+ ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op);
+ if (modeWatcherSet == null) {
+ modeWatcherSet = new ArraySet<>();
+ mOpModeWatchers.put(op, modeWatcherSet);
+ }
+ modeWatcherSet.add(changedListener);
+ }
+ }
+
+ @Override
+ public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+ @NonNull String packageName) {
+ Objects.requireNonNull(changedListener);
+ Objects.requireNonNull(packageName);
+ synchronized (mLock) {
+ ArraySet<OnOpModeChangedListener> modeWatcherSet =
+ mPackageModeWatchers.get(packageName);
+ if (modeWatcherSet == null) {
+ modeWatcherSet = new ArraySet<>();
+ mPackageModeWatchers.put(packageName, modeWatcherSet);
+ }
+ modeWatcherSet.add(changedListener);
+ }
+ }
+
+ @Override
+ public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
+ Objects.requireNonNull(changedListener);
+
+ synchronized (mLock) {
+ for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
+ ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
+ cbs.remove(changedListener);
+ if (cbs.size() <= 0) {
+ mOpModeWatchers.removeAt(i);
+ }
+ }
+
+ for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
+ ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
+ cbs.remove(changedListener);
+ if (cbs.size() <= 0) {
+ mPackageModeWatchers.removeAt(i);
+ }
+ }
+ }
+ }
+
+ @Override
+ public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
+ synchronized (mLock) {
+ ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op);
+ if (modeChangedListenersSet == null) {
+ return new ArraySet<>();
+ }
+ return new ArraySet<>(modeChangedListenersSet);
+ }
+ }
+
+ @Override
+ public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
+ @NonNull String packageName) {
+ Objects.requireNonNull(packageName);
+
+ synchronized (mLock) {
+ ArraySet<OnOpModeChangedListener> modeChangedListenersSet =
+ mPackageModeWatchers.get(packageName);
+ if (modeChangedListenersSet == null) {
+ return new ArraySet<>();
+ }
+ return new ArraySet<>(modeChangedListenersSet);
+ }
+ }
+
+ @Override
+ public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
+ int uid, @Nullable String packageName) {
+ Objects.requireNonNull(onModeChangedListener);
+
+ if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
+ && onModeChangedListener.getWatchingUid() != uid) {
+ return;
+ }
+
+ // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
+ int[] switchedCodes;
+ if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
+ switchedCodes = mSwitchedOps.get(code);
+ } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
+ switchedCodes = new int[]{code};
+ } else {
+ switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
+ }
+
+ for (int switchedCode : switchedCodes) {
+ // There are features watching for mode changes such as window manager
+ // and location manager which are in our process. The callbacks in these
+ // features may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
+ onModeChangedListener.getCallingUid())) {
+ continue;
+ }
+ onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
+ } catch (RemoteException e) {
+ /* ignore */
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+ // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+ // as watcher should not use this to signal if the value is changed.
+ return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
+ public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+ @Nullable OnOpModeChangedListener callbackToIgnore) {
+ String[] uidPackageNames = getPackagesForUid(uid);
+ ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
+
+ synchronized (mLock) {
+ ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
+ if (callbacks != null) {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+ if (onlyForeground && (callback.getFlags()
+ & WATCH_FOREGROUND_CHANGES) == 0) {
+ continue;
+ }
+
+ ArraySet<String> changedPackages = new ArraySet<>();
+ Collections.addAll(changedPackages, uidPackageNames);
+ if (callbackSpecs == null) {
+ callbackSpecs = new ArrayMap<>();
+ }
+ callbackSpecs.put(callback, changedPackages);
+ }
+ }
+
+ for (String uidPackageName : uidPackageNames) {
+ callbacks = mPackageModeWatchers.get(uidPackageName);
+ if (callbacks != null) {
+ if (callbackSpecs == null) {
+ callbackSpecs = new ArrayMap<>();
+ }
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ OnOpModeChangedListener callback = callbacks.valueAt(i);
+
+ if (onlyForeground && (callback.getFlags()
+ & WATCH_FOREGROUND_CHANGES) == 0) {
+ continue;
+ }
+
+ ArraySet<String> changedPackages = callbackSpecs.get(callback);
+ if (changedPackages == null) {
+ changedPackages = new ArraySet<>();
+ callbackSpecs.put(callback, changedPackages);
+ }
+ changedPackages.add(uidPackageName);
+ }
+ }
+ }
+
+ if (callbackSpecs != null && callbackToIgnore != null) {
+ callbackSpecs.remove(callbackToIgnore);
+ }
+ }
+
+ if (callbackSpecs == null) {
+ return;
+ }
+
+ for (int i = 0; i < callbackSpecs.size(); i++) {
+ final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
+ final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
+ if (reportedPackageNames == null) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+ this, callback, code, uid, (String) null));
+
+ } else {
+ final int reportedPackageCount = reportedPackageNames.size();
+ for (int j = 0; j < reportedPackageCount; j++) {
+ final String reportedPackageName = reportedPackageNames.valueAt(j);
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+ this, callback, code, uid, reportedPackageName));
+ }
+ }
+ }
+ }
+
+ private static String[] getPackagesForUid(int uid) {
+ String[] packageNames = null;
+
+ // Very early during boot the package manager is not yet or not yet fully started. At this
+ // time there are no packages yet.
+ if (AppGlobals.getPackageManager() != null) {
+ try {
+ packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+ if (packageNames == null) {
+ return EmptyArray.STRING;
+ }
+ return packageNames;
+ }
+
+ @Override
+ public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+ synchronized (mLock) {
+ return evalForegroundOps(mUidModes.get(uid), foregroundOps);
+ }
+ }
+
+ @Override
+ public SparseBooleanArray evalForegroundPackageOps(String packageName,
+ SparseBooleanArray foregroundOps) {
+ synchronized (mLock) {
+ return evalForegroundOps(mPackageModes.get(packageName), foregroundOps);
+ }
+ }
+
+ private SparseBooleanArray evalForegroundOps(SparseIntArray opModes,
+ SparseBooleanArray foregroundOps) {
+ SparseBooleanArray tempForegroundOps = foregroundOps;
+ if (opModes != null) {
+ for (int i = opModes.size() - 1; i >= 0; i--) {
+ if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
+ if (tempForegroundOps == null) {
+ tempForegroundOps = new SparseBooleanArray();
+ }
+ evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps);
+ }
+ }
+ }
+ return tempForegroundOps;
+ }
+
+ private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) {
+ boolean curValue = foregroundOps.get(op, false);
+ ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op);
+ if (listenerSet != null) {
+ for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) {
+ if ((listenerSet.valueAt(cbi).getFlags()
+ & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
+ curValue = true;
+ }
+ }
+ }
+ foregroundOps.put(op, curValue);
+ }
+
+ @Override
+ public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
+ PrintWriter printWriter) {
+ boolean needSep = false;
+ if (mOpModeWatchers.size() > 0) {
+ boolean printedHeader = false;
+ for (int i = 0; i < mOpModeWatchers.size(); i++) {
+ if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
+ continue;
+ }
+ boolean printedOpHeader = false;
+ ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
+ mOpModeWatchers.valueAt(i);
+ for (int j = 0; j < modeChangedListenerSet.size(); j++) {
+ final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j);
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) {
+ continue;
+ }
+ needSep = true;
+ if (!printedHeader) {
+ printWriter.println(" Op mode watchers:");
+ printedHeader = true;
+ }
+ if (!printedOpHeader) {
+ printWriter.print(" Op ");
+ printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
+ printWriter.println(":");
+ printedOpHeader = true;
+ }
+ printWriter.print(" #"); printWriter.print(j); printWriter.print(": ");
+ printWriter.println(listener.toString());
+ }
+ }
+ }
+
+ if (mPackageModeWatchers.size() > 0 && dumpOp < 0) {
+ boolean printedHeader = false;
+ for (int i = 0; i < mPackageModeWatchers.size(); i++) {
+ if (dumpPackage != null
+ && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
+ continue;
+ }
+ needSep = true;
+ if (!printedHeader) {
+ printWriter.println(" Package mode watchers:");
+ printedHeader = true;
+ }
+ printWriter.print(" Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i));
+ printWriter.println(":");
+ ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
+ mPackageModeWatchers.valueAt(i);
+
+ for (int j = 0; j < modeChangedListenerSet.size(); j++) {
+ printWriter.print(" #"); printWriter.print(j); printWriter.print(": ");
+ printWriter.println(modeChangedListenerSet.valueAt(j).toString());
+ }
+ }
+ }
+ return needSep;
+ }
+
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
new file mode 100644
index 0000000..5ebe811
--- /dev/null
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -0,0 +1,102 @@
+/*
+ * 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.appop;
+
+import android.os.RemoteException;
+
+/**
+ * Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
+ * change.
+ */
+abstract class OnOpModeChangedListener {
+
+ // Constant meaning that any UID should be matched when dispatching callbacks
+ private static final int UID_ANY = -2;
+
+ private int mWatchingUid;
+ private int mFlags;
+ private int mWatchedOpCode;
+ private int mCallingUid;
+ private int mCallingPid;
+
+ OnOpModeChangedListener(int watchingUid, int flags, int watchedOpCode, int callingUid,
+ int callingPid) {
+ this.mWatchingUid = watchingUid;
+ this.mFlags = flags;
+ this.mWatchedOpCode = watchedOpCode;
+ this.mCallingUid = callingUid;
+ this.mCallingPid = callingPid;
+ }
+
+ /**
+ * Returns the user id that is watching for the mode change.
+ */
+ public int getWatchingUid() {
+ return mWatchingUid;
+ }
+
+ /**
+ * Returns the flags associated with the mode change listener.
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Get the app-op whose mode change should trigger the callback.
+ */
+ public int getWatchedOpCode() {
+ return mWatchedOpCode;
+ }
+
+ /**
+ * Get the user-id that triggered the app-op mode change to be watched.
+ */
+ public int getCallingUid() {
+ return mCallingUid;
+ }
+
+ /**
+ * Get the process-id that triggered the app-op mode change to be watched.
+ */
+ public int getCallingPid() {
+ return mCallingPid;
+ }
+
+ /**
+ * returns true if the user id passed in the param is the one that is watching for op mode
+ * changed.
+ */
+ public boolean isWatchingUid(int uid) {
+ return uid == UID_ANY || mWatchingUid < 0 || mWatchingUid == uid;
+ }
+
+ /**
+ * Method that should be triggered when the app-op's mode is changed.
+ * @param op app-op whose mode-change is being listened to.
+ * @param uid user-is associated with the app-op.
+ * @param packageName package name associated with the app-op.
+ */
+ public abstract void onOpModeChanged(int op, int uid, String packageName)
+ throws RemoteException;
+
+ /**
+ * Return human readable string representing the listener.
+ */
+ public abstract String toString();
+
+}
diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING
new file mode 100644
index 0000000..35b8165
--- /dev/null
+++ b/services/core/java/com/android/server/attention/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsVoiceInteractionTestCases",
+ "options": [
+ {
+ "include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectedResultTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceProximityTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 25211c8..c1f4969 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -376,7 +376,8 @@
makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
- streamType, btInfo.mAudioSystemDevice, "onSetBtActiveDevice");
+ streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+ "onSetBtActiveDevice");
}
break;
default: throw new IllegalArgumentException("Invalid profile "
@@ -1176,8 +1177,8 @@
}
@GuardedBy("mDevicesLock")
- private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device,
- String eventSource) {
+ private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
+ int volumeIndex, int device, String eventSource) {
if (device != AudioSystem.DEVICE_NONE) {
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
@@ -1198,7 +1199,9 @@
return;
}
- final int leAudioVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, device);
+ final int leAudioVolIndex = (volumeIndex == -1)
+ ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
+ : volumeIndex;
final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8356134..aedbe4e 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -571,7 +571,9 @@
// There may be different devices with the same device type (aliasing).
// We always send the full device state info on each change.
private void logDeviceState(SADeviceState deviceState, String event) {
- final String deviceName = AudioSystem.getDeviceName(deviceState.mDeviceType);
+ final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
+ deviceState.mDeviceType);
+ final String deviceName = AudioSystem.getDeviceName(deviceType);
new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
.set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress)
.set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false")
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
new file mode 100644
index 0000000..0f1fe68
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Common attributes for all biometric service providers.
+ *
+ * @param <T> Internal settings type.
+ */
+public interface BiometricServiceProvider<T extends SensorPropertiesInternal> {
+
+ /** Checks if the specified sensor is owned by this provider. */
+ boolean containsSensor(int sensorId);
+
+ /** All sensor properties. */
+ @NonNull
+ List<T> getSensorProperties();
+
+ /** Properties for the given sensor id. */
+ @NonNull
+ T getSensorProperties(int sensorId);
+
+ boolean isHardwareDetected(int sensorId);
+
+ /** If the user has any enrollments for the given sensor. */
+ boolean hasEnrollments(int sensorId, int userId);
+
+ long getAuthenticatorId(int sensorId, int userId);
+
+ @LockoutTracker.LockoutMode
+ int getLockoutModeForUser(int sensorId, int userId);
+
+ void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
+ boolean clearSchedulerBuffer);
+
+ void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
+
+ void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
new file mode 100644
index 0000000..4779f6f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricServiceRegistry.java
@@ -0,0 +1,243 @@
+/*
+ * 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.biometrics.sensors;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.Handler;
+import android.os.IInterface;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Container for all BiometricServiceProvider implementations.
+ *
+ * @param <T> The service provider type.
+ * @param <P> The internal properties type.
+ * @param <C> The registration callback for {@link #invokeRegisteredCallback(IInterface, List)}.
+ */
+public abstract class BiometricServiceRegistry<T extends BiometricServiceProvider<P>,
+ P extends SensorPropertiesInternal,
+ C extends IInterface> {
+
+ private static final String TAG = "BiometricServiceRegistry";
+
+ // Volatile so they can be read without a lock once all services are registered.
+ // But, ideally remove this and provide immutable copies via the callback instead.
+ @Nullable
+ private volatile List<T> mServiceProviders;
+ @Nullable
+ private volatile List<P> mAllProps;
+
+ @NonNull
+ private final Supplier<IBiometricService> mBiometricServiceSupplier;
+ @NonNull
+ private final RemoteCallbackList<C> mRegisteredCallbacks = new RemoteCallbackList<>();
+
+ public BiometricServiceRegistry(@NonNull Supplier<IBiometricService> biometricSupplier) {
+ mBiometricServiceSupplier = biometricSupplier;
+ }
+
+ /**
+ * Register an implementation by creating a new authenticator and initializing it via
+ * {@link IBiometricService#registerAuthenticator(int, int, int, IBiometricAuthenticator)}
+ * using the given properties.
+ *
+ * @param service service to register with
+ * @param props internal properties to initialize the authenticator
+ */
+ protected abstract void registerService(@NonNull IBiometricService service, @NonNull P props);
+
+ /**
+ * Invoke the callback to notify clients that all authenticators have been registered.
+ *
+ * @param callback callback to invoke
+ * @param allProps properties of all authenticators
+ */
+ protected abstract void invokeRegisteredCallback(@NonNull C callback,
+ @NonNull List<P> allProps) throws RemoteException;
+
+ /**
+ * Register all authenticators in a background thread.
+ *
+ * @param serviceProvider Supplier function that will be invoked on the background thread.
+ */
+ public void registerAll(Supplier<List<T>> serviceProvider) {
+ // Some HAL might not be started before the system service and will cause the code below
+ // to wait, and some of the operations below might take a significant amount of time to
+ // complete (calls to the HALs). To avoid blocking the rest of system server we put
+ // this on a background thread.
+ final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ true /* allowIo */);
+ thread.start();
+ final Handler handler = new Handler(thread.getLooper());
+ handler.post(() -> registerAllInBackground(serviceProvider));
+ thread.quitSafely();
+ }
+
+ /** Register authenticators now, only called by {@link #registerAll(Supplier).} */
+ @VisibleForTesting
+ public void registerAllInBackground(Supplier<List<T>> serviceProvider) {
+ List<T> providers = serviceProvider.get();
+ if (providers == null) {
+ providers = new ArrayList<>();
+ }
+
+ final IBiometricService biometricService = mBiometricServiceSupplier.get();
+ if (biometricService == null) {
+ throw new IllegalStateException("biometric service cannot be null");
+ }
+
+ // Register each sensor individually with BiometricService
+ final List<P> allProps = new ArrayList<>();
+ for (T provider : providers) {
+ final List<P> props = provider.getSensorProperties();
+ for (P prop : props) {
+ registerService(biometricService, prop);
+ }
+ allProps.addAll(props);
+ }
+
+ finishRegistration(providers, allProps);
+ }
+
+ private synchronized void finishRegistration(
+ @NonNull List<T> providers, @NonNull List<P> allProps) {
+ mServiceProviders = Collections.unmodifiableList(providers);
+ mAllProps = Collections.unmodifiableList(allProps);
+ broadcastAllAuthenticatorsRegistered();
+ }
+
+ /**
+ * Add a callback that will be invoked once the work from {@link #registerAll(Supplier)}
+ * has finished registering all providers (executes immediately if already done).
+ *
+ * @param callback registration callback
+ */
+ public synchronized void addAllRegisteredCallback(@Nullable C callback) {
+ if (callback == null) {
+ Slog.e(TAG, "addAllRegisteredCallback, callback is null");
+ return;
+ }
+
+ final boolean registered = mRegisteredCallbacks.register(callback);
+ final boolean allRegistered = mServiceProviders != null;
+ if (registered && allRegistered) {
+ broadcastAllAuthenticatorsRegistered();
+ } else if (!registered) {
+ Slog.e(TAG, "addAllRegisteredCallback failed to register callback");
+ }
+ }
+
+ private synchronized void broadcastAllAuthenticatorsRegistered() {
+ final int n = mRegisteredCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ final C cb = mRegisteredCallbacks.getBroadcastItem(i);
+ try {
+ invokeRegisteredCallback(cb, mAllProps);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in broadcastAllAuthenticatorsRegistered", e);
+ } finally {
+ mRegisteredCallbacks.unregister(cb);
+ }
+ }
+ mRegisteredCallbacks.finishBroadcast();
+ }
+
+ /**
+ * Get a list of registered providers.
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @NonNull
+ public List<T> getProviders() {
+ return mServiceProviders != null ? mServiceProviders : Collections.emptyList();
+ }
+
+ /**
+ * Gets the provider for given sensor id or null if not registered.
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @Nullable
+ public T getProviderForSensor(int sensorId) {
+ if (mServiceProviders != null) {
+ for (T provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return provider;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds the provider for devices with only a single sensor.
+ *
+ * If no providers returns null. If multiple sensors are present this method
+ * will return the first one that is found (this is a legacy for test devices that
+ * use aidl/hidl concurrently and should not occur on real devices).
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @Nullable
+ public Pair<Integer, T> getSingleProvider() {
+ if (mAllProps == null || mAllProps.isEmpty()) {
+ Slog.e(TAG, "No sensors found");
+ return null;
+ }
+
+ if (mAllProps.size() > 1) {
+ Slog.e(TAG, "getSingleProvider() called but multiple sensors present: "
+ + mAllProps.size());
+ }
+
+ final int sensorId = mAllProps.get(0).sensorId;
+ final T provider = getProviderForSensor(sensorId);
+ if (provider != null) {
+ return new Pair<>(sensorId, provider);
+ }
+
+ Slog.e(TAG, "Single sensor: " + sensorId + ", but provider not found");
+ return null;
+ }
+
+ /**
+ * Get the properties for all providers.
+ *
+ * Undefined until {@link #registerAll(Supplier)} has fired the completion callback.
+ */
+ @NonNull
+ public List<P> getAllProperties() {
+ return mAllProps != null ? mAllProps : Collections.emptyList();
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 0d789f7..f854316 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -23,32 +23,64 @@
import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.IBiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Slog;
import com.android.server.biometrics.Utils;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* A callback for receiving notifications about biometric sensor state changes.
+ *
+ * @param <T> service provider type
+ * @param <P> internal property type
*/
-public class BiometricStateCallback implements ClientMonitorCallback {
+public class BiometricStateCallback<T extends BiometricServiceProvider<P>,
+ P extends SensorPropertiesInternal> implements ClientMonitorCallback {
private static final String TAG = "BiometricStateCallback";
@NonNull
- private final CopyOnWriteArrayList<IBiometricStateListener>
- mBiometricStateListeners = new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<IBiometricStateListener> mBiometricStateListeners =
+ new CopyOnWriteArrayList<>();
+ @NonNull
+ private final UserManager mUserManager;
+ @BiometricStateListener.State
+ private int mBiometricState;
+ @NonNull
+ private List<T> mProviders = List.of();
- private @BiometricStateListener.State int mBiometricState;
-
- public BiometricStateCallback() {
+ /**
+ * Create a new callback that must be {@link #start(List)}ed.
+ *
+ * @param userManager user manager
+ */
+ public BiometricStateCallback(@NonNull UserManager userManager) {
mBiometricState = STATE_IDLE;
+ mUserManager = userManager;
}
+ /**
+ * This should be called when the service has been initialized and all providers are ready.
+ *
+ * @param allProviders all registered biometric service providers
+ */
+ public synchronized void start(@NonNull List<T> allProviders) {
+ mProviders = Collections.unmodifiableList(allProviders);
+ broadcastCurrentEnrollmentState(null /* listener */);
+ }
+
+ /** Get the current state. */
+ @BiometricStateListener.State
public int getBiometricState() {
return mBiometricState;
}
@@ -120,23 +152,43 @@
}
/**
- * This should be invoked when:
- * 1) Enrolled --> None-enrolled
- * 2) None-enrolled --> enrolled
- * 3) HAL becomes ready
- * 4) Listener is registered
+ * Enables clients to register a BiometricStateListener. For example, this is used to forward
+ * fingerprint sensor state changes to SideFpsEventHandler.
+ *
+ * @param listener listener to register
*/
- public void notifyAllEnrollmentStateChanged(int userId, int sensorId,
+ public synchronized void registerBiometricStateListener(
+ @NonNull IBiometricStateListener listener) {
+ mBiometricStateListeners.add(listener);
+ broadcastCurrentEnrollmentState(listener);
+ }
+
+ private synchronized void broadcastCurrentEnrollmentState(
+ @Nullable IBiometricStateListener listener) {
+ for (T provider : mProviders) {
+ for (SensorPropertiesInternal prop : provider.getSensorProperties()) {
+ for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+ final boolean enrolled = provider.hasEnrollments(prop.sensorId, userInfo.id);
+ if (listener != null) {
+ notifyEnrollmentStateChanged(
+ listener, userInfo.id, prop.sensorId, enrolled);
+ } else {
+ notifyAllEnrollmentStateChanged(
+ userInfo.id, prop.sensorId, enrolled);
+ }
+ }
+ }
+ }
+ }
+
+ private void notifyAllEnrollmentStateChanged(int userId, int sensorId,
boolean hasEnrollments) {
for (IBiometricStateListener listener : mBiometricStateListeners) {
notifyEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
}
}
- /**
- * Notifies the listener of enrollment state changes.
- */
- public void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
+ private void notifyEnrollmentStateChanged(@NonNull IBiometricStateListener listener,
int userId, int sensorId, boolean hasEnrollments) {
try {
listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
@@ -144,14 +196,4 @@
Slog.e(TAG, "Remote exception", e);
}
}
-
- /**
- * Enables clients to register a BiometricStateListener. For example, this is used to forward
- * fingerprint sensor state changes to SideFpsEventHandler.
- *
- * @param listener
- */
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- mBiometricStateListeners.add(listener);
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 79e65cc..271bce9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -17,20 +17,18 @@
package com.android.server.biometrics.sensors.face;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FACE;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricStateListener;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
@@ -39,18 +37,18 @@
import android.hardware.face.Face;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.os.NativeHandle;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -58,10 +56,10 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -88,51 +86,10 @@
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull
- private final List<ServiceProvider> mServiceProviders;
-
- @Nullable
- private ServiceProvider getProviderForSensor(int sensorId) {
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return provider;
- }
- }
- return null;
- }
-
- /**
- * For devices with only a single provider, returns that provider. If no providers, or multiple
- * providers exist, returns null.
- */
- @Nullable
- private Pair<Integer, ServiceProvider> getSingleProvider() {
- final List<FaceSensorPropertiesInternal> properties = getSensorProperties();
- if (properties.size() != 1) {
- Slog.e(TAG, "Multiple sensors found: " + properties.size());
- return null;
- }
-
- // Theoretically we can just return the first provider, but maybe this is easier to
- // understand.
- final int sensorId = properties.get(0).sensorId;
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return new Pair<>(sensorId, provider);
- }
- }
-
- Slog.e(TAG, "Single sensor, but provider not found");
- return null;
- }
-
+ private final FaceServiceRegistry mRegistry;
@NonNull
- private List<FaceSensorPropertiesInternal> getSensorProperties() {
- final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
- for (ServiceProvider provider : mServiceProviders) {
- properties.addAll(provider.getSensorProperties());
- }
- return properties;
- }
+ private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
+ mBiometricStateCallback;
/**
* Receives the incoming binder calls from FaceManager.
@@ -142,8 +99,7 @@
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -156,9 +112,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
final ProtoOutputStream proto = new ProtoOutputStream();
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider != null) {
provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
@@ -170,16 +125,14 @@
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
-
- return FaceService.this.getSensorProperties();
+ return mRegistry.getAllProperties();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
+ ", caller: " + opPackageName);
@@ -193,8 +146,7 @@
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFaceServiceReceiver receiver, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
return;
@@ -207,8 +159,7 @@
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
return;
@@ -222,8 +173,7 @@
public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
return -1;
@@ -245,8 +195,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
return;
@@ -260,7 +209,6 @@
public long authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName,
boolean isKeyguardBypassEnabled) {
-
// TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
// lockdown, something wrong happened. See similar path in FingerprintService.
@@ -273,7 +221,7 @@
// permission.
final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
return -1;
@@ -301,7 +249,7 @@
return -1;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
return -1;
@@ -318,8 +266,7 @@
IBinder token, long operationId, int userId,
IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
int cookie, boolean allowBackgroundAuthentication) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
@@ -336,8 +283,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
return;
@@ -350,8 +296,7 @@
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName,
final long requestId) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthentication");
return;
@@ -370,7 +315,7 @@
return;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelFaceDetect");
return;
@@ -383,8 +328,7 @@
@Override // Binder call
public void cancelAuthenticationFromService(int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
return;
@@ -397,8 +341,7 @@
@Override // Binder call
public void remove(final IBinder token, final int faceId, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
return;
@@ -412,7 +355,6 @@
@Override // Binder call
public void removeAll(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
-
final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
int sensorsFinishedRemoving = 0;
final int numSensors = getSensorPropertiesInternal(
@@ -432,7 +374,7 @@
// This effectively iterates through all sensors, but has to do so by finding all
// sensors under each provider.
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
for (FaceSensorPropertiesInternal prop : props) {
provider.scheduleRemoveAll(prop.sensorId, token, userId, internalReceiver,
@@ -467,27 +409,27 @@
try {
if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpProtoState(props.sensorId, proto, false);
}
}
proto.flush();
} else if (args.length > 0 && "--proto".equals(args[0])) {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpProtoMetrics(props.sensorId, fd);
}
}
} else if (args.length > 1 && "--hal".equals(args[0])) {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.dumpHal(props.sensorId, fd,
Arrays.copyOfRange(args, 1, args.length, args.getClass()));
}
}
} else {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
pw.println("Dumping for sensorId: " + props.sensorId
+ ", provider: " + provider.getClass().getSimpleName());
@@ -504,10 +446,9 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
final long token = Binder.clearCallingIdentity();
try {
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
return false;
@@ -521,12 +462,11 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getEnrolledFaces, caller: " + opPackageName);
return Collections.emptyList();
@@ -538,12 +478,11 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
-
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for hasEnrolledFaces, caller: " + opPackageName);
return false;
@@ -555,8 +494,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
return LockoutTracker.LOCKOUT_NONE;
@@ -569,8 +507,7 @@
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
return;
@@ -582,7 +519,7 @@
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
return 0;
@@ -595,8 +532,7 @@
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
return;
@@ -610,8 +546,7 @@
public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for setFeature");
return;
@@ -625,8 +560,7 @@
@Override
public void getFeature(final IBinder token, int userId, int feature,
IFaceServiceReceiver receiver, final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for getFeature");
return;
@@ -636,18 +570,14 @@
new ClientMonitorCallbackConverter(receiver), opPackageName);
}
- private void addHidlProviders(@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
- for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
- mServiceProviders.add(
- Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
- }
- }
+ private List<ServiceProvider> getAidlProviders() {
+ final List<ServiceProvider> providers = new ArrayList<>();
- private void addAidlProviders() {
final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
if (instances == null || instances.length == 0) {
- return;
+ return providers;
}
+
for (String instance : instances) {
final String fqName = IFace.DESCRIPTOR + "/" + instance;
final IFace face = IFace.Stub.asInterface(
@@ -660,53 +590,41 @@
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(), props, instance,
mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
- mServiceProviders.add(provider);
+ providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
}
+
+ return providers;
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public void registerAuthenticators(
@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
-
- // Some HAL might not be started before the system service and will cause the code below
- // to wait, and some of the operations below might take a significant amount of time to
- // complete (calls to the HALs). To avoid blocking the rest of system server we put
- // this on a background thread.
- final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
- true /* allowIo */);
- thread.start();
- final Handler handler = new Handler(thread.getLooper());
-
- handler.post(() -> {
- addHidlProviders(hidlSensors);
- addAidlProviders();
-
- final IBiometricService biometricService = IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE));
-
- // Register each sensor individually with BiometricService
- for (ServiceProvider provider : mServiceProviders) {
- final List<FaceSensorPropertiesInternal> props = provider.getSensorProperties();
- for (FaceSensorPropertiesInternal prop : props) {
- final int sensorId = prop.sensorId;
- final @BiometricManager.Authenticators.Types int strength =
- Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
- final FaceAuthenticator authenticator = new FaceAuthenticator(
- mServiceWrapper, sensorId);
- try {
- biometricService.registerAuthenticator(sensorId, TYPE_FACE, strength,
- authenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
- }
- }
+ mRegistry.registerAll(() -> {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
+ providers.add(
+ Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
}
+ providers.addAll(getAidlProviders());
+ return providers;
});
}
+
+ @Override
+ public void addAuthenticatorsRegisteredCallback(
+ IFaceAuthenticatorsRegisteredCallback callback) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ mRegistry.addAllRegisteredCallback(callback);
+ }
+
+ @Override
+ public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ mBiometricStateCallback.registerBiometricStateListener(listener);
+ }
}
public FaceService(Context context) {
@@ -714,7 +632,16 @@
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
- mServiceProviders = new ArrayList<>();
+ mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+ mRegistry = new FaceServiceRegistry(mServiceWrapper,
+ () -> IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
+ mBiometricStateCallback.start(mRegistry.getProviders());
+ }
+ });
}
@Override
@@ -752,7 +679,7 @@
if (Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FaceSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
new file mode 100644
index 0000000..0f0a81d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFaceService} providers. */
+public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+ FaceSensorPropertiesInternal, IFaceAuthenticatorsRegisteredCallback> {
+
+ private static final String TAG = "FaceServiceRegistry";
+
+ @NonNull
+ private final IFaceService mService;
+
+ /** Creates a new registry tied to the given service. */
+ public FaceServiceRegistry(@NonNull IFaceService service,
+ @Nullable Supplier<IBiometricService> biometricSupplier) {
+ super(biometricSupplier);
+ mService = service;
+ }
+
+ @Override
+ protected void registerService(@NonNull IBiometricService service,
+ @NonNull FaceSensorPropertiesInternal props) {
+ @BiometricManager.Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+ try {
+ service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
+ new FaceAuthenticator(mService, props.sensorId));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+ }
+ }
+
+ @Override
+ protected void invokeRegisteredCallback(@NonNull IFaceAuthenticatorsRegisteredCallback callback,
+ @NonNull List<FaceSensorPropertiesInternal> allProps) throws RemoteException {
+ callback.onAllAuthenticatorsRegistered(allProps);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 6f98365..4efaedb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -26,15 +26,13 @@
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
import android.view.Surface;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.List;
/**
@@ -56,24 +54,11 @@
* to check (e.g. via {@link FaceManager#getSensorPropertiesInternal()}) that the code path isn't
* taken. ServiceProviders will provide a no-op for unsupported operations to fail safely.
*/
-public interface ServiceProvider {
- /**
- * Checks if the specified sensor is owned by this provider.
- */
- boolean containsSensor(int sensorId);
-
- @NonNull
- List<FaceSensorPropertiesInternal> getSensorProperties();
-
- @NonNull
- FaceSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends BiometricServiceProvider<FaceSensorPropertiesInternal> {
@NonNull
List<Face> getEnrolledFaces(int sensorId, int userId);
- @LockoutTracker.LockoutMode
- int getLockoutModeForUser(int sensorId, int userId);
-
/**
* Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to be
* invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -84,10 +69,6 @@
+ " this method");
}
- long getAuthenticatorId(int sensorId, int userId);
-
- boolean isHardwareDetected(int sensorId);
-
void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName);
@@ -142,13 +123,6 @@
void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
- void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
- boolean clearSchedulerBuffer);
-
- void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
- void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 19d54c8..6bff179 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -285,6 +285,11 @@
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFaces(sensorId, userId).isEmpty();
+ }
+
+ @Override
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 6528912..c0a119f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -484,6 +484,11 @@
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFaces(sensorId, userId).isEmpty();
+ }
+
+ @Override
@LockoutTracker.LockoutMode
public int getLockoutModeForUser(int sensorId, int userId) {
return mLockoutTracker.getLockoutModeForUser(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 2ba449a..7e2742e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -17,14 +17,11 @@
package com.android.server.biometrics.sensors.fingerprint;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
-import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
@@ -36,8 +33,6 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -65,7 +60,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -79,11 +73,9 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -115,74 +107,32 @@
protected static final String TAG = "FingerprintService";
- private final Object mLock = new Object();
private final AppOpsManager mAppOps;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private final LockPatternUtils mLockPatternUtils;
- @NonNull private final List<ServiceProvider> mServiceProviders;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull private final Handler mHandler;
- @NonNull private final BiometricContext mBiometricContext;
- @NonNull private final Supplier<IBiometricService> mBiometricServiceSupplier;
- @NonNull private final Function<String, IFingerprint> mIFingerprintProvider;
+ @NonNull
+ private final BiometricContext mBiometricContext;
+ @NonNull
+ private final Supplier<String[]> mAidlInstanceNameSupplier;
+ @NonNull
+ private final Function<String, IFingerprint> mIFingerprintProvider;
+ @NonNull
+ private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
+ mBiometricStateCallback;
+ @NonNull
+ private final Handler mHandler;
+ @NonNull
+ private final FingerprintServiceRegistry mRegistry;
- @GuardedBy("mLock")
- @NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
- mAuthenticatorsRegisteredCallbacks;
-
- @GuardedBy("mLock")
- @NonNull private final List<FingerprintSensorPropertiesInternal> mSensorProps;
-
- /**
- * Registers BiometricStateListener in list stored by FingerprintService
- * @param listener new BiometricStateListener being added
- */
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- mBiometricStateCallback.registerBiometricStateListener(listener);
- broadcastCurrentEnrollmentState(listener);
- }
-
- /**
- * @param listener if non-null, notifies only this listener. if null, notifies all listeners
- * in {@link BiometricStateCallback}. This is slightly ugly, but reduces
- * redundant code.
- */
- private void broadcastCurrentEnrollmentState(@Nullable IBiometricStateListener listener) {
- final UserManager um = UserManager.get(getContext());
- synchronized (mLock) {
- // Update the new listener with current state of all sensors
- for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
- final ServiceProvider provider = getProviderForSensor(prop.sensorId);
- for (UserInfo userInfo : um.getAliveUsers()) {
- final boolean enrolled = !provider
- .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
-
- // Defer this work and allow the loop to release the lock sooner
- mHandler.post(() -> {
- if (listener != null) {
- mBiometricStateCallback.notifyEnrollmentStateChanged(
- listener, userInfo.id, prop.sensorId, enrolled);
- } else {
- mBiometricStateCallback.notifyAllEnrollmentStateChanged(
- userInfo.id, prop.sensorId, enrolled);
- }
- });
- }
- }
- }
- }
-
- /**
- * Receives the incoming binder calls from FingerprintManager.
- */
- private final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
+ /** Receives the incoming binder calls from FingerprintManager. */
+ @VisibleForTesting
+ final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for createTestSession, sensorId: " + sensorId);
@@ -195,9 +145,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
-
final ProtoOutputStream proto = new ProtoOutputStream();
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider != null) {
provider.dumpProtoState(sensorId, proto, clearSchedulerBuffer);
}
@@ -212,16 +161,14 @@
!= PackageManager.PERMISSION_GRANTED) {
Utils.checkPermission(getContext(), TEST_BIOMETRIC);
}
-
- return FingerprintService.this.getSensorProperties();
+ return mRegistry.getAllProperties();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
+ ", caller: " + opPackageName);
@@ -234,8 +181,7 @@
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFingerprintServiceReceiver receiver, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
return;
@@ -248,8 +194,7 @@
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
return;
@@ -264,8 +209,7 @@
public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
return -1;
@@ -278,8 +222,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
return;
@@ -339,10 +282,10 @@
final Pair<Integer, ServiceProvider> provider;
if (sensorId == FingerprintManager.SENSOR_ID_ANY) {
- provider = getSingleProvider();
+ provider = mRegistry.getSingleProvider();
} else {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- provider = new Pair<>(sensorId, getProviderForSensor(sensorId));
+ provider = new Pair<>(sensorId, mRegistry.getProviderForSensor(sensorId));
}
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
@@ -374,7 +317,6 @@
final IFingerprintServiceReceiver receiver,
final String opPackageName,
boolean ignoreEnrollmentState) throws PackageManager.NameNotFoundException {
-
final Context context = getUiContext();
final Context promptContext = context.createPackageContextAsUser(
opPackageName, 0 /* flags */, UserHandle.getUserHandleForUid(uId));
@@ -468,7 +410,7 @@
return -1;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
return -1;
@@ -484,8 +426,7 @@
public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
long requestId, int cookie, boolean allowBackgroundAuthentication) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
return;
@@ -501,8 +442,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
return;
@@ -532,7 +472,7 @@
return;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthentication");
return;
@@ -553,7 +493,7 @@
// For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as
// cancelling authentication.
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelFingerprintDetect");
return;
@@ -566,11 +506,9 @@
@Override // Binder call
public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
-
-
Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
return;
@@ -583,8 +521,7 @@
@Override // Binder call
public void remove(final IBinder token, final int fingerId, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
-
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
return;
@@ -617,7 +554,7 @@
// This effectively iterates through all sensors, but has to do so by finding all
// sensors under each provider.
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
List<FingerprintSensorPropertiesInternal> props = provider.getSensorProperties();
for (FingerprintSensorPropertiesInternal prop : props) {
provider.scheduleRemoveAll(prop.sensorId, token, internalReceiver, userId,
@@ -652,7 +589,7 @@
try {
if (args.length > 1 && "--proto".equals(args[0]) && "--state".equals(args[1])) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
provider.dumpProtoState(props.sensorId, proto, false);
@@ -660,14 +597,14 @@
}
proto.flush();
} else if (args.length > 0 && "--proto".equals(args[0])) {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
provider.dumpProtoMetrics(props.sensorId, fd);
}
}
} else {
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props
: provider.getSensorProperties()) {
pw.println("Dumping for sensorId: " + props.sensorId
@@ -698,7 +635,7 @@
final long token = Binder.clearCallingIdentity();
try {
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetectedDeprecated, caller: "
+ opPackageName);
@@ -713,8 +650,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
return false;
@@ -730,7 +666,7 @@
return;
}
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for rename");
return;
@@ -781,8 +717,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
return false;
@@ -794,8 +729,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
return LockoutTracker.LOCKOUT_NONE;
@@ -807,8 +741,7 @@
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
return;
@@ -819,8 +752,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
return 0;
@@ -832,8 +764,7 @@
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId,
@Nullable byte[] hardwareAuthToken, String opPackageName) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
return;
@@ -864,55 +795,38 @@
@Override // Binder call
public void registerAuthenticators(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
-
- // Some HAL might not be started before the system service and will cause the code below
- // to wait, and some of the operations below might take a significant amount of time to
- // complete (calls to the HALs). To avoid blocking the rest of system server we put
- // this on a background thread.
- final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
- true /* allowIo */);
- thread.start();
- final Handler handler = new Handler(thread.getLooper());
- handler.post(() -> {
+ mRegistry.registerAll(() -> {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ providers.addAll(getHidlProviders(hidlSensors));
List<String> aidlSensors = new ArrayList<>();
- final String[] instances =
- ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+ final String[] instances = mAidlInstanceNameSupplier.get();
if (instances != null) {
aidlSensors.addAll(Lists.newArrayList(instances));
}
- registerAuthenticatorsForService(aidlSensors, hidlSensors);
+ providers.addAll(getAidlProviders(
+ Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
+ return providers;
});
- thread.quitSafely();
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void addAuthenticatorsRegisteredCallback(
IFingerprintAuthenticatorsRegisteredCallback callback) {
- if (callback == null) {
- Slog.e(TAG, "addAuthenticatorsRegisteredCallback, callback is null");
- return;
- }
+ mRegistry.addAllRegisteredCallback(callback);
+ }
- final boolean registered;
- final boolean hasSensorProps;
- synchronized (mLock) {
- registered = mAuthenticatorsRegisteredCallbacks.register(callback);
- hasSensorProps = !mSensorProps.isEmpty();
- }
- if (registered && hasSensorProps) {
- broadcastAllAuthenticatorsRegistered();
- } else if (!registered) {
- Slog.e(TAG, "addAuthenticatorsRegisteredCallback failed to register callback");
- }
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ mBiometricStateCallback.registerBiometricStateListener(listener);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPointerDown(long requestId, int sensorId, int x, int y,
float minor, float major) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
return;
@@ -923,8 +837,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPointerUp(long requestId, int sensorId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
return;
@@ -935,8 +848,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onUiReady(long requestId, int sensorId) {
-
- final ServiceProvider provider = getProviderForSensor(sensorId);
+ final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
return;
@@ -947,8 +859,7 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
-
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
provider.setUdfpsOverlayController(controller);
}
}
@@ -956,22 +867,15 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setSidefpsController(@NonNull ISidefpsController controller) {
-
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
provider.setSidefpsController(controller);
}
}
- @Override
- public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- FingerprintService.this.registerBiometricStateListener(listener);
- }
-
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPowerPressed() {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
provider.onPowerPressed();
}
}
@@ -981,6 +885,7 @@
this(context, BiometricContext.getInstance(context),
() -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
+ () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
(fqName) -> IFingerprint.Stub.asInterface(
Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))));
}
@@ -988,61 +893,34 @@
@VisibleForTesting
FingerprintService(Context context,
BiometricContext biometricContext,
- Supplier<IBiometricService> biometricServiceProvider,
+ Supplier<IBiometricService> biometricServiceSupplier,
+ Supplier<String[]> aidlInstanceNameSupplier,
Function<String, IFingerprint> fingerprintProvider) {
super(context);
mBiometricContext = biometricContext;
- mBiometricServiceSupplier = biometricServiceProvider;
+ mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
mIFingerprintProvider = fingerprintProvider;
mAppOps = context.getSystemService(AppOpsManager.class);
mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
- mServiceProviders = new ArrayList<>();
- mBiometricStateCallback = new BiometricStateCallback();
- mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
- mSensorProps = new ArrayList<>();
+ mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
mHandler = new Handler(Looper.getMainLooper());
+ mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
+ mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ mBiometricStateCallback.start(mRegistry.getProviders());
+ }
+ });
}
- @VisibleForTesting
- void registerAuthenticatorsForService(@NonNull List<String> aidlInstanceNames,
+ @NonNull
+ private List<ServiceProvider> getHidlProviders(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- addHidlProviders(hidlSensors);
- addAidlProviders(Utils.filterAvailableHalInstances(getContext(), aidlInstanceNames));
+ final List<ServiceProvider> providers = new ArrayList<>();
- final IBiometricService biometricService = mBiometricServiceSupplier.get();
-
- // Register each sensor individually with BiometricService
- for (ServiceProvider provider : mServiceProviders) {
- final List<FingerprintSensorPropertiesInternal> props =
- provider.getSensorProperties();
- for (FingerprintSensorPropertiesInternal prop : props) {
- final int sensorId = prop.sensorId;
- @BiometricManager.Authenticators.Types final int strength =
- Utils.propertyStrengthToAuthenticatorStrength(prop.sensorStrength);
- final FingerprintAuthenticator authenticator = new FingerprintAuthenticator(
- mServiceWrapper, sensorId);
- try {
- biometricService.registerAuthenticator(sensorId, TYPE_FINGERPRINT,
- strength, authenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
- }
- }
- }
-
- synchronized (mLock) {
- for (ServiceProvider provider : mServiceProviders) {
- mSensorProps.addAll(provider.getSensorProperties());
- }
- }
-
- broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
- broadcastAllAuthenticatorsRegistered();
- }
-
- private void addHidlProviders(List<FingerprintSensorPropertiesInternal> hidlSensors) {
for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
final Fingerprint21 fingerprint21;
if ((Build.IS_USERDEBUG || Build.IS_ENG)
@@ -1059,11 +937,16 @@
mBiometricStateCallback, hidlSensor, mHandler,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
- mServiceProviders.add(fingerprint21);
+ providers.add(fingerprint21);
}
+
+ return providers;
}
- private void addAidlProviders(List<String> instances) {
+ @NonNull
+ private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+
for (String instance : instances) {
final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
final IFingerprint fp = mIFingerprintProvider.apply(fqName);
@@ -1075,7 +958,7 @@
mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
mBiometricContext);
Slog.i(TAG, "Adding AIDL provider: " + fqName);
- mServiceProviders.add(provider);
+ providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
@@ -1083,38 +966,8 @@
Slog.e(TAG, "Unable to get declared service: " + fqName);
}
}
- }
- // Notifies the callbacks that all of the authenticators have been registered and removes the
- // invoked callbacks from the callback list.
- private void broadcastAllAuthenticatorsRegistered() {
- // Make a local copy of the data so it can be used outside of the synchronized block when
- // making Binder calls.
- final List<IFingerprintAuthenticatorsRegisteredCallback> callbacks = new ArrayList<>();
- final List<FingerprintSensorPropertiesInternal> props;
- synchronized (mLock) {
- if (!mSensorProps.isEmpty()) {
- props = new ArrayList<>(mSensorProps);
- } else {
- Slog.e(TAG, "mSensorProps is empty");
- return;
- }
- final int n = mAuthenticatorsRegisteredCallbacks.beginBroadcast();
- for (int i = 0; i < n; ++i) {
- final IFingerprintAuthenticatorsRegisteredCallback cb =
- mAuthenticatorsRegisteredCallbacks.getBroadcastItem(i);
- callbacks.add(cb);
- mAuthenticatorsRegisteredCallbacks.unregister(cb);
- }
- mAuthenticatorsRegisteredCallbacks.finishBroadcast();
- }
- for (IFingerprintAuthenticatorsRegisteredCallback cb : callbacks) {
- try {
- cb.onAllAuthenticatorsRegistered(props);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception in onAllAuthenticatorsRegistered", e);
- }
- }
+ return providers;
}
@Override
@@ -1122,51 +975,9 @@
publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
}
- @Nullable
- private ServiceProvider getProviderForSensor(int sensorId) {
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return provider;
- }
- }
- return null;
- }
-
- /**
- * For devices with only a single provider, returns that provider. If multiple providers,
- * returns the first one. If no providers, returns null.
- */
- @Nullable
- private Pair<Integer, ServiceProvider> getSingleProvider() {
- final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties();
- if (properties.isEmpty()) {
- Slog.e(TAG, "No providers found");
- return null;
- }
-
- // Theoretically we can just return the first provider, but maybe this is easier to
- // understand.
- final int sensorId = properties.get(0).sensorId;
- for (ServiceProvider provider : mServiceProviders) {
- if (provider.containsSensor(sensorId)) {
- return new Pair<>(sensorId, provider);
- }
- }
-
- Slog.e(TAG, "Provider not found");
- return null;
- }
-
- @NonNull
- private List<FingerprintSensorPropertiesInternal> getSensorProperties() {
- synchronized (mLock) {
- return mSensorProps;
- }
- }
-
@NonNull
private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) {
- final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for getEnrolledFingerprintsDeprecated, caller: "
+ opPackageName);
@@ -1229,7 +1040,7 @@
if (Utils.isVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
- for (ServiceProvider provider : mServiceProviders) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
for (FingerprintSensorPropertiesInternal props : provider.getSensorProperties()) {
provider.scheduleInternalCleanup(props.sensorId, userId, null /* callback */,
true /* favorHalEnrollments */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
new file mode 100644
index 0000000..33810b7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * 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.biometrics.sensors.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.BiometricServiceRegistry;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Registry for {@link IFingerprintService} providers. */
+public class FingerprintServiceRegistry extends BiometricServiceRegistry<ServiceProvider,
+ FingerprintSensorPropertiesInternal, IFingerprintAuthenticatorsRegisteredCallback> {
+
+ private static final String TAG = "FingerprintServiceRegistry";
+
+ @NonNull
+ private final IFingerprintService mService;
+
+ /** Creates a new registry tied to the given service. */
+ public FingerprintServiceRegistry(@NonNull IFingerprintService service,
+ @Nullable Supplier<IBiometricService> biometricSupplier) {
+ super(biometricSupplier);
+ mService = service;
+ }
+
+ @Override
+ protected void registerService(@NonNull IBiometricService service,
+ @NonNull FingerprintSensorPropertiesInternal props) {
+ @BiometricManager.Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+ try {
+ service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
+ new FingerprintAuthenticator(mService, props.sensorId));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
+ }
+ }
+
+ @Override
+ protected void invokeRegisteredCallback(
+ @NonNull IFingerprintAuthenticatorsRegisteredCallback callback,
+ @NonNull List<FingerprintSensorPropertiesInternal> allProps) throws RemoteException {
+ callback.onAllAuthenticatorsRegistered(allProps);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 275d7e4..9075e7e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,14 +28,11 @@
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
-import android.util.proto.ProtoOutputStream;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.List;
/**
@@ -59,23 +56,8 @@
* fail safely.
*/
@SuppressWarnings("deprecation")
-public interface ServiceProvider {
- /**
- * Checks if the specified sensor is owned by this provider.
- */
- boolean containsSensor(int sensorId);
-
- @NonNull
- List<FingerprintSensorPropertiesInternal> getSensorProperties();
-
- /**
- * Returns the internal properties of the specified sensor, if owned by this provider.
- *
- * @param sensorId The ID of a fingerprint sensor, or -1 for any sensor.
- * @return An object representing the internal properties of the specified sensor.
- */
- @Nullable
- FingerprintSensorPropertiesInternal getSensorProperties(int sensorId);
+public interface ServiceProvider extends
+ BiometricServiceProvider<FingerprintSensorPropertiesInternal> {
void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken);
@@ -126,16 +108,11 @@
void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments);
- boolean isHardwareDetected(int sensorId);
-
void rename(int sensorId, int fingerId, int userId, @NonNull String name);
@NonNull
List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId);
- @LockoutTracker.LockoutMode
- int getLockoutModeForUser(int sensorId, int userId);
-
/**
* Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
* be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
@@ -143,7 +120,6 @@
void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback);
- long getAuthenticatorId(int sensorId, int userId);
void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major);
@@ -161,13 +137,6 @@
*/
void setSidefpsController(@NonNull ISidefpsController controller);
- void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
- boolean clearSchedulerBuffer);
-
- void dumpProtoMetrics(int sensorId, @NonNull FileDescriptor fd);
-
- void dumpInternal(int sensorId, @NonNull PrintWriter pw);
-
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 2dc00520..3fe6332 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -565,6 +565,11 @@
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+ }
+
+ @Override
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index ed482f0..0e6df8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -789,6 +789,11 @@
}
@Override
+ public boolean hasEnrollments(int sensorId, int userId) {
+ return !getEnrolledFingerprints(sensorId, userId).isEmpty();
+ }
+
+ @Override
@LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
return mLockoutTracker.getLockoutModeForUser(userId);
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 16a060a..931c692 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -752,7 +752,7 @@
return true;
}
- private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ private Intent buildVpnManagerEventIntent(@NonNull String category, int errorClass,
int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
@NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
@Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
@@ -771,6 +771,20 @@
intent.putExtra(VpnManager.EXTRA_ERROR_CODE, errorCode);
}
+ return intent;
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
+ @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
+ @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+ final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
+ packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
+ return sendEventToVpnManagerApp(intent, packageName);
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull final Intent intent,
+ @NonNull final String packageName) {
// Allow VpnManager app to temporarily run background services to handle this error.
// If an app requires anything beyond this grace period, they MUST either declare
// themselves as a foreground service, or schedule a job/workitem.
@@ -1182,12 +1196,25 @@
mContext.unbindService(mConnection);
cleanupVpnStateLocked();
} else if (mVpnRunner != null) {
- if (!VpnConfig.LEGACY_VPN.equals(mPackage)) {
- notifyVpnManagerVpnStopped(mPackage, mOwnerUID);
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+ // ConnectivityServiceTest.
+ final int ownerUid = mOwnerUID;
+ Intent intent = null;
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+ intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, mPackage,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
}
-
// cleanupVpnStateLocked() is called from mVpnRunner.exit()
mVpnRunner.exit();
+ if (intent != null && isVpnApp(mPackage)) {
+ notifyVpnManagerVpnStopped(mPackage, ownerUid, intent);
+ }
}
try {
@@ -2886,6 +2913,9 @@
final LinkProperties lp;
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mInterface = interfaceName;
mConfig.mtu = maxMtu;
mConfig.interfaze = mInterface;
@@ -2987,6 +3017,9 @@
try {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mConfig.underlyingNetworks = new Network[] {network};
mNetworkCapabilities =
new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3076,7 +3109,12 @@
// Clear mInterface to prevent Ikev2VpnRunner being cleared when
// interfaceRemoved() is called.
- mInterface = null;
+ synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
+ mInterface = null;
+ }
// Without MOBIKE, we have no way to seamlessly migrate. Close on old
// (non-default) network, and start the new one.
resetIkeState();
@@ -3261,6 +3299,9 @@
/** Marks the state as FAILED, and disconnects. */
private void markFailedAndDisconnect(Exception exception) {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
updateState(DetailedState.FAILED, exception.getMessage());
}
@@ -3345,6 +3386,9 @@
}
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
@@ -3371,6 +3415,9 @@
Log.d(TAG, "Resetting state for token: " + mCurrentToken);
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
// Since this method handles non-fatal errors only, set mInterface to null to
// prevent the NetworkManagementEventObserver from killing this VPN based on the
// interface going down (which we expect).
@@ -3993,6 +4040,7 @@
mConfig.proxyInfo = profile.proxy;
mConfig.requiresInternetValidation = profile.requiresInternetValidation;
mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
+ mConfig.allowBypass = profile.isBypassable;
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4042,13 +4090,23 @@
// To stop the VPN profile, the caller must be the current prepared package and must be
// running an Ikev2VpnProfile.
if (isCurrentIkev2VpnLocked(packageName)) {
- notifyVpnManagerVpnStopped(packageName, mOwnerUID);
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ final int ownerUid = mOwnerUID;
+ final Intent intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, packageName,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
mVpnRunner.exit();
+ notifyVpnManagerVpnStopped(packageName, ownerUid, intent);
}
}
- private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID) {
+ private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID,
+ Intent intent) {
mAppOpsManager.finishOp(
AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, ownerUID, packageName, null);
// The underlying network, NetworkCapabilities and LinkProperties are not
@@ -4057,10 +4115,7 @@
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
// ConnectivityServiceTest.
if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
- -1 /* errorClass */, -1 /* errorCode*/, packageName,
- getSessionKeyLocked(), makeVpnProfileStateLocked(),
- null /* underlyingNetwork */, null /* nc */, null /* lp */);
+ sendEventToVpnManagerApp(intent, packageName);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 8807e19..2476350 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -813,13 +813,7 @@
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
loadFromDisplayDeviceConfig(token, info);
- if (DEBUG) {
- Trace.beginAsyncSection("DisplayPowerController#updatePowerState", 0);
- }
updatePowerState();
- if (DEBUG) {
- Trace.endAsyncSection("DisplayPowerController#updatePowerState", 0);
- }
}
});
}
@@ -1147,6 +1141,16 @@
}
private void updatePowerState() {
+ if (DEBUG) {
+ Trace.beginSection("DisplayPowerController#updatePowerState");
+ }
+ updatePowerStateInternal();
+ if (DEBUG) {
+ Trace.endSection();
+ }
+ }
+
+ private void updatePowerStateInternal() {
// Update the power state request.
final boolean mustNotify;
final int previousPolicy;
@@ -1645,11 +1649,11 @@
mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
- | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0));
- mTempBrightnessEvent.setRbcStrength((mCdsi != null && mCdsi.isReduceBrightColorsActivated())
+ | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
+ | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+ mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
- mTempBrightnessEvent.setPowerFactor(
- mPowerRequest.lowPowerMode ? mPowerRequest.screenLowPowerBrightnessFactor : 1.0f);
+ mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1662,10 +1666,10 @@
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
mTempBrightnessEvent.setFastAmbientLux(
mAutomaticBrightnessController == null
- ? -1 : mAutomaticBrightnessController.getFastAmbientLux());
+ ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
mTempBrightnessEvent.setSlowAmbientLux(
mAutomaticBrightnessController == null
- ? -1 : mAutomaticBrightnessController.getSlowAmbientLux());
+ ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2769,12 +2773,13 @@
}
private void logManualBrightnessEvent(BrightnessEvent event) {
- float hbmMaxNits =
+ float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
+ int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
+ float appliedHbmMaxNits =
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
? -1f : convertToNits(event.getHbmMax());
-
// thermalCapNits set to -1 if not currently capping max brightness
- float thermalCapNits =
+ float appliedThermalCapNits =
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
? -1f : convertToNits(event.getThermalMax());
@@ -2784,10 +2789,10 @@
event.getSlowAmbientLux(),
event.getPhysicalDisplayId(),
event.isShortTermModelActive(),
- event.getPowerFactor(),
- event.getRbcStrength(),
- hbmMaxNits,
- thermalCapNits,
+ appliedLowPowerMode,
+ appliedRbcStrength,
+ appliedHbmMaxNits,
+ appliedThermalCapNits,
event.isAutomaticBrightnessEnabled(),
FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index 70415a3..e3fa622 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -21,6 +21,8 @@
import android.os.SystemClock;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Represents a particular brightness change event.
*/
@@ -29,7 +31,8 @@
public static final int FLAG_INVALID_LUX = 0x2;
public static final int FLAG_DOZE_SCALE = 0x4;
public static final int FLAG_USER_SET = 0x8;
- public static final int FLAG_IDLE_CURVE = 0x16;
+ public static final int FLAG_IDLE_CURVE = 0x10;
+ public static final int FLAG_LOW_POWER_MODE = 0x20;
private BrightnessReason mReason = new BrightnessReason();
private int mDisplayId;
@@ -317,6 +320,10 @@
this.mRbcStrength = mRbcStrength;
}
+ public boolean isRbcEnabled() {
+ return (mFlags & FLAG_RBC) != 0;
+ }
+
public float getThermalMax() {
return mThermalMax;
}
@@ -333,6 +340,10 @@
this.mPowerFactor = mPowerFactor;
}
+ public boolean isLowPowerModeSet() {
+ return (mFlags & FLAG_LOW_POWER_MODE) != 0;
+ }
+
public int getFlags() {
return mFlags;
}
@@ -361,11 +372,17 @@
this.mAutomaticBrightnessEnabled = mAutomaticBrightnessEnabled;
}
- private String flagsToString() {
+ /**
+ * A utility to stringify flags from a BrightnessEvent
+ * @return Stringified flags from BrightnessEvent
+ */
+ @VisibleForTesting
+ public String flagsToString() {
return ((mFlags & FLAG_USER_SET) != 0 ? "user_set " : "")
+ ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
+ ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
+ ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
- + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "");
+ + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "")
+ + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "");
}
}
diff --git a/services/core/java/com/android/server/hdmi/CecMessageBuffer.java b/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
index 8f971fd..0c73582 100644
--- a/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/CecMessageBuffer.java
@@ -48,6 +48,12 @@
case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
bufferSystemAudioModeRequest(message);
return true;
+ case Constants.MESSAGE_ROUTING_CHANGE:
+ bufferRoutingChange(message);
+ return true;
+ case Constants.MESSAGE_SET_STREAM_PATH:
+ bufferSetStreamPath(message);
+ return true;
// Add here if new message that needs to buffer
default:
// Do not need to buffer messages other than above
@@ -89,6 +95,22 @@
}
}
+ private void bufferRoutingChange(HdmiCecMessage message) {
+ if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ROUTING_CHANGE)) {
+ mBuffer.add(message);
+ }
+ }
+
+ private void bufferSetStreamPath(HdmiCecMessage message) {
+ if (!replaceMessageIfBuffered(message, Constants.MESSAGE_SET_STREAM_PATH)) {
+ mBuffer.add(message);
+ }
+ }
+
+ public List<HdmiCecMessage> getBuffer() {
+ return new ArrayList<>(mBuffer);
+ }
+
// Returns true if the message is replaced
private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
for (int i = 0; i < mBuffer.size(); i++) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 97e9c64..5aa3fa4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -147,6 +147,9 @@
private final HdmiCecAtomWriter mHdmiCecAtomWriter;
+ // This variable is used for testing, in order to delay the logical address allocation.
+ private long mLogicalAddressAllocationDelay = 0;
+
// Private constructor. Use HdmiCecController.create().
private HdmiCecController(
HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
@@ -215,12 +218,12 @@
final AllocateAddressCallback callback) {
assertRunOnServiceThread();
- runOnIoThread(new Runnable() {
+ mIoHandler.postDelayed(new Runnable() {
@Override
public void run() {
handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
}
- });
+ }, mLogicalAddressAllocationDelay);
}
/**
@@ -386,6 +389,14 @@
}
/**
+ * This method is used for testing, in order to delay the logical address allocation.
+ */
+ @VisibleForTesting
+ void setLogicalAddressAllocationDelay(long delay) {
+ mLogicalAddressAllocationDelay = delay;
+ }
+
+ /**
* Returns true if the language code is well-formed.
*/
@VisibleForTesting static boolean isLanguage(String language) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 1de1a7a..2622cef 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -649,6 +649,13 @@
return Constants.NOT_HANDLED;
}
+ /**
+ * Called after logical address allocation is finished, allowing a local device to react to
+ * messages in the buffer before they are processed. This method may be used to cancel deferred
+ * actions.
+ */
+ protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {}
+
@Constants.RcProfile
protected abstract int getRcProfile();
@@ -963,8 +970,10 @@
}
@ServiceThreadOnly
- final void handleAddressAllocated(int logicalAddress, int reason) {
+ final void handleAddressAllocated(
+ int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason) {
assertRunOnServiceThread();
+ preprocessBufferedMessages(bufferedMessages);
mPreferredAddress = logicalAddress;
updateDeviceFeatures();
if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 5cfe27a..ea54b30 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -530,6 +530,25 @@
}
}
+ /**
+ * Called after logical address allocation is finished, allowing a local device to react to
+ * messages in the buffer before they are processed. This method may be used to cancel deferred
+ * actions.
+ */
+ @Override
+ protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {
+ for (HdmiCecMessage message: bufferedMessages) {
+ // Prevent the device from broadcasting <Active Source> message if the active path
+ // changed during address allocation.
+ if (message.getOpcode() == Constants.MESSAGE_ROUTING_CHANGE
+ || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH
+ || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
+ removeAction(ActiveSourceAction.class);
+ return;
+ }
+ }
+ }
+
@Override
protected int findKeyReceiverAddress() {
return Constants.ADDR_TV;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 6c37bf1..fa8b5c1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1079,9 +1079,10 @@
@ServiceThreadOnly
private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
assertRunOnServiceThread();
+ List<HdmiCecMessage> bufferedMessages = mCecMessageBuffer.getBuffer();
for (HdmiCecLocalDevice device : devices) {
int address = device.getDeviceInfo().getLogicalAddress();
- device.handleAddressAllocated(address, initiatedBy);
+ device.handleAddressAllocated(address, bufferedMessages, initiatedBy);
}
if (isTvDeviceEnabled()) {
tv().setSelectRequestBuffer(mSelectRequestBuffer);
@@ -1361,8 +1362,9 @@
@Constants.HandleMessageResult int handleMessageResult =
dispatchMessageToLocalDevice(message);
- if (handleMessageResult == Constants.NOT_HANDLED
- && !mAddressAllocated
+ // mAddressAllocated is false during address allocation, meaning there is no device to
+ // handle the message, so it should be buffered, if possible.
+ if (!mAddressAllocated
&& mCecMessageBuffer.bufferMessage(message)) {
return Constants.HANDLED;
}
@@ -3286,7 +3288,8 @@
}
@ServiceThreadOnly
- private void onWakeUp(@WakeReason final int wakeUpAction) {
+ @VisibleForTesting
+ protected void onWakeUp(@WakeReason final int wakeUpAction) {
assertRunOnServiceThread();
mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
false);
diff --git a/services/core/java/com/android/server/location/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java
index ca2ff60..f7da0d8 100644
--- a/services/core/java/com/android/server/location/LocationPermissions.java
+++ b/services/core/java/com/android/server/location/LocationPermissions.java
@@ -26,8 +26,10 @@
import android.content.Context;
import android.os.Binder;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
/** Utility class for dealing with location permissions. */
public final class LocationPermissions {
@@ -49,6 +51,7 @@
*/
public static final int PERMISSION_FINE = 2;
+ @Target(ElementType.TYPE_USE)
@IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionLevel {}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceKey.java b/services/core/java/com/android/server/location/geofence/GeofenceKey.java
deleted file mode 100644
index bbfa68f..0000000
--- a/services/core/java/com/android/server/location/geofence/GeofenceKey.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.geofence;
-
-import android.app.PendingIntent;
-import android.location.Geofence;
-
-import com.android.server.location.listeners.PendingIntentListenerRegistration;
-
-import java.util.Objects;
-
-// geofencing unfortunately allows multiple geofences under the same pending intent, even though
-// this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
-// geofence) instead of (pendingintent).
-final class GeofenceKey implements PendingIntentListenerRegistration.PendingIntentKey {
-
- private final PendingIntent mPendingIntent;
- private final Geofence mGeofence;
-
- GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
- mPendingIntent = Objects.requireNonNull(pendingIntent);
- mGeofence = Objects.requireNonNull(geofence);
- }
-
- @Override
- public PendingIntent getPendingIntent() {
- return mPendingIntent;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof GeofenceKey)) {
- return false;
- }
- GeofenceKey that = (GeofenceKey) o;
- return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(that.mGeofence);
- }
-
- @Override
- public int hashCode() {
- return mPendingIntent.hashCode();
- }
-}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index b6342a4..0f5e3d4 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -59,8 +59,8 @@
* Manages all geofences.
*/
public class GeofenceManager extends
- ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
- LocationRequest> implements
+ ListenerMultiplexer<GeofenceManager.GeofenceKey, PendingIntent,
+ GeofenceManager.GeofenceRegistration, LocationRequest> implements
LocationListener {
private static final String TAG = "GeofenceManager";
@@ -73,13 +73,49 @@
private static final long MAX_LOCATION_AGE_MS = 5 * 60 * 1000L; // five minutes
private static final long MAX_LOCATION_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
- protected final class GeofenceRegistration extends
- PendingIntentListenerRegistration<Geofence, PendingIntent> {
+ // geofencing unfortunately allows multiple geofences under the same pending intent, even though
+ // this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
+ // geofence) instead of (pendingintent).
+ static class GeofenceKey {
+
+ private final PendingIntent mPendingIntent;
+ private final Geofence mGeofence;
+
+ GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
+ mPendingIntent = Objects.requireNonNull(pendingIntent);
+ mGeofence = Objects.requireNonNull(geofence);
+ }
+
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GeofenceKey) {
+ GeofenceKey that = (GeofenceKey) o;
+ return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(
+ that.mGeofence);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPendingIntent.hashCode();
+ }
+ }
+
+ protected class GeofenceRegistration extends
+ PendingIntentListenerRegistration<GeofenceKey, PendingIntent> {
private static final int STATE_UNKNOWN = 0;
private static final int STATE_INSIDE = 1;
private static final int STATE_OUTSIDE = 2;
+ private final Geofence mGeofence;
+ private final CallerIdentity mIdentity;
private final Location mCenter;
private final PowerManager.WakeLock mWakeLock;
@@ -89,13 +125,15 @@
// spam us, and because checking the values may be more expensive
private boolean mPermitted;
- private @Nullable Location mCachedLocation;
+ @Nullable private Location mCachedLocation;
private float mCachedLocationDistanceM;
- protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
+ GeofenceRegistration(Geofence geofence, CallerIdentity identity,
PendingIntent pendingIntent) {
- super(geofence, identity, pendingIntent);
+ super(pendingIntent);
+ mGeofence = geofence;
+ mIdentity = identity;
mCenter = new Location("");
mCenter.setLatitude(geofence.getLatitude());
mCenter.setLongitude(geofence.getLongitude());
@@ -107,16 +145,36 @@
mWakeLock.setWorkSource(identity.addToWorkSource(null));
}
+ public Geofence getGeofence() {
+ return mGeofence;
+ }
+
+ public CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) {
+ return geofenceKey.getPendingIntent();
+ }
+
@Override
protected GeofenceManager getOwner() {
return GeofenceManager.this;
}
@Override
- protected void onPendingIntentListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
mGeofenceState = STATE_UNKNOWN;
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
}
@Override
@@ -132,7 +190,7 @@
}
boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -140,7 +198,7 @@
}
boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
+ if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -149,7 +207,7 @@
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -164,12 +222,12 @@
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
}
- return Math.abs(getRequest().getRadius() - mCachedLocationDistanceM);
+ return Math.abs(mGeofence.getRadius() - mCachedLocationDistanceM);
}
ListenerOperation<PendingIntent> onLocationChanged(Location location) {
// remove expired fences
- if (getRequest().isExpired()) {
+ if (mGeofence.isExpired()) {
remove();
return null;
}
@@ -178,7 +236,7 @@
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
int oldState = mGeofenceState;
- float radius = Math.max(getRequest().getRadius(), location.getAccuracy());
+ float radius = Math.max(mGeofence.getRadius(), location.getAccuracy());
if (mCachedLocationDistanceM <= radius) {
mGeofenceState = STATE_INSIDE;
if (oldState != STATE_INSIDE) {
@@ -206,14 +264,14 @@
null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
} catch (PendingIntent.CanceledException e) {
mWakeLock.release();
- removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this);
+ removeRegistration(new GeofenceKey(pendingIntent, mGeofence), this);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(getIdentity());
+ builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(1);
if (!mPermitted) {
@@ -223,7 +281,7 @@
builder.append(" ").append(flags);
}
- builder.append(" ").append(getRequest());
+ builder.append(" ").append(mGeofence);
return builder.toString();
}
}
@@ -258,10 +316,10 @@
protected final LocationUsageLogger mLocationUsageLogger;
@GuardedBy("mLock")
- private @Nullable LocationManager mLocationManager;
+ @Nullable private LocationManager mLocationManager;
@GuardedBy("mLock")
- private @Nullable Location mLastLocation;
+ @Nullable private Location mLastLocation;
public GeofenceManager(Context context, Injector injector) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
@@ -271,11 +329,6 @@
mLocationUsageLogger = injector.getLocationUsageLogger();
}
- @Override
- public String getTag() {
- return TAG;
- }
-
private LocationManager getLocationManager() {
synchronized (mLock) {
if (mLocationManager == null) {
@@ -375,7 +428,7 @@
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
- registration.getRequest(), true);
+ registration.getGeofence(), true);
}
@Override
@@ -389,7 +442,7 @@
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
- registration.getRequest(), true);
+ registration.getGeofence(), true);
}
@Override
@@ -417,7 +470,7 @@
WorkSource workSource = null;
double minFenceDistanceM = Double.MAX_VALUE;
for (GeofenceRegistration registration : registrations) {
- if (registration.getRequest().isExpired(realtimeMs)) {
+ if (registration.getGeofence().isExpired(realtimeMs)) {
continue;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
index e375007..62ab22a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.gnss.GnssManagerService.TAG;
import android.annotation.Nullable;
@@ -25,6 +26,7 @@
import android.os.Binder;
import android.os.IBinder;
+import com.android.server.FgThread;
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.listeners.BinderListenerRegistration;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -45,17 +47,35 @@
* Registration object for GNSS listeners.
*/
protected class AntennaInfoListenerRegistration extends
- BinderListenerRegistration<Void, IGnssAntennaInfoListener> {
+ BinderListenerRegistration<IBinder, IGnssAntennaInfoListener> {
- protected AntennaInfoListenerRegistration(CallerIdentity callerIdentity,
+ private final CallerIdentity mIdentity;
+
+ protected AntennaInfoListenerRegistration(CallerIdentity identity,
IGnssAntennaInfoListener listener) {
- super(null, callerIdentity, listener);
+ super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+ mIdentity = identity;
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
}
@Override
protected GnssAntennaInfoProvider getOwner() {
return GnssAntennaInfoProvider.this;
}
+
+ @Override
+ protected IBinder getBinderFromKey(IBinder key) {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return mIdentity.toString();
+ }
}
private final GnssNative mGnssNative;
@@ -72,11 +92,6 @@
return mAntennaInfos;
}
- @Override
- public String getTag() {
- return TAG;
- }
-
public boolean isSupported() {
return mGnssNative.isAntennaInfoSupported();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index a540476..82bcca2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -18,6 +18,7 @@
import static android.location.LocationManager.GPS_PROVIDER;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.TAG;
@@ -33,6 +34,7 @@
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.Injector;
@@ -67,16 +69,34 @@
* Registration object for GNSS listeners.
*/
protected class GnssListenerRegistration extends
- BinderListenerRegistration<TRequest, TListener> {
+ BinderListenerRegistration<IBinder, TListener> {
+
+ private final TRequest mRequest;
+ private final CallerIdentity mIdentity;
// we store these values because we don't trust the listeners not to give us dupes, not to
// spam us, and because checking the values may be more expensive
private boolean mForeground;
private boolean mPermitted;
- protected GnssListenerRegistration(@Nullable TRequest request,
- CallerIdentity callerIdentity, TListener listener) {
- super(request, callerIdentity, listener);
+ protected GnssListenerRegistration(TRequest request, CallerIdentity identity,
+ TListener listener) {
+ super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+ mRequest = request;
+ mIdentity = identity;
+ }
+
+ public final TRequest getRequest() {
+ return mRequest;
+ }
+
+ public final CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
}
@Override
@@ -84,6 +104,11 @@
return GnssListenerMultiplexer.this;
}
+ @Override
+ protected IBinder getBinderFromKey(IBinder key) {
+ return key;
+ }
+
/**
* Returns true if this registration is currently in the foreground.
*/
@@ -96,31 +121,16 @@
}
@Override
- protected final void onBinderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
- mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
-
- onGnssListenerRegister();
+ mIdentity);
+ mForeground = mAppForegroundHelper.isAppForeground(mIdentity.getUid());
}
- @Override
- protected final void onBinderListenerUnregister() {
- onGnssListenerUnregister();
- }
-
- /**
- * May be overridden in place of {@link #onBinderListenerRegister()}.
- */
- protected void onGnssListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onBinderListenerUnregister()}.
- */
- protected void onGnssListenerUnregister() {}
-
boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -128,7 +138,7 @@
}
boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
+ if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -137,7 +147,7 @@
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -147,7 +157,7 @@
}
boolean onForegroundChanged(int uid, boolean foreground) {
- if (getIdentity().getUid() == uid && foreground != mForeground) {
+ if (mIdentity.getUid() == uid && foreground != mForeground) {
mForeground = foreground;
return true;
}
@@ -158,7 +168,7 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(getIdentity());
+ builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(2);
if (!mForeground) {
@@ -171,8 +181,8 @@
builder.append(" ").append(flags);
}
- if (getRequest() != null) {
- builder.append(" ").append(getRequest());
+ if (mRequest != null) {
+ builder.append(" ").append(mRequest);
}
return builder.toString();
}
@@ -218,11 +228,6 @@
LocalServices.getService(LocationManagerInternal.class));
}
- @Override
- public String getTag() {
- return TAG;
- }
-
/**
* May be overridden by subclasses to return whether the service is supported or not. This value
* should never change for the lifetime of the multiplexer. If the service is unsupported, all
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index e4e9d01..9f2a9cf 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -40,10 +40,7 @@
import java.util.Collection;
/**
- * An base implementation for GNSS measurements provider. It abstracts out the responsibility of
- * handling listeners, while still allowing technology specific implementations to be built.
- *
- * @hide
+ * GNSS measurements HAL module and listener multiplexer.
*/
public final class GnssMeasurementsProvider extends
GnssListenerMultiplexer<GnssMeasurementRequest, IGnssMeasurementsListener,
@@ -61,7 +58,9 @@
}
@Override
- protected void onGnssListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
executeOperation(listener -> listener.onStatusChanged(
GnssMeasurementsEvent.Callback.STATUS_READY));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index e9fce05..63134bb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -32,11 +32,7 @@
import java.util.Collection;
/**
- * An base implementation for GPS navigation messages provider.
- * It abstracts out the responsibility of handling listeners, while still allowing technology
- * specific implementations to be built.
- *
- * @hide
+ * GNSS navigation message HAL module and listener multiplexer.
*/
public class GnssNavigationMessageProvider extends
GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> implements
@@ -51,7 +47,9 @@
}
@Override
- protected void onGnssListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
executeOperation(listener -> listener.onStatusChanged(
GnssNavigationMessage.Callback.STATUS_READY));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
index bfef978..d4e38b6a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
@@ -34,7 +34,7 @@
import java.util.function.Function;
/**
- * Implementation of a handler for {@link IGnssNmeaListener}.
+ * GNSS NMEA HAL module and listener multiplexer.
*/
class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener, Void> implements
GnssNative.BaseCallbacks, GnssNative.NmeaCallbacks {
@@ -97,7 +97,7 @@
ListenerExecutor.ListenerOperation<IGnssNmeaListener>>() {
// only read in the nmea string if we need to
- private @Nullable String mNmea;
+ @Nullable private String mNmea;
@Override
public ListenerExecutor.ListenerOperation<IGnssNmeaListener> apply(
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 0ce36d6..41fa7a1 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -35,7 +35,7 @@
import java.util.Collection;
/**
- * Implementation of a handler for {@link IGnssStatusListener}.
+ * GNSS status HAL module and listener multiplexer.
*/
public class GnssStatusProvider extends
GnssListenerMultiplexer<Void, IGnssStatusListener, Void> implements
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index 709e236..5555aeb 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -16,71 +16,59 @@
package com.android.server.location.listeners;
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Executor;
+
/**
* A registration that works with IBinder keys, and registers a DeathListener to automatically
- * remove the registration if the binder dies. The key for this registration must either be an
- * {@link IBinder} or a {@link BinderKey}.
+ * remove the registration if the binder dies.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class BinderListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
+public abstract class BinderListenerRegistration<TKey, TListener> extends
+ RemovableListenerRegistration<TKey, TListener> implements DeathRecipient {
- /**
- * Interface to allow binder retrieval when keys are not themselves IBinders.
- */
- public interface BinderKey {
- /**
- * Returns the binder associated with this key.
- */
- IBinder getBinder();
+ protected BinderListenerRegistration(Executor executor, TListener listener) {
+ super(executor, listener);
}
- protected BinderListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
- TListener listener) {
- super(request, callerIdentity, listener);
- }
+ protected abstract IBinder getBinderFromKey(TKey key);
@Override
- protected final void onRemovableListenerRegister() {
- IBinder binder = getBinderFromKey(getKey());
+ protected void onRegister() {
+ super.onRegister();
+
try {
- binder.linkToDeath(this, 0);
+ getBinderFromKey(getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
remove();
}
-
- onBinderListenerRegister();
}
@Override
- protected final void onRemovableListenerUnregister() {
- onBinderListenerUnregister();
- getBinderFromKey(getKey()).unlinkToDeath(this, 0);
+ protected void onUnregister() {
+ try {
+ getBinderFromKey(getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been thrown
+ // prior to registration completing, and that exception is currently unwinding the call
+ // stack and causing this cleanup. since that exception should crash us anyways, drop
+ // this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
- /**
- * May be overridden in place of {@link #onRemovableListenerRegister()}.
- */
- protected void onBinderListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onRemovableListenerUnregister()}.
- */
- protected void onBinderListenerUnregister() {}
-
- @Override
public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof RemoteException) {
- Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -90,9 +78,10 @@
@Override
public void binderDied() {
try {
- if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
- Log.d(getOwner().getTag(), "binder registration " + getIdentity() + " died");
+ if (Log.isLoggable(getTag(), Log.DEBUG)) {
+ Log.d(getTag(), "binder registration " + this + " died");
}
+
remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
@@ -100,14 +89,4 @@
throw new AssertionError(e);
}
}
-
- private static IBinder getBinderFromKey(Object key) {
- if (key instanceof IBinder) {
- return (IBinder) key;
- } else if (key instanceof BinderKey) {
- return ((BinderKey) key).getBinder();
- } else {
- throw new IllegalArgumentException("key must be IBinder or BinderKey");
- }
- }
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 33b08d4..67ae265 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -37,40 +36,48 @@
import java.util.function.Predicate;
/**
- * A base class to multiplex client listener registrations within system server. Every listener is
+ * A base class to multiplex some event source to multiple listener registrations. Every listener is
* represented by a registration object which stores all required state for a listener. Keys are
* used to uniquely identify every registration. Listener operations may be executed on
* registrations in order to invoke the represented listener.
*
- * Registrations are divided into two categories, active registrations and inactive registrations,
- * as defined by {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
- * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
- * whose active state may have changed. Listeners will only be invoked for active registrations.
+ * <p>Registrations are divided into two categories, active registrations and inactive
+ * registrations, as defined by {@link #isActive(ListenerRegistration)}. The set of active
+ * registrations is combined into a single merged registration, which is submitted to the backing
+ * event source when necessary in order to register with the event source. The merged registration
+ * is updated whenever the set of active registration changes. Listeners will only be invoked for
+ * active registrations.
*
- * The set of active registrations is combined into a single merged registration, which is submitted
- * to the backing service when necessary in order to register the service. The merged registration
- * is updated whenever the set of active registration changes.
+ * <p>In order to inform the multiplexer of state changes, if a registration's active state changes,
+ * or if the merged registration changes, {@link #updateRegistrations(Predicate)} or {@link
+ * #updateRegistration(Object, Predicate)} must be invoked and return true for any registration
+ * whose state may have changed in such a way that the active state or merged registration state has
+ * changed. It is acceptable to return true from a predicate even if nothing has changed, though
+ * this may result in extra pointless work.
*
- * Callbacks invoked for various changes will always be ordered according to this lifecycle list:
+ * <p>Callbacks invoked for various changes will always be ordered according to this lifecycle list:
*
* <ul>
- * <li>{@link #onRegister()}</li>
- * <li>{@link ListenerRegistration#onRegister(Object)}</li>
- * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
- * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
- * invoked if this registration is replacing a prior registration)</li>
- * <li>{@link #onActive()}</li>
- * <li>{@link ListenerRegistration#onActive()}</li>
- * <li>{@link ListenerRegistration#onInactive()}</li>
- * <li>{@link #onInactive()}</li>
- * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}</li>
- * <li>{@link ListenerRegistration#onUnregister()}</li>
- * <li>{@link #onUnregister()}</li>
+ * <li>{@link #onRegister()}
+ * <li>{@link ListenerRegistration#onRegister(Object)}
+ * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}
+ * <li>{@link #onActive()}
+ * <li>{@link ListenerRegistration#onActive()}
+ * <li>{@link ListenerRegistration#onInactive()}
+ * <li>{@link #onInactive()}
+ * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}
+ * <li>{@link ListenerRegistration#onUnregister()}
+ * <li>{@link #onUnregister()}
* </ul>
*
- * Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
- * other operation or callback. Removal is allowed re-entrantly, however only via
- * {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
+ * <p>If one registration replaces another, then {@link #onRegistrationReplaced(Object,
+ * ListenerRegistration, Object, ListenerRegistration)} is invoked instead of {@link
+ * #onRegistrationRemoved(Object, ListenerRegistration)} and {@link #onRegistrationAdded(Object,
+ * ListenerRegistration)}.
+ *
+ * <p>Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
+ * other operation or callback). Removal is allowed re-entrantly, however only via {@link
+ * #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
* ensures re-entrant removal does not accidentally remove the incorrect registration.
*
* @param <TKey> key type
@@ -81,30 +88,31 @@
public abstract class ListenerMultiplexer<TKey, TListener,
TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> {
- @GuardedBy("mRegistrations")
+ /**
+ * The lock object used by the multiplexer. Acquiring this lock allows for multiple operations
+ * on the multiplexer to be completed atomically. Otherwise, it is not required to hold this
+ * lock. This lock is held while invoking all lifecycle callbacks on both the multiplexer and
+ * any registrations.
+ */
+ protected final Object mMultiplexerLock = new Object();
+
+ @GuardedBy("mMultiplexerLock")
private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
- @GuardedBy("mRegistrations")
private final UpdateServiceBuffer mUpdateServiceBuffer = new UpdateServiceBuffer();
- @GuardedBy("mRegistrations")
private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private int mActiveRegistrationsCount = 0;
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private boolean mServiceRegistered = false;
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
@Nullable private TMergedRegistration mMerged;
/**
- * Should be implemented to return a unique identifying tag that may be used for logging, etc...
- */
- public abstract @NonNull String getTag();
-
- /**
* Should be implemented to register with the backing service with the given merged
* registration, and should return true if a matching call to {@link #unregisterWithService()}
* is required to unregister (ie, if registration succeeds). The set of registrations passed in
@@ -120,6 +128,7 @@
* @see #mergeRegistrations(Collection)
* @see #reregisterWithService(Object, Object, Collection)
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract boolean registerWithService(TMergedRegistration merged,
@NonNull Collection<TRegistration> registrations);
@@ -130,6 +139,7 @@
*
* @see #registerWithService(Object, Collection)
*/
+ @GuardedBy("mMultiplexerLock")
protected boolean reregisterWithService(TMergedRegistration oldMerged,
TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
return registerWithService(newMerged, registrations);
@@ -138,6 +148,7 @@
/**
* Should be implemented to unregister from the backing service.
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract void unregisterWithService();
/**
@@ -147,6 +158,7 @@
* {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
* any registrations that may have changed their active state.
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract boolean isActive(@NonNull TRegistration registration);
/**
@@ -157,7 +169,8 @@
* {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
* merged registration so that the backing service can be updated.
*/
- protected abstract @Nullable TMergedRegistration mergeRegistrations(
+ @GuardedBy("mMultiplexerLock")
+ protected abstract TMergedRegistration mergeRegistrations(
@NonNull Collection<TRegistration> registrations);
/**
@@ -166,6 +179,7 @@
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegister() {}
/**
@@ -174,28 +188,38 @@
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onUnregister() {}
/**
* Invoked when a registration is added. Invoked while holding the multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
- * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
- * registration is replacing an old registration. The old registration will have already been
- * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
- * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
+ * Invoked when one registration replaces another (through {@link #replaceRegistration(Object,
+ * Object, ListenerRegistration)}). The old registration has already been unregistered at this
+ * point. Invoked while holding the multiplexer's internal lock.
+ *
+ * <p>The default behavior is simply to call first {@link #onRegistrationRemoved(Object,
+ * ListenerRegistration)} and then {@link #onRegistrationAdded(Object, ListenerRegistration)}.
*/
- protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
+ @GuardedBy("mMultiplexerLock")
+ protected void onRegistrationReplaced(
+ @NonNull TKey oldKey,
+ @NonNull TRegistration oldRegistration,
+ @NonNull TKey newKey,
@NonNull TRegistration newRegistration) {
- onRegistrationAdded(key, newRegistration);
+ onRegistrationRemoved(oldKey, oldRegistration);
+ onRegistrationAdded(newKey, newRegistration);
}
/**
* Invoked when a registration is removed. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
@@ -204,6 +228,7 @@
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onActive() {}
/**
@@ -212,6 +237,7 @@
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onInactive() {}
/**
@@ -224,13 +250,12 @@
/**
* Atomically removes the registration with the old key and adds a new registration with the
- * given key. If there was a registration for the old key,
- * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
- * invoked for the new registration and key instead of
- * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
- * the same key. The old key may be the same value as the new key, in which case this function
- * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
- * be called to add a registration re-entrantly.
+ * given key. If there was a registration for the old key, {@link
+ * #onRegistrationReplaced(Object, ListenerRegistration, Object, ListenerRegistration)} will be
+ * invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)}, even if they
+ * share the same key. The old key may be the same value as the new key, in which case this
+ * function is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method
+ * cannot be called to add a registration re-entrantly.
*/
protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
@NonNull TRegistration registration) {
@@ -238,7 +263,7 @@
Objects.requireNonNull(key);
Objects.requireNonNull(registration);
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// adding listeners reentrantly is not supported
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -257,12 +282,18 @@
boolean wasEmpty = mRegistrations.isEmpty();
TRegistration oldRegistration = null;
- int index = mRegistrations.indexOfKey(oldKey);
- if (index >= 0) {
- oldRegistration = removeRegistration(index, oldKey != key);
+ int oldIndex = mRegistrations.indexOfKey(oldKey);
+ if (oldIndex >= 0) {
+ // remove ourselves instead of using remove(), to balance registration callbacks
+ oldRegistration = mRegistrations.valueAt(oldIndex);
+ unregister(oldRegistration);
+ oldRegistration.onUnregister();
+ if (oldKey != key) {
+ mRegistrations.removeAt(oldIndex);
+ }
}
- if (oldKey == key && index >= 0) {
- mRegistrations.setValueAt(index, registration);
+ if (oldKey == key && oldIndex >= 0) {
+ mRegistrations.setValueAt(oldIndex, registration);
} else {
mRegistrations.put(key, registration);
}
@@ -274,7 +305,7 @@
if (oldRegistration == null) {
onRegistrationAdded(key, registration);
} else {
- onRegistrationReplaced(key, oldRegistration, registration);
+ onRegistrationReplaced(oldKey, oldRegistration, key, registration);
}
onRegistrationActiveChanged(registration);
}
@@ -282,29 +313,11 @@
}
/**
- * Removes the registration with the given key. This method cannot be called to remove a
- * registration re-entrantly.
- */
- protected final void removeRegistration(@NonNull Object key) {
- synchronized (mRegistrations) {
- // this method does not support removing listeners reentrantly
- Preconditions.checkState(!mReentrancyGuard.isReentrant());
-
- int index = mRegistrations.indexOfKey(key);
- if (index < 0) {
- return;
- }
-
- removeRegistration(index, true);
- }
- }
-
- /**
* Removes all registrations with keys that satisfy the given predicate. This method cannot be
* called to remove a registration re-entrantly.
*/
protected final void removeRegistrationIf(@NonNull Predicate<TKey> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// this method does not support removing listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -329,13 +342,31 @@
}
/**
+ * Removes the registration with the given key. This method cannot be called to remove a
+ * registration re-entrantly.
+ */
+ protected final void removeRegistration(TKey key) {
+ synchronized (mMultiplexerLock) {
+ // this method does not support removing listeners reentrantly
+ Preconditions.checkState(!mReentrancyGuard.isReentrant());
+
+ int index = mRegistrations.indexOfKey(key);
+ if (index < 0) {
+ return;
+ }
+
+ removeRegistration(index);
+ }
+ }
+
+ /**
* Removes the given registration with the given key. If the given key has a different
* registration at the time this method is called, nothing happens. This method allows for
* re-entrancy, and may be called to remove a registration re-entrantly.
*/
- protected final void removeRegistration(@NonNull Object key,
+ protected final void removeRegistration(@NonNull TKey key,
@NonNull ListenerRegistration<?> registration) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
return;
@@ -350,17 +381,13 @@
unregister(typedRegistration);
mReentrancyGuard.markForRemoval(key, typedRegistration);
} else {
- removeRegistration(index, true);
+ removeRegistration(index);
}
}
}
- @GuardedBy("mRegistrations")
- private TRegistration removeRegistration(int index, boolean removeEntry) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
-
+ @GuardedBy("mMultiplexerLock")
+ private void removeRegistration(int index) {
TKey key = mRegistrations.keyAt(index);
TRegistration registration = mRegistrations.valueAt(index);
@@ -376,15 +403,11 @@
unregister(registration);
onRegistrationRemoved(key, registration);
registration.onUnregister();
- if (removeEntry) {
- mRegistrations.removeAt(index);
- if (mRegistrations.isEmpty()) {
- onUnregister();
- }
+ mRegistrations.removeAt(index);
+ if (mRegistrations.isEmpty()) {
+ onUnregister();
}
}
-
- return registration;
}
/**
@@ -392,14 +415,14 @@
* registration accordingly.
*/
protected final void updateService() {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
if (mUpdateServiceBuffer.isBuffered()) {
mUpdateServiceBuffer.markUpdateServiceRequired();
return;
}
- ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
final int size = mRegistrations.size();
+ ArrayList<TRegistration> actives = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
@@ -413,17 +436,17 @@
mServiceRegistered = false;
unregisterWithService();
}
- return;
- }
-
- TMergedRegistration merged = mergeRegistrations(actives);
- if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
+ } else {
+ TMergedRegistration merged = mergeRegistrations(actives);
if (mServiceRegistered) {
- mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+ if (!Objects.equals(merged, mMerged)) {
+ mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+ mMerged = mServiceRegistered ? merged : null;
+ }
} else {
mServiceRegistered = registerWithService(merged, actives);
+ mMerged = mServiceRegistered ? merged : null;
}
- mMerged = mServiceRegistered ? merged : null;
}
}
}
@@ -437,7 +460,7 @@
* reinitialized.
*/
protected final void resetService() {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
if (mServiceRegistered) {
mMerged = null;
mServiceRegistered = false;
@@ -453,7 +476,31 @@
* buffering {@code updateService()} until after multiple adds/removes/updates occur.
*/
public UpdateServiceLock newUpdateServiceLock() {
- return new UpdateServiceLock(mUpdateServiceBuffer.acquire());
+ return new UpdateServiceLock(mUpdateServiceBuffer);
+ }
+
+ /**
+ * Evaluates the predicate on all registrations until the predicate returns true, at which point
+ * evaluation will cease. Returns true if the predicate ever returned true, and returns false
+ * otherwise.
+ */
+ protected final boolean findRegistration(Predicate<TRegistration> predicate) {
+ synchronized (mMultiplexerLock) {
+ // we only acquire a reentrancy guard in case of removal while iterating. this method
+ // does not directly affect active state or merged state, so there is no advantage to
+ // acquiring an update source buffer.
+ try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
+ TRegistration registration = mRegistrations.valueAt(i);
+ if (predicate.test(registration)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
/**
@@ -463,7 +510,7 @@
* the resulting changes.
*/
protected final void updateRegistrations(@NonNull Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -492,7 +539,7 @@
*/
protected final boolean updateRegistration(@NonNull Object key,
@NonNull Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -515,12 +562,8 @@
}
}
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private void onRegistrationActiveChanged(TRegistration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
-
boolean active = registration.isRegistered() && isActive(registration);
boolean changed = registration.setActive(active);
if (changed) {
@@ -547,7 +590,7 @@
*/
protected final void deliverToListeners(
@NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -571,7 +614,7 @@
* </pre>
*/
protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -584,6 +627,7 @@
}
}
+ @GuardedBy("mMultiplexerLock")
private void unregister(TRegistration registration) {
registration.unregisterInternal();
onRegistrationActiveChanged(registration);
@@ -593,7 +637,7 @@
* Dumps debug information.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
pw.print("service: ");
pw.print(getServiceState());
pw.println();
@@ -620,6 +664,7 @@
* May be overridden to provide additional details on service state when dumping the manager
* state. Invoked while holding the multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected String getServiceState() {
if (mServiceRegistered) {
if (mMerged != null) {
@@ -643,61 +688,63 @@
*/
private final class ReentrancyGuard implements AutoCloseable {
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private int mGuardCount;
- @GuardedBy("mRegistrations")
- private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
+
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private ArraySet<Entry<TKey, ListenerRegistration<?>>> mScheduledRemovals;
ReentrancyGuard() {
mGuardCount = 0;
mScheduledRemovals = null;
}
- @GuardedBy("mRegistrations")
boolean isReentrant() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
+ synchronized (mMultiplexerLock) {
+ return mGuardCount != 0;
}
- return mGuardCount != 0;
}
- @GuardedBy("mRegistrations")
- void markForRemoval(Object key, ListenerRegistration<?> registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
- Preconditions.checkState(isReentrant());
+ void markForRemoval(TKey key, ListenerRegistration<?> registration) {
+ synchronized (mMultiplexerLock) {
+ Preconditions.checkState(isReentrant());
- if (mScheduledRemovals == null) {
- mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+ if (mScheduledRemovals == null) {
+ mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+ }
+ mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
- mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
ReentrancyGuard acquire() {
- ++mGuardCount;
- return this;
+ synchronized (mMultiplexerLock) {
+ ++mGuardCount;
+ return this;
+ }
}
@Override
public void close() {
- ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
+ synchronized (mMultiplexerLock) {
+ Preconditions.checkState(mGuardCount > 0);
- Preconditions.checkState(mGuardCount > 0);
- if (--mGuardCount == 0) {
- scheduledRemovals = mScheduledRemovals;
- mScheduledRemovals = null;
- }
+ ArraySet<Entry<TKey, ListenerRegistration<?>>> scheduledRemovals = null;
- if (scheduledRemovals == null) {
- return;
- }
+ if (--mGuardCount == 0) {
+ scheduledRemovals = mScheduledRemovals;
+ mScheduledRemovals = null;
+ }
- try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
- final int size = scheduledRemovals.size();
- for (int i = 0; i < size; i++) {
- Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
- removeRegistration(entry.getKey(), entry.getValue());
+ if (scheduledRemovals == null) {
+ return;
+ }
+
+ try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
+ final int size = scheduledRemovals.size();
+ for (int i = 0; i < size; i++) {
+ Entry<TKey, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
+ removeRegistration(entry.getKey(), entry.getValue());
+ }
}
}
}
@@ -721,6 +768,7 @@
@GuardedBy("this")
private int mBufferCount;
+
@GuardedBy("this")
private boolean mUpdateServiceRequired;
@@ -765,18 +813,18 @@
* {@link #close()}ed. This can be used to save work by acquiring the lock before multiple calls
* to updateService() are expected, and closing the lock after.
*/
- public final class UpdateServiceLock implements AutoCloseable {
+ public static final class UpdateServiceLock implements AutoCloseable {
- private @Nullable UpdateServiceBuffer mUpdateServiceBuffer;
+ @Nullable private ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer mUpdateServiceBuffer;
- UpdateServiceLock(UpdateServiceBuffer updateServiceBuffer) {
- mUpdateServiceBuffer = updateServiceBuffer;
+ UpdateServiceLock(ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer updateServiceBuffer) {
+ mUpdateServiceBuffer = updateServiceBuffer.acquire();
}
@Override
public void close() {
if (mUpdateServiceBuffer != null) {
- UpdateServiceBuffer buffer = mUpdateServiceBuffer;
+ ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer buffer = mUpdateServiceBuffer;
mUpdateServiceBuffer = null;
buffer.close();
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index 711dde8..fcb2a9b 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -35,7 +35,7 @@
private boolean mActive;
- private volatile @Nullable TListener mListener;
+ @Nullable private volatile TListener mListener;
protected ListenerRegistration(Executor executor, TListener listener) {
mExecutor = Objects.requireNonNull(executor);
@@ -43,6 +43,13 @@
mListener = Objects.requireNonNull(listener);
}
+ /**
+ * Returns a tag to use for logging. Should be overridden by subclasses.
+ */
+ protected String getTag() {
+ return "ListenerRegistration";
+ }
+
protected final Executor getExecutor() {
return mExecutor;
}
@@ -50,26 +57,36 @@
/**
* May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
* owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
*/
protected void onRegister(Object key) {}
/**
* May be overridden by subclasses. Invoked when unregistration occurs. Invoked while holding
* the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+ * in the overridden method).
*/
protected void onUnregister() {}
/**
- * May be overridden by subclasses. Invoked when this registration becomes active. If this
- * returns a non-null operation, that operation will be invoked for the listener. Invoked
- * while holding the owning multiplexer's internal lock.
+ * May be overridden by subclasses. Invoked when this registration becomes active. Invoked while
+ * holding the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
*/
protected void onActive() {}
/**
- * May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
- * a non-null operation, that operation will be invoked for the listener. Invoked while holding
- * the owning multiplexer's internal lock.
+ * May be overridden by subclasses. Invoked when registration becomes inactive. Invoked while
+ * holding the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+ * in the overridden method).
*/
protected void onInactive() {}
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index 240ac01..c976601 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -16,63 +16,47 @@
package com.android.server.location.listeners;
-import android.annotation.Nullable;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
import android.app.PendingIntent;
-import android.location.util.identity.CallerIdentity;
import android.util.Log;
/**
* A registration that works with PendingIntent keys, and registers a CancelListener to
- * automatically remove the registration if the PendingIntent is canceled. The key for this
- * registration must either be a {@link PendingIntent} or a {@link PendingIntentKey}.
+ * automatically remove the registration if the PendingIntent is canceled.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
+public abstract class PendingIntentListenerRegistration<TKey, TListener> extends
+ RemovableListenerRegistration<TKey, TListener> implements PendingIntent.CancelListener {
- /**
- * Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
- */
- public interface PendingIntentKey {
- /**
- * Returns the pending intent associated with this key.
- */
- PendingIntent getPendingIntent();
+ protected PendingIntentListenerRegistration(TListener listener) {
+ super(DIRECT_EXECUTOR, listener);
}
- protected PendingIntentListenerRegistration(@Nullable TRequest request,
- CallerIdentity callerIdentity, TListener listener) {
- super(request, callerIdentity, listener);
+ protected abstract PendingIntent getPendingIntentFromKey(TKey key);
+
+ @Override
+ protected void onRegister() {
+ super.onRegister();
+
+ if (!getPendingIntentFromKey(getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
+ remove();
+ }
}
@Override
- protected final void onRemovableListenerRegister() {
- getPendingIntentFromKey(getKey()).registerCancelListener(this);
- onPendingIntentListenerRegister();
+ protected void onUnregister() {
+ getPendingIntentFromKey(getKey()).removeCancelListener(this);
+
+ super.onUnregister();
}
@Override
- protected final void onRemovableListenerUnregister() {
- onPendingIntentListenerUnregister();
- getPendingIntentFromKey(getKey()).unregisterCancelListener(this);
- }
-
- /**
- * May be overridden in place of {@link #onRemovableListenerRegister()}.
- */
- protected void onPendingIntentListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onRemovableListenerUnregister()}.
- */
- protected void onPendingIntentListenerUnregister() {}
-
- @Override
protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof PendingIntent.CanceledException) {
- Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -81,21 +65,10 @@
@Override
public void onCanceled(PendingIntent intent) {
- if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
- Log.d(getOwner().getTag(),
- "pending intent registration " + getIdentity() + " canceled");
+ if (Log.isLoggable(getTag(), Log.DEBUG)) {
+ Log.d(getTag(), "pending intent registration " + this + " canceled");
}
remove();
}
-
- private PendingIntent getPendingIntentFromKey(Object key) {
- if (key instanceof PendingIntent) {
- return (PendingIntent) key;
- } else if (key instanceof PendingIntentKey) {
- return ((PendingIntentKey) key).getPendingIntent();
- } else {
- throw new IllegalArgumentException("key must be PendingIntent or PendingIntentKey");
- }
- }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
deleted file mode 100644
index 4eca577..0000000
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.listeners;
-
-
-import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Process;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.FgThread;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration representing a remote (possibly from a different process) listener.
- * Listeners from a different process will be run on a direct executor, since the x-process listener
- * invocation should already be asynchronous. Listeners from the same process will be run on a
- * normal executor, since in-process listener invocation may be synchronous.
- *
- * @param <TRequest> request type
- * @param <TListener> listener type
- */
-public abstract class RemoteListenerRegistration<TRequest, TListener> extends
- RemovableListenerRegistration<TRequest, TListener> {
-
- @VisibleForTesting
- public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
-
- private static Executor chooseExecutor(CallerIdentity identity) {
- // if a client is in the same process as us, binder calls will execute synchronously and
- // we shouldn't run callbacks directly since they might be run under lock and deadlock
- if (identity.getPid() == Process.myPid()) {
- // there's a slight loophole here for pending intents - pending intent callbacks can
- // always be run on the direct executor since they're always asynchronous, but honestly
- // you shouldn't be using pending intent callbacks within the same process anyways
- return IN_PROCESS_EXECUTOR;
- } else {
- return DIRECT_EXECUTOR;
- }
- }
-
- private final CallerIdentity mIdentity;
-
- protected RemoteListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
- TListener listener) {
- super(chooseExecutor(identity), request, listener);
- mIdentity = Objects.requireNonNull(identity);
- }
-
- /**
- * Returns the listener identity.
- */
- public final CallerIdentity getIdentity() {
- return mIdentity;
- }
-}
-
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 618ff24..3c302fb 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -20,22 +20,23 @@
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* A listener registration that stores its own key, and thus can remove itself. By default it will
* remove itself if any checked exception occurs on listener execution.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class RemovableListenerRegistration<TRequest, TListener> extends
- RequestListenerRegistration<TRequest, TListener> {
+public abstract class RemovableListenerRegistration<TKey, TListener> extends
+ ListenerRegistration<TListener> {
- private volatile @Nullable Object mKey;
+ @Nullable private volatile TKey mKey;
+ private final AtomicBoolean mRemoved = new AtomicBoolean(false);
- protected RemovableListenerRegistration(Executor executor, @Nullable TRequest request,
- TListener listener) {
- super(executor, request, listener);
+ protected RemovableListenerRegistration(Executor executor, TListener listener) {
+ super(executor, listener);
}
/**
@@ -43,46 +44,76 @@
* with. Often this is easiest to accomplish by defining registration subclasses as non-static
* inner classes of the multiplexer they are to be used with.
*/
- protected abstract ListenerMultiplexer<?, ? super TListener, ?, ?> getOwner();
+ protected abstract ListenerMultiplexer<TKey, ? super TListener, ?, ?> getOwner();
/**
* Returns the key associated with this registration. May not be invoked before
* {@link #onRegister(Object)} or after {@link #onUnregister()}.
*/
- protected final Object getKey() {
+ protected final TKey getKey() {
return Objects.requireNonNull(mKey);
}
/**
- * Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or
- * after {@link #onUnregister()}. It is safe to invoke this from within either function.
+ * Convenience method equivalent to invoking {@link #remove(boolean)} with the
+ * {@code immediately} parameter set to true.
*/
public final void remove() {
- Object key = mKey;
- if (key != null) {
- getOwner().removeRegistration(key, this);
+ remove(true);
+ }
+
+ /**
+ * Removes this registration. If the {@code immediately} parameter is true, all pending listener
+ * invocations will fail. If the {@code immediately} parameter is false, listener invocations
+ * that were scheduled before remove was invoked (including invocations scheduled within {@link
+ * #onRemove(boolean)}) will continue, but any listener invocations scheduled after remove was
+ * invoked will fail.
+ *
+ * <p>Only the first call to this method will ever go through (and so {@link #onRemove(boolean)}
+ * will only ever be invoked once).
+ *
+ * <p>Does nothing if invoked before {@link #onRegister()} or after {@link #onUnregister()}.
+ */
+ public final void remove(boolean immediately) {
+ TKey key = mKey;
+ if (key != null && !mRemoved.getAndSet(true)) {
+ onRemove(immediately);
+ if (immediately) {
+ getOwner().removeRegistration(key, this);
+ } else {
+ executeOperation(listener -> getOwner().removeRegistration(key, this));
+ }
}
}
+ /**
+ * Invoked just before this registration is removed due to {@link #remove(boolean)}, on the same
+ * thread as the responsible {@link #remove(boolean)} call.
+ *
+ * <p>This method will only ever be invoked once, no matter how many calls to {@link
+ * #remove(boolean)} are made, as any registration can only be removed once.
+ */
+ protected void onRemove(boolean immediately) {}
+
@Override
protected final void onRegister(Object key) {
- mKey = Objects.requireNonNull(key);
- onRemovableListenerRegister();
+ super.onRegister(key);
+ mKey = (TKey) Objects.requireNonNull(key);
+ onRegister();
}
+ /**
+ * May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
+ * owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
+ */
+ protected void onRegister() {}
+
@Override
- protected final void onUnregister() {
- onRemovableListenerUnregister();
+ protected void onUnregister() {
mKey = null;
+ super.onUnregister();
}
-
- /**
- * May be overridden in place of {@link #onRegister(Object)}.
- */
- protected void onRemovableListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onUnregister()}.
- */
- protected void onRemovableListenerUnregister() {}
}
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
deleted file mode 100644
index 0c2fc91..0000000
--- a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location.listeners;
-
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration object which includes an associated request.
- *
- * @param <TRequest> request type
- * @param <TListener> listener type
- */
-public class RequestListenerRegistration<TRequest, TListener> extends
- ListenerRegistration<TListener> {
-
- private final TRequest mRequest;
-
- protected RequestListenerRegistration(Executor executor, TRequest request,
- TListener listener) {
- super(executor, listener);
- mRequest = request;
- }
-
- /**
- * Returns the request associated with this listener, or null if one wasn't supplied.
- */
- public TRequest getRequest() {
- return mRequest;
- }
-
- @Override
- public String toString() {
- if (mRequest == null) {
- return "[]";
- } else {
- return mRequest.toString();
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 549fd49..a69a079 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -67,7 +67,6 @@
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -115,7 +114,7 @@
import com.android.server.location.injector.UserInfoHelper;
import com.android.server.location.injector.UserInfoHelper.UserListener;
import com.android.server.location.listeners.ListenerMultiplexer;
-import com.android.server.location.listeners.RemoteListenerRegistration;
+import com.android.server.location.listeners.RemovableListenerRegistration;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
@@ -124,8 +123,10 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
/**
@@ -354,44 +355,60 @@
public void deliverOnFlushComplete(int requestCode) {}
}
- protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
- LocationTransport> {
+ protected abstract class Registration extends RemovableListenerRegistration<Object,
+ LocationTransport> {
+ private final LocationRequest mBaseRequest;
+ private final CallerIdentity mIdentity;
private final @PermissionLevel int mPermissionLevel;
// we cache these values because checking/calculating on the fly is more expensive
+ @GuardedBy("mMultiplexerLock")
private boolean mPermitted;
+ @GuardedBy("mMultiplexerLock")
private boolean mForeground;
+ @GuardedBy("mMultiplexerLock")
private LocationRequest mProviderLocationRequest;
+ @GuardedBy("mMultiplexerLock")
private boolean mIsUsingHighPower;
- private @Nullable Location mLastLocation = null;
+ @Nullable private Location mLastLocation = null;
- protected Registration(LocationRequest request, CallerIdentity identity,
+ protected Registration(LocationRequest request, CallerIdentity identity, Executor executor,
LocationTransport transport, @PermissionLevel int permissionLevel) {
- super(Objects.requireNonNull(request), identity, transport);
+ super(executor, transport);
Preconditions.checkArgument(identity.getListenerId() != null);
Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
Preconditions.checkArgument(!request.getWorkSource().isEmpty());
+ mBaseRequest = Objects.requireNonNull(request);
+ mIdentity = Objects.requireNonNull(identity);
mPermissionLevel = permissionLevel;
mProviderLocationRequest = request;
}
- @GuardedBy("mLock")
- @Override
- protected final void onRemovableListenerRegister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
+ public final CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ public final LocationRequest getRequest() {
+ synchronized (mMultiplexerLock) {
+ return mProviderLocationRequest;
}
+ }
+
+ @GuardedBy("mMultiplexerLock")
+ @Override
+ protected void onRegister() {
+ super.onRegister();
if (D) {
Log.d(TAG, mName + " provider added registration from " + getIdentity() + " -> "
+ getRequest());
}
- EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+ EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), mBaseRequest);
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -400,110 +417,72 @@
mProviderLocationRequest = calculateProviderLocationRequest();
mIsUsingHighPower = isUsingHighPower();
- onProviderListenerRegister();
-
if (mForeground) {
EVENT_LOG.logProviderClientForeground(mName, getIdentity());
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onRemovableListenerUnregister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- onProviderListenerUnregister();
-
+ protected void onUnregister() {
EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
}
+
+ super.onUnregister();
}
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerRegister() {}
-
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerUnregister() {}
-
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onActive() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
+ protected void onActive() {
EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, getIdentity());
}
onHighPowerUsageChanged();
-
- onProviderListenerActive();
}
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onInactive() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
+ protected void onInactive() {
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, getIdentity());
}
- onProviderListenerInactive();
-
EVENT_LOG.logProviderClientInactive(mName, getIdentity());
}
- /**
- * Subclasses may override this instead of {@link #onActive()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerActive() {}
-
- /**
- * Subclasses may override this instead of {@link #onInactive()} ()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerInactive() {}
-
- @Override
- public final LocationRequest getRequest() {
- return mProviderLocationRequest;
- }
-
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
final void setLastDeliveredLocation(@Nullable Location location) {
mLastLocation = location;
}
- @GuardedBy("mLock")
public final Location getLastDeliveredLocation() {
- return mLastLocation;
+ synchronized (mMultiplexerLock) {
+ return mLastLocation;
+ }
}
public @PermissionLevel int getPermissionLevel() {
- return mPermissionLevel;
+ synchronized (mMultiplexerLock) {
+ return mPermissionLevel;
+ }
}
public final boolean isForeground() {
- return mForeground;
+ synchronized (mMultiplexerLock) {
+ return mForeground;
+ }
}
public final boolean isPermitted() {
- return mPermitted;
+ synchronized (mMultiplexerLock) {
+ return mPermitted;
+ }
}
public final void flush(int requestCode) {
@@ -519,13 +498,14 @@
return LocationProviderManager.this;
}
- @GuardedBy("mLock")
final boolean onProviderPropertiesChanged() {
- onHighPowerUsageChanged();
- return false;
+ synchronized (mMultiplexerLock) {
+ onHighPowerUsageChanged();
+ return false;
+ }
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onHighPowerUsageChanged() {
boolean isUsingHighPower = isUsingHighPower();
if (isUsingHighPower != mIsUsingHighPower) {
@@ -541,12 +521,7 @@
}
}
- @GuardedBy("mLock")
private boolean isUsingHighPower() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
ProviderProperties properties = getProperties();
if (properties == null) {
return false;
@@ -557,30 +532,28 @@
&& properties.getPowerUsage() == ProviderProperties.POWER_USAGE_HIGH;
}
- @GuardedBy("mLock")
final boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
- return onLocationPermissionsChanged();
- }
+ synchronized (mMultiplexerLock) {
+ if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ return onLocationPermissionsChanged();
+ }
- return false;
+ return false;
+ }
}
- @GuardedBy("mLock")
final boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
- return onLocationPermissionsChanged();
- }
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUid() == uid) {
+ return onLocationPermissionsChanged();
+ }
- return false;
+ return false;
+ }
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private boolean onLocationPermissionsChanged() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
getIdentity());
if (permitted != mPermitted) {
@@ -603,82 +576,73 @@
return false;
}
- @GuardedBy("mLock")
final boolean onAdasGnssLocationEnabledChanged(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- if (getIdentity().getUserId() == userId) {
- return onProviderLocationRequestChanged();
- }
-
- return false;
- }
-
- @GuardedBy("mLock")
- final boolean onForegroundChanged(int uid, boolean foreground) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- if (getIdentity().getUid() == uid && foreground != mForeground) {
- if (D) {
- Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUserId() == userId) {
+ return onProviderLocationRequestChanged();
}
- mForeground = foreground;
-
- if (mForeground) {
- EVENT_LOG.logProviderClientForeground(mName, getIdentity());
- } else {
- EVENT_LOG.logProviderClientBackground(mName, getIdentity());
- }
-
- // note that onProviderLocationRequestChanged() is always called
- return onProviderLocationRequestChanged()
- || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
- == LOCATION_MODE_FOREGROUND_ONLY;
- }
-
- return false;
- }
-
- @GuardedBy("mLock")
- final boolean onProviderLocationRequestChanged() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- LocationRequest newRequest = calculateProviderLocationRequest();
- if (mProviderLocationRequest.equals(newRequest)) {
return false;
}
-
- LocationRequest oldRequest = mProviderLocationRequest;
- mProviderLocationRequest = newRequest;
- onHighPowerUsageChanged();
- updateService();
-
- // if bypass state has changed then the active state may have changed
- return oldRequest.isBypass() != newRequest.isBypass();
}
+ final boolean onForegroundChanged(int uid, boolean foreground) {
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUid() == uid && foreground != mForeground) {
+ if (D) {
+ Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+ }
+
+ mForeground = foreground;
+
+ if (mForeground) {
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
+ } else {
+ EVENT_LOG.logProviderClientBackground(mName, getIdentity());
+ }
+
+ // note that onProviderLocationRequestChanged() is always called
+ return onProviderLocationRequestChanged()
+ || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
+ == LOCATION_MODE_FOREGROUND_ONLY;
+ }
+
+ return false;
+ }
+ }
+
+ final boolean onProviderLocationRequestChanged() {
+ synchronized (mMultiplexerLock) {
+ LocationRequest newRequest = calculateProviderLocationRequest();
+ if (mProviderLocationRequest.equals(newRequest)) {
+ return false;
+ }
+
+ LocationRequest oldRequest = mProviderLocationRequest;
+ mProviderLocationRequest = newRequest;
+ onHighPowerUsageChanged();
+ updateService();
+
+ // if bypass state has changed then the active state may have changed
+ return oldRequest.isBypass() != newRequest.isBypass();
+ }
+ }
+
+ @GuardedBy("mMultiplexerLock")
private LocationRequest calculateProviderLocationRequest() {
- LocationRequest baseRequest = super.getRequest();
- LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
+ LocationRequest.Builder builder = new LocationRequest.Builder(mBaseRequest);
if (mPermissionLevel < PERMISSION_FINE) {
builder.setQuality(LocationRequest.QUALITY_LOW_POWER);
- if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ if (mBaseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
- if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ if (mBaseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setMinUpdateIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
}
- boolean locationSettingsIgnored = baseRequest.isLocationSettingsIgnored();
+ boolean locationSettingsIgnored = mBaseRequest.isLocationSettingsIgnored();
if (locationSettingsIgnored) {
// if we are not currently allowed use location settings ignored, disable it
if (!mSettingsHelper.getIgnoreSettingsAllowlist().contains(
@@ -690,7 +654,7 @@
builder.setLocationSettingsIgnored(locationSettingsIgnored);
}
- boolean adasGnssBypass = baseRequest.isAdasGnssBypass();
+ boolean adasGnssBypass = mBaseRequest.isAdasGnssBypass();
if (adasGnssBypass) {
// if we are not currently allowed use adas gnss bypass, disable it
if (!GPS_PROVIDER.equals(mName)) {
@@ -710,7 +674,7 @@
if (!locationSettingsIgnored && !isThrottlingExempt()) {
// throttle in the background
if (!mForeground) {
- builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
+ builder.setIntervalMillis(max(mBaseRequest.getIntervalMillis(),
mSettingsHelper.getBackgroundThrottleIntervalMs()));
}
}
@@ -727,8 +691,7 @@
return mLocationManagerInternal.isProvider(null, getIdentity());
}
- @GuardedBy("mLock")
- abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ @Nullable abstract ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult);
@Override
@@ -769,13 +732,19 @@
final ExternalWakeLockReleaser mWakeLockReleaser;
private volatile ProviderTransport mProviderTransport;
+
+ @GuardedBy("mMultiplexerLock")
private int mNumLocationsDelivered = 0;
+ @GuardedBy("mMultiplexerLock")
private long mExpirationRealtimeMs = Long.MAX_VALUE;
protected <TTransport extends LocationTransport & ProviderTransport> LocationRegistration(
- LocationRequest request, CallerIdentity identity, TTransport transport,
+ LocationRequest request,
+ CallerIdentity identity,
+ Executor executor,
+ TTransport transport,
@PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity, executor, transport, permissionLevel);
mProviderTransport = transport;
mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
@@ -789,9 +758,13 @@
mProviderTransport = null;
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
long registerTimeMs = SystemClock.elapsedRealtime();
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
@@ -810,8 +783,6 @@
// start listening for provider enabled/disabled events
addEnabledListener(this);
- onLocationListenerRegister();
-
// if the provider is currently disabled, let the client know immediately
int userId = getIdentity().getUserId();
if (!isEnabled(userId)) {
@@ -819,9 +790,11 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerUnregister() {
+ protected void onUnregister() {
// stop listening for provider enabled/disabled events
removeEnabledListener(this);
@@ -830,24 +803,16 @@
mAlarmHelper.cancel(this);
}
- onLocationListenerUnregister();
+ super.onUnregister();
}
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
- */
- @GuardedBy("mLock")
- protected void onLocationListenerRegister() {}
-
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
- */
- @GuardedBy("mLock")
- protected void onLocationListenerUnregister() {}
-
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerActive() {
+ protected void onActive() {
+ super.onActive();
+
// a new registration may not get a location immediately, the provider request may be
// delayed. therefore we deliver a historical location if available. since delivering an
// older location could be considered a breaking change for some applications, we only
@@ -883,21 +848,17 @@
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
remove();
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1017,9 +978,7 @@
+ " finished after " + mNumLocationsDelivered + " updates");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
}
@@ -1049,12 +1008,18 @@
LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity,
+ identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, transport,
+ permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -1062,10 +1027,22 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerUnregister() {
- ((IBinder) getKey()).unlinkToDeath(this, 0);
+ protected void onUnregister() {
+ try {
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been
+ // thrown prior to registration completing, and that exception is currently
+ // unwinding the call stack and causing this cleanup. since that exception should
+ // crash us anyways, drop this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
@Override
@@ -1083,9 +1060,7 @@
private void onTransportFailure(Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1098,9 +1073,7 @@
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
@@ -1115,21 +1088,27 @@
LocationPendingIntentRegistration(LocationRequest request,
CallerIdentity identity, LocationPendingIntentTransport transport,
@PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity, DIRECT_EXECUTOR, transport, permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
if (!((PendingIntent) getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
remove();
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerUnregister() {
+ protected void onUnregister() {
((PendingIntent) getKey()).removeCancelListener(this);
+ super.onUnregister();
}
@Override
@@ -1147,9 +1126,7 @@
private void onTransportFailure(Exception e) {
if (e instanceof PendingIntent.CanceledException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1161,25 +1138,32 @@
Log.d(TAG, mName + " provider registration " + getIdentity() + " canceled");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
protected final class GetCurrentLocationListenerRegistration extends Registration implements
IBinder.DeathRecipient, OnAlarmListener {
+ @GuardedBy("mMultiplexerLock")
private long mExpirationRealtimeMs = Long.MAX_VALUE;
GetCurrentLocationListenerRegistration(LocationRequest request,
CallerIdentity identity, LocationTransport transport, int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request,
+ identity,
+ identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR,
+ transport,
+ permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -1202,20 +1186,36 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerUnregister() {
+ protected void onUnregister() {
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
mAlarmHelper.cancel(this);
}
- ((IBinder) getKey()).unlinkToDeath(this, 0);
+ try {
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been
+ // thrown prior to registration completing, and that exception is currently
+ // unwinding the call stack and causing this cleanup. since that exception should
+ // crash us anyways, drop this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerActive() {
+ protected void onActive() {
+ super.onActive();
+
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
@@ -1226,17 +1226,19 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerInactive() {
+ protected void onInactive() {
// if we go inactive for any reason, fail immediately
executeOperation(acceptLocationChange(null));
+ super.onInactive();
}
+ @GuardedBy("mMultiplexerLock")
void deliverNull() {
- synchronized (mLock) {
- executeOperation(acceptLocationChange(null));
- }
+ executeOperation(acceptLocationChange(null));
}
@Override
@@ -1246,21 +1248,17 @@
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
executeOperation(acceptLocationChange(null));
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
@Nullable LocationResult fineLocationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1311,9 +1309,7 @@
// on failure we're automatically removed anyways, no need to attempt removal
// again
if (success) {
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
};
@@ -1324,9 +1320,7 @@
Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1339,9 +1333,7 @@
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
@@ -1350,23 +1342,21 @@
}
}
- protected final Object mLock = new Object();
-
protected final String mName;
- private final @Nullable PassiveLocationProviderManager mPassiveManager;
+ @Nullable private final PassiveLocationProviderManager mPassiveManager;
protected final Context mContext;
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private @State int mState;
// maps of user id to value
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final SparseBooleanArray mEnabled; // null or not present means unknown
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final SparseArray<LastLocation> mLastLocations;
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final ArrayList<ProviderEnabledListener> mEnabledListeners;
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
@@ -1418,14 +1408,14 @@
private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
this::onScreenInteractiveChanged;
- // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+ // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
- @GuardedBy("mLock")
- private @Nullable OnAlarmListener mDelayedRegister;
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private OnAlarmListener mDelayedRegister;
- @GuardedBy("mLock")
- private @Nullable StateChangedListener mStateChangedListener;
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private StateChangedListener mStateChangedListener;
public LocationProviderManager(Context context, Injector injector,
String name, @Nullable PassiveLocationProviderManager passiveManager) {
@@ -1453,19 +1443,14 @@
mLocationUsageLogger = injector.getLocationUsageLogger();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
- mProvider = new MockableLocationProvider(mLock);
+ mProvider = new MockableLocationProvider(mMultiplexerLock);
// set listener last, since this lets our reference escape
mProvider.getController().setListener(this);
}
- @Override
- public String getTag() {
- return TAG;
- }
-
public void startManager(@Nullable StateChangedListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState == STATE_STOPPED);
mState = STATE_STARTED;
mStateChangedListener = listener;
@@ -1485,7 +1470,7 @@
}
public void stopManager() {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState == STATE_STARTED);
mState = STATE_STOPPING;
@@ -1522,11 +1507,11 @@
return mProvider.getState();
}
- public @Nullable CallerIdentity getProviderIdentity() {
+ @Nullable public CallerIdentity getProviderIdentity() {
return mProvider.getState().identity;
}
- public @Nullable ProviderProperties getProperties() {
+ @Nullable public ProviderProperties getProperties() {
return mProvider.getState().properties;
}
@@ -1543,7 +1528,7 @@
Preconditions.checkArgument(userId >= 0);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
int index = mEnabled.indexOfKey(userId);
if (index < 0) {
// this generally shouldn't occur, but might be possible due to race conditions
@@ -1558,14 +1543,14 @@
}
public void addEnabledListener(ProviderEnabledListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.add(listener);
}
}
public void removeEnabledListener(ProviderEnabledListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.remove(listener);
}
@@ -1582,7 +1567,7 @@
}
public void setRealProvider(@Nullable AbstractLocationProvider provider) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
@@ -1595,7 +1580,7 @@
}
public void setMockProvider(@Nullable MockLocationProvider provider) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
EVENT_LOG.logProviderMocked(mName, provider != null);
@@ -1622,7 +1607,7 @@
}
public void setMockProviderAllowed(boolean enabled) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
@@ -1637,7 +1622,7 @@
}
public void setMockProviderLocation(Location location) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
@@ -1659,7 +1644,7 @@
}
}
- public @Nullable Location getLastLocation(LastLocationRequest request,
+ @Nullable public Location getLastLocation(LastLocationRequest request,
CallerIdentity identity, @PermissionLevel int permissionLevel) {
request = calculateLastLocationRequest(request, identity);
@@ -1732,7 +1717,7 @@
* location, even if the permissionLevel is coarse. You are responsible for coarsening the
* location if necessary.
*/
- public @Nullable Location getLastLocationUnsafe(int userId,
+ @Nullable public Location getLastLocationUnsafe(int userId,
@PermissionLevel int permissionLevel, boolean isBypass,
long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
@@ -1756,7 +1741,7 @@
Preconditions.checkArgument(userId >= 0);
Location location;
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
@@ -1778,7 +1763,7 @@
}
public void injectLastLocation(Location location, int userId) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
setLastLocation(location, userId);
@@ -1800,7 +1785,7 @@
Preconditions.checkArgument(userId >= 0);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
lastLocation = new LastLocation();
@@ -1814,7 +1799,7 @@
}
}
- public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request,
+ @Nullable public ICancellationSignal getCurrentLocation(LocationRequest request,
CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
if (request.getDurationMillis() > MAX_GET_CURRENT_LOCATION_TIMEOUT_MS) {
request = new LocationRequest.Builder(request)
@@ -1829,7 +1814,7 @@
new GetCurrentLocationTransport(callback),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1849,9 +1834,7 @@
() -> {
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- removeRegistration(callback.asBinder(), registration);
- }
+ removeRegistration(callback.asBinder(), registration);
} catch (RuntimeException e) {
// since this is within a oneway binder transaction there is nowhere
// for exceptions to go - move onto another thread to crash system
@@ -1885,7 +1868,7 @@
new LocationListenerTransport(listener),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1904,7 +1887,7 @@
new LocationPendingIntentTransport(mContext, pendingIntent),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1916,42 +1899,38 @@
}
public void flush(ILocationListener listener, int requestCode) {
- synchronized (mLock) {
- final long identity = Binder.clearCallingIdentity();
- try {
- boolean flushed = updateRegistration(listener.asBinder(), registration -> {
- registration.flush(requestCode);
- return false;
- });
- if (!flushed) {
- throw new IllegalArgumentException("unregistered listener cannot be flushed");
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ boolean flushed = updateRegistration(listener.asBinder(), registration -> {
+ registration.flush(requestCode);
+ return false;
+ });
+ if (!flushed) {
+ throw new IllegalArgumentException("unregistered listener cannot be flushed");
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
public void flush(PendingIntent pendingIntent, int requestCode) {
- synchronized (mLock) {
- final long identity = Binder.clearCallingIdentity();
- try {
- boolean flushed = updateRegistration(pendingIntent, registration -> {
- registration.flush(requestCode);
- return false;
- });
- if (!flushed) {
- throw new IllegalArgumentException(
- "unregistered pending intent cannot be flushed");
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ boolean flushed = updateRegistration(pendingIntent, registration -> {
+ registration.flush(requestCode);
+ return false;
+ });
+ if (!flushed) {
+ throw new IllegalArgumentException(
+ "unregistered pending intent cannot be flushed");
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
public void unregisterLocationRequest(ILocationListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1963,7 +1942,7 @@
}
public void unregisterLocationRequest(PendingIntent pendingIntent) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1974,13 +1953,9 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -1997,13 +1972,9 @@
mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onUnregister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mSettingsHelper.removeOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -2019,13 +1990,9 @@
mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegistrationAdded(Object key, Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2038,23 +2005,21 @@
null, registration.isForeground());
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onRegistrationReplaced(Object key, Registration oldRegistration,
- Registration newRegistration) {
+ protected void onRegistrationReplaced(Object oldKey, Registration oldRegistration,
+ Object newKey, Registration newRegistration) {
// by saving the last delivered location state we are able to potentially delay the
// resulting provider request longer and save additional power
newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation());
- super.onRegistrationReplaced(key, oldRegistration, newRegistration);
+ super.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegistrationRemoved(Object key, Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2067,21 +2032,17 @@
null, registration.isForeground());
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean registerWithService(ProviderRequest request,
Collection<Registration> registrations) {
return reregisterWithService(ProviderRequest.EMPTY_REQUEST, request, registrations);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
ProviderRequest newRequest, Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
@@ -2117,7 +2078,7 @@
mDelayedRegister = new OnAlarmListener() {
@Override
public void onAlarm() {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mDelayedRegister == this) {
mDelayedRegister = null;
setProviderRequest(newRequest);
@@ -2135,17 +2096,13 @@
return true;
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void unregisterWithService() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
setProviderRequest(ProviderRequest.EMPTY_REQUEST);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
void setProviderRequest(ProviderRequest request) {
if (mDelayedRegister != null) {
mAlarmHelper.cancel(mDelayedRegister);
@@ -2166,13 +2123,9 @@
});
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean isActive(Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (!registration.isPermitted()) {
return false;
}
@@ -2236,13 +2189,9 @@
return true;
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
long intervalMs = ProviderRequest.INTERVAL_DISABLED;
int quality = LocationRequest.QUALITY_LOW_POWER;
long maxUpdateDelayMs = Long.MAX_VALUE;
@@ -2307,7 +2256,7 @@
.build();
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
protected long calculateRequestDelayMillis(long newIntervalMs,
Collection<Registration> registrations) {
// calculate the minimum delay across all registrations, ensuring that it is not more than
@@ -2349,7 +2298,7 @@
}
private void onUserChanged(int userId, int change) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mState == STATE_STOPPED) {
return;
}
@@ -2372,15 +2321,13 @@
private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) {
- synchronized (mLock) {
- updateRegistrations(
- registration -> registration.onAdasGnssLocationEnabledChanged(userId));
- }
+ updateRegistrations(
+ registration -> registration.onAdasGnssLocationEnabledChanged(userId));
}
}
private void onLocationEnabledChanged(int userId) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mState == STATE_STOPPED) {
return;
}
@@ -2390,88 +2337,64 @@
}
private void onScreenInteractiveChanged(boolean screenInteractive) {
- synchronized (mLock) {
- switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
- case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
- if (!GPS_PROVIDER.equals(mName)) {
- break;
- }
- // fall through
- case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
- // fall through
- case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
- updateRegistrations(registration -> true);
+ switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+ case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+ if (!GPS_PROVIDER.equals(mName)) {
break;
- default:
- break;
- }
+ }
+ // fall through
+ case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+ // fall through
+ case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+ updateRegistrations(registration -> true);
+ break;
+ default:
+ break;
}
}
private void onBackgroundThrottlePackageWhitelistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onBackgroundThrottleIntervalChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode) {
- synchronized (mLock) {
- // this is rare, just assume everything has changed to keep it simple
- updateRegistrations(registration -> true);
- }
+ // this is rare, just assume everything has changed to keep it simple
+ updateRegistrations(registration -> true);
}
private void onAppForegroundChanged(int uid, boolean foreground) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
- }
+ updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
}
private void onAdasAllowlistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onIgnoreSettingsWhitelistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onLocationPackageBlacklistChanged(int userId) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
- }
+ updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
private void onLocationPermissionsChanged(@Nullable String packageName) {
- synchronized (mLock) {
- updateRegistrations(
- registration -> registration.onLocationPermissionsChanged(packageName));
- }
+ updateRegistrations(
+ registration -> registration.onLocationPermissionsChanged(packageName));
}
private void onLocationPermissionsChanged(int uid) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
- }
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
public void onStateChanged(
AbstractLocationProvider.State oldState, AbstractLocationProvider.State newState) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (oldState.allowed != newState.allowed) {
onEnabledChanged(UserHandle.USER_ALL);
}
@@ -2487,13 +2410,9 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
public void onReportLocation(LocationResult locationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
LocationResult filtered;
if (mPassiveManager != null) {
filtered = locationResult.filter(location -> {
@@ -2549,12 +2468,8 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onUserStarted(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
return;
}
@@ -2572,12 +2487,8 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onUserStopped(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
return;
}
@@ -2592,12 +2503,8 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onEnabledChanged(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
// used during initialization - ignore since many lower level operations (checking
// settings for instance) do not support the null user
@@ -2697,7 +2604,7 @@
}
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
ipw.print(mName);
ipw.print(" provider");
if (mProvider.isMock()) {
@@ -2738,10 +2645,10 @@
private static class LastLocation {
- private @Nullable Location mFineLocation;
- private @Nullable Location mCoarseLocation;
- private @Nullable Location mFineBypassLocation;
- private @Nullable Location mCoarseBypassLocation;
+ @Nullable private Location mFineLocation;
+ @Nullable private Location mCoarseLocation;
+ @Nullable private Location mFineBypassLocation;
+ @Nullable private Location mCoarseBypassLocation;
LastLocation() {}
@@ -2765,7 +2672,7 @@
mCoarseLocation = null;
}
- public @Nullable Location get(@PermissionLevel int permissionLevel,
+ @Nullable public Location get(@PermissionLevel int permissionLevel,
boolean isBypass) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2862,7 +2769,7 @@
private static class GatedCallback implements Runnable {
@GuardedBy("this")
- private @Nullable Runnable mCallback;
+ @Nullable private Runnable mCallback;
@GuardedBy("this")
private boolean mGate;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index b35af4f..0cb4f9e 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -54,7 +54,7 @@
* Reports a new location to passive location provider clients.
*/
public void updateLocation(LocationResult locationResult) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
PassiveLocationProvider passive = (PassiveLocationProvider) mProvider.getProvider();
Preconditions.checkState(passive != null);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c5f73625..8ab3a94 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1587,7 +1587,7 @@
if (!savedCredential.isNone()) {
throw new IllegalStateException("Saved credential given, but user has no SP");
}
- initializeSyntheticPasswordLocked(savedCredential, userId);
+ initializeSyntheticPasswordLocked(userId);
} else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
// get credential from keystore when profile has unified lock
try {
@@ -2513,35 +2513,21 @@
}
/**
- * Creates the synthetic password (SP) for the given user and protects it with the user's LSKF.
+ * Creates the synthetic password (SP) for the given user and protects it with an empty LSKF.
* This is called just once in the lifetime of the user: the first time a nonempty LSKF is set,
* or when an escrow token is activated on a device with an empty LSKF.
- *
- * Maintains the SP invariants described in {@link SyntheticPasswordManager}.
*/
@GuardedBy("mSpManager")
@VisibleForTesting
- SyntheticPassword initializeSyntheticPasswordLocked(LockscreenCredential credential,
- int userId) {
+ SyntheticPassword initializeSyntheticPasswordLocked(int userId) {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
SyntheticPasswordManager.NULL_PROTECTOR_ID,
"Cannot reinitialize SP");
final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
- long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(), credential,
- sp, userId);
- if (!credential.isNone()) {
- mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
- mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
- setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
- setKeystorePassword(sp.deriveKeyStorePassword(), userId);
- } else {
- clearUserKeyProtection(userId, null);
- setKeystorePassword(null, userId);
- gateKeeperClearSecureUserId(userId);
- }
- fixateNewestUserKeyAuth(userId);
+ final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
+ LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
onSyntheticPasswordKnown(userId, sp);
return sp;
@@ -2818,8 +2804,7 @@
if (!isUserSecure(userId)) {
long protectorId = getCurrentLskfBasedProtectorId(userId);
if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
- sp = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
- userId);
+ sp = initializeSyntheticPasswordLocked(userId);
} else {
sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
LockscreenCredential.createNone(), userId, null).syntheticPassword;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e03b423..aa2a25b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1445,6 +1445,11 @@
}
if (flags != data.getFlags()) {
+ int changedFlags = data.getFlags() ^ flags;
+ if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
+ // Suppress notification flag changed, clear any effects
+ clearEffectsLocked(key);
+ }
data.setFlags(flags);
// Shouldn't alert again just because of a flag change.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
@@ -1596,6 +1601,20 @@
updateLightsLocked();
}
+ @GuardedBy("mNotificationLock")
+ private void clearEffectsLocked(String key) {
+ if (key.equals(mSoundNotificationKey)) {
+ clearSoundLocked();
+ }
+ if (key.equals(mVibrateNotificationKey)) {
+ clearVibrateLocked();
+ }
+ boolean removed = mLights.remove(key);
+ if (removed) {
+ updateLightsLocked();
+ }
+ }
+
protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -6665,6 +6684,20 @@
}
}
+ // Ensure only allowed packages have a substitute app name
+ if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
+ int hasSubstituteAppNamePermission = mPackageManager.checkPermission(
+ permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId);
+ if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) {
+ notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME);
+ if (DBG) {
+ Slog.w(TAG, "warning: pkg " + pkg + " attempting to substitute app name"
+ + " without holding perm "
+ + Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME);
+ }
+ }
+ }
+
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
}
@@ -7814,7 +7847,8 @@
&& (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0;
if (!record.isUpdate
&& record.getImportance() > IMPORTANCE_MIN
- && !suppressedByDnd) {
+ && !suppressedByDnd
+ && isNotificationForCurrentUser(record)) {
sendAccessibilityEvent(record);
sentAccessibilityEvent = true;
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 477b8da..729c521 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -86,6 +86,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -1327,16 +1328,17 @@
return null;
}
NotificationChannelGroup group = r.groups.get(groupId).clone();
- group.setChannels(new ArrayList<>());
+ ArrayList channels = new ArrayList();
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
if (groupId.equals(nc.getGroup())) {
- group.addChannel(nc);
+ channels.add(nc);
}
}
}
+ group.setChannels(channels);
return group;
}
}
@@ -1349,7 +1351,10 @@
if (r == null) {
return null;
}
- return r.groups.get(groupId);
+ if (r.groups.get(groupId) != null) {
+ return r.groups.get(groupId).clone();
+ }
+ return null;
}
}
@@ -1357,44 +1362,48 @@
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Objects.requireNonNull(pkg);
- Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
+ List<NotificationChannelGroup> groups = new ArrayList<>();
synchronized (mPackagePreferences) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return ParceledListSlice.emptyList();
}
- NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap();
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
if (nc.getGroup() != null) {
if (r.groups.get(nc.getGroup()) != null) {
- NotificationChannelGroup ncg = groups.get(nc.getGroup());
- if (ncg == null) {
- ncg = r.groups.get(nc.getGroup()).clone();
- ncg.setChannels(new ArrayList<>());
- groups.put(nc.getGroup(), ncg);
-
- }
- ncg.addChannel(nc);
+ ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
+ nc.getGroup(), new ArrayList<>());
+ channels.add(nc);
+ groupedChannels.put(nc.getGroup(), channels);
}
} else {
- nonGrouped.addChannel(nc);
+ ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
+ null, new ArrayList<>());
+ channels.add(nc);
+ groupedChannels.put(null, channels);
}
}
}
- if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
- groups.put(null, nonGrouped);
- }
- if (includeEmpty) {
- for (NotificationChannelGroup group : r.groups.values()) {
- if (!groups.containsKey(group.getId())) {
- groups.put(group.getId(), group);
- }
+ for (NotificationChannelGroup group : r.groups.values()) {
+ ArrayList<NotificationChannel> channels =
+ groupedChannels.getOrDefault(group.getId(), new ArrayList<>());
+ if (includeEmpty || !channels.isEmpty()) {
+ NotificationChannelGroup clone = group.clone();
+ clone.setChannels(channels);
+ groups.add(clone);
}
}
- return new ParceledListSlice<>(new ArrayList<>(groups.values()));
+
+ if (includeNonGrouped && groupedChannels.containsKey(null)) {
+ NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ nonGrouped.setChannels(groupedChannels.get(null));
+ groups.add(nonGrouped);
+ }
+ return new ParceledListSlice<>(groups);
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 7159673..51b36dd 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -39,6 +39,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -1416,16 +1417,18 @@
private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
final int userId) {
+ final PackageManagerInternal pmInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ final ActivityManagerInternal amInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
CollectionUtils.forEach(targetPackages, target -> {
final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", target, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- try {
- ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null,
- null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
- } catch (RemoteException e) {
- Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e);
- }
+ final int[] allowList = pmInternal.getVisibilityAllowList(target, userId);
+ amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */,
+ false /* serialized */, userId, allowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */);
});
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 8501c5e..5b3eff9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -28,6 +28,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -340,7 +341,9 @@
return !isForceQueryable(targetPkgSetting.getAppId())
&& !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId());
}
- if (mCacheReady) { // use cache
+ // use cache
+ if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache",
+ true)) {
if (!shouldFilterApplicationUsingCache(callingUid,
targetPkgSetting.getAppId(),
userId)) {
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 2aec187..4e9c472 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -28,7 +28,6 @@
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.BroadcastOptions;
@@ -41,19 +40,20 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerExemptionManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.text.TextUtils;
+import android.util.IntArray;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
/**
* Helper class to send broadcasts for various situations.
@@ -80,6 +80,7 @@
final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
final int[] userIds, int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
try {
final IActivityManager am = ActivityManager.getService();
@@ -93,11 +94,13 @@
if (ArrayUtils.isEmpty(instantUserIds)) {
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+ resolvedUserIds, false /* isInstantApp */, broadcastAllowList,
+ filterExtrasForReceiver, bOptions);
} else {
// send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- instantUserIds, true /* isInstantApp */, null, bOptions);
+ instantUserIds, true /* isInstantApp */, null,
+ null /* filterExtrasForReceiver */, bOptions);
}
} catch (RemoteException ex) {
}
@@ -113,6 +116,7 @@
public void doSendBroadcast(String action, String pkg, Bundle extras,
int flags, String targetPkg, IIntentReceiver finishedReceiver,
int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
for (int userId : userIds) {
final Intent intent = new Intent(action,
@@ -148,45 +152,30 @@
intent, finishedReceiver, requiredPermissions,
finishedReceiver != null, userId,
broadcastAllowList == null ? null : broadcastAllowList.get(userId),
- bOptions);
+ filterExtrasForReceiver, bOptions);
}
}
- public void sendResourcesChangedBroadcast(@NonNull Computer snapshot, boolean mediaStatus,
- boolean replacing, @NonNull String[] pkgNames, @NonNull int[] uids) {
+ public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer,
+ boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames,
+ @NonNull int[] uids) {
if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
return;
}
-
- try {
- final IActivityManager am = ActivityManager.getService();
- if (am == null) {
- return;
- }
-
- final int[] resolvedUserIds = am.getRunningUserIds();
- for (int userId : resolvedUserIds) {
- final var lists = getBroadcastParams(snapshot, pkgNames, uids, userId);
- for (int i = 0; i < lists.size(); i++) {
- // Send broadcasts here
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
- list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- final String action =
- mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
- : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
- sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
- null /* targetPkg */, null /* finishedReceiver */, new int[]{userId},
- null /* instantUserIds */, allowList, null /* bOptions */);
- }
- }
- } catch (RemoteException ex) {
+ Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+ if (replacing) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
}
+ String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
+ : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
+ sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
+ null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
+ null /* instantUserIds */, null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> filterExtrasChangedPackageList(
+ snapshotComputer.get(), callingUid, intentExtras),
+ null /* bOptions */);
}
/**
@@ -266,7 +255,8 @@
final int flags = !componentNames.contains(packageName)
? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
- userIds, instantUserIds, broadcastAllowList, null);
+ userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */);
}
public static void sendDeviceCustomizationReadyBroadcast() {
@@ -334,53 +324,72 @@
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, 0, null, null, userIds, instantUserIds,
- broadcastAllowlist, null);
+ broadcastAllowlist, null /* filterExtrasForReceiver */, null);
}
public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
- installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */, null);
+ installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
+ null /* filterExtrasForReceiver */, null);
}
/**
- * Get broadcast params list based on the given package and uid list. The broadcast params are
- * used to send broadcast separately if the given packages have different visibility allow list.
+ * Filter package names for the intent extras {@link Intent#EXTRA_CHANGED_PACKAGE_LIST} and
+ * {@link Intent#EXTRA_CHANGED_UID_LIST} by using the rules of the package visibility.
*
- * @param pkgList The names of packages which have changes.
- * @param uidList The uids of packages which have changes.
- * @param userId The user where packages reside.
- * @return The list of {@link BroadcastParams} object.
+ * @param callingUid The uid that is going to access the intent extras.
+ * @param extras The intent extras to filter
+ * @return An extras that have been filtered, or {@code null} if the given uid is unable to
+ * access all the packages in the extras.
*/
- public List<BroadcastParams> getBroadcastParams(@NonNull Computer snapshot,
- @NonNull String[] pkgList, @NonNull int[] uidList, @UserIdInt int userId) {
- final List<BroadcastParams> lists = new ArrayList<>(pkgList.length);
- // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
- // allow lists are the same.
- for (int i = 0; i < pkgList.length; i++) {
- final String pkgName = pkgList[i];
- final int uid = uidList[i];
- if (TextUtils.isEmpty(pkgName) || Process.INVALID_UID == uid) {
+ @Nullable
+ public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
+ @NonNull Bundle extras) {
+ if (UserHandle.isCore(callingUid)) {
+ // see all
+ return extras;
+ }
+ final String[] pkgs = extras.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (ArrayUtils.isEmpty(pkgs)) {
+ return extras;
+ }
+ final int userId = extras.getInt(
+ Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(callingUid));
+ final int[] uids = extras.getIntArray(Intent.EXTRA_CHANGED_UID_LIST);
+ final Pair<String[], int[]> filteredPkgs =
+ filterPackages(snapshot, pkgs, uids, callingUid, userId);
+ if (ArrayUtils.isEmpty(filteredPkgs.first)) {
+ // caller is unable to access this intent
+ return null;
+ }
+ final Bundle filteredExtras = new Bundle(extras);
+ filteredExtras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, filteredPkgs.first);
+ filteredExtras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, filteredPkgs.second);
+ return filteredExtras;
+ }
+
+ @NonNull
+ private static Pair<String[], int[]> filterPackages(@NonNull Computer snapshot,
+ @NonNull String[] pkgs, @Nullable int[] uids, int callingUid, int userId) {
+ final int pkgSize = pkgs.length;
+ final int uidSize = !ArrayUtils.isEmpty(uids) ? uids.length : 0;
+
+ final ArrayList<String> pkgList = new ArrayList<>(pkgSize);
+ final IntArray uidList = uidSize > 0 ? new IntArray(uidSize) : null;
+ for (int i = 0; i < pkgSize; i++) {
+ final String packageName = pkgs[i];
+ if (snapshot.shouldFilterApplication(
+ snapshot.getPackageStateInternal(packageName), callingUid, userId)) {
continue;
}
- int[] allowList = snapshot.getVisibilityAllowList(pkgName, userId);
- if (allowList == null) {
- allowList = new int[0];
- }
- boolean merged = false;
- for (int j = 0; j < lists.size(); j++) {
- final BroadcastParams list = lists.get(j);
- if (Arrays.equals(list.getAllowList().get(userId), allowList)) {
- list.addPackage(pkgName, uid);
- merged = true;
- break;
- }
- }
- if (!merged) {
- lists.add(new BroadcastParams(pkgName, uid, allowList, userId));
+ pkgList.add(packageName);
+ if (uidList != null && i < uidSize) {
+ uidList.add(uids[i]);
}
}
-
- return lists;
+ return new Pair<>(
+ pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null,
+ uidList != null && uidList.size() > 0 ? uidList.toArray() : null);
}
}
diff --git a/services/core/java/com/android/server/pm/BroadcastParams.java b/services/core/java/com/android/server/pm/BroadcastParams.java
deleted file mode 100644
index 279aab0..0000000
--- a/services/core/java/com/android/server/pm/BroadcastParams.java
+++ /dev/null
@@ -1,62 +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.server.pm;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.util.IntArray;
-import android.util.SparseArray;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A helper class that contains information about package names and uids that share the same allow
- * list for sending broadcasts. Used by various package helpers.
- */
-final class BroadcastParams {
- private final @NonNull List<String> mPackageNames;
- private final @NonNull IntArray mUids;
- private final @NonNull SparseArray<int[]> mAllowList;
-
- BroadcastParams(@NonNull String packageName, @IntRange(from = 0) int uid,
- @NonNull int[] allowList, @UserIdInt int userId) {
- mPackageNames = new ArrayList<>(Arrays.asList(packageName));
- mUids = IntArray.wrap(new int[]{uid});
- mAllowList = new SparseArray<>(1);
- mAllowList.put(userId, allowList);
- }
-
- public void addPackage(@NonNull String packageName, @IntRange(from = 0) int uid) {
- mPackageNames.add(packageName);
- mUids.add(uid);
- }
-
- public @NonNull String[] getPackageNames() {
- return mPackageNames.toArray(new String[0]);
- }
-
- public @NonNull int[] getUids() {
- return mUids.toArray();
- }
-
- public @NonNull SparseArray<int[]> getAllowList() {
- return mAllowList;
- }
-}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index a878bfd..b3c7cc0 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -500,6 +500,13 @@
boolean isComponentEffectivelyEnabled(@NonNull ComponentInfo componentInfo,
@UserIdInt int userId);
+ /**
+ * @return true if the runtime app user enabled state and the install-time app manifest enabled
+ * state are both effectively enabled for the given app. Or if the app cannot be found,
+ * returns false.
+ */
+ boolean isApplicationEffectivelyEnabled(@NonNull String packageName, @UserIdInt int userId);
+
@Nullable
KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 8ec3d2b..7dc648c 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -5412,6 +5412,26 @@
}
}
+ @Override
+ public boolean isApplicationEffectivelyEnabled(@NonNull String packageName,
+ @UserIdInt int userId) {
+ try {
+ int appEnabledSetting = mSettings.getApplicationEnabledSetting(packageName, userId);
+ if (appEnabledSetting == COMPONENT_ENABLED_STATE_DEFAULT) {
+ final AndroidPackage pkg = getPackage(packageName);
+ if (pkg == null) {
+ // Should not happen because getApplicationEnabledSetting would have thrown
+ return false;
+ }
+ return pkg.isEnabled();
+ } else {
+ return appEnabledSetting == COMPONENT_ENABLED_STATE_ENABLED;
+ }
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return false;
+ }
+ }
+
@Nullable
@Override
public KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias) {
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index 53e9754..dbf0d29 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -27,7 +27,6 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -127,7 +126,7 @@
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);
- sendDistractingPackagesChanged(snapshot, changedPackages, changedUids.toArray(), userId,
+ sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
restrictionFlags);
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -169,7 +168,7 @@
if (!changedPackages.isEmpty()) {
final String[] packageArray = changedPackages.toArray(
new String[changedPackages.size()]);
- sendDistractingPackagesChanged(snapshot, packageArray, changedUids.toArray(), userId,
+ sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
RESTRICTION_NONE);
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -182,24 +181,21 @@
* @param uidList The uids of packages which have suspension changes.
* @param userId The user where packages reside.
*/
- void sendDistractingPackagesChanged(@NonNull Computer snapshot, @NonNull String[] pkgList,
- int[] uidList, int userId, int distractionFlags) {
- final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
- snapshot, pkgList, uidList, userId);
+ void sendDistractingPackagesChanged(@NonNull String[] pkgList, int[] uidList, int userId,
+ int distractionFlags) {
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+
final Handler handler = mInjector.getHandler();
- for (int i = 0; i < lists.size(); i++) {
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
- Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
- extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
- null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
- allowList, null /* bOptions */));
- }
+ handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
+ extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ mPm.snapshotComputer(), callingUid, intentExtras),
+ null /* bOptions */));
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 728d2d1..9db9837 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2626,7 +2626,7 @@
}
final String[] pkgNames = new String[]{res.mRemovedInfo.mRemovedPackage};
final int[] uids = new int[]{res.mRemovedInfo.mUid};
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
false /* mediaStatus */, true /* replacing */, pkgNames, uids);
}
res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
@@ -2804,7 +2804,7 @@
}
final String[] pkgNames = new String[]{packageName};
final int[] uids = new int[]{res.mPkg.getUid()};
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
true /* mediaStatus */, true /* replacing */, pkgNames, uids);
}
} else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 99c95d5..5ad39f2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2947,7 +2947,7 @@
@Nullable Bundle bOptions) {
mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags,
targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
- bOptions));
+ null /* filterExtrasForReceiver */, bOptions));
}
@Override
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index f621d8b..41c6c0f 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -298,7 +298,7 @@
packageNames[i] = pkg.getPackageName();
packageUids[i] = pkg.getUid();
}
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(), mediaStatus,
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus,
replacing, packageNames, packageUids);
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 01aecd9..6847b70 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -38,7 +38,6 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -184,11 +183,9 @@
}
});
- final Computer newSnapshot = mPm.snapshotComputer();
-
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(new String[0]);
- sendPackagesSuspendedForUser(newSnapshot,
+ sendPackagesSuspendedForUser(
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, changedUids.toArray(), userId);
@@ -197,7 +194,7 @@
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!modifiedPackages.isEmpty()) {
- sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
}
return unmodifiablePackages.toArray(new String[0]);
@@ -326,14 +323,12 @@
}
});
- final Computer newSnapshot = mPm.snapshotComputer();
-
mPm.scheduleWritePackageRestrictions(userId);
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
- sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_UNSUSPENDED,
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
packageArray, unsuspendedUids.toArray(), userId);
}
}
@@ -585,23 +580,19 @@
* @param userId The user where packages reside.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- void sendPackagesSuspendedForUser(@NonNull Computer snapshot, @NonNull String intent,
- @NonNull String[] pkgList, @NonNull int[] uidList, int userId) {
- final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
- snapshot, pkgList, uidList, userId);
+ void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
+ @NonNull int[] uidList, int userId) {
final Handler handler = mInjector.getHandler();
- for (int i = 0; i < lists.size(); i++) {
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
- extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
- null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
- allowList, null /* bOptions */));
- }
+ final Bundle extras = new Bundle(3);
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
+ extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ mPm.snapshotComputer(), callingUid, intentExtras),
+ null /* bOptions */));
}
private String getKnownPackageName(@NonNull Computer snapshot,
@@ -652,7 +643,7 @@
}
mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
- targetUserIds, false, null, null);
+ targetUserIds, false, null, null, null);
}
});
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 8dc9428..297439f 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.UserManager;
@@ -305,4 +306,19 @@
* for users that already existed on-disk from an older version of Android.
*/
public abstract boolean shouldIgnorePrepareStorageErrors(int userId);
+
+ /**
+ * Returns the {@link UserProperties} of the given user, or {@code null} if it is not found.
+ * NB: The actual object is returned. So do NOT modify it!
+ */
+ public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
+
+ /** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */
+ public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
+
+ /**
+ * Returns {@code true} if the user is visible (as defined by
+ * {@link UserManager#isUserVisible()} in the given display.
+ */
+ public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5b4468f..e423ea9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.os.UserManager.DISALLOW_USER_SWITCH;
import android.Manifest;
import android.accounts.Account;
@@ -52,6 +53,7 @@
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -107,6 +109,7 @@
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -180,7 +183,7 @@
private static final String TAG_NAME = "name";
private static final String TAG_ACCOUNT = "account";
private static final String ATTR_FLAGS = "flags";
- private static final String ATTR_TYPE = "type";
+ private static final String ATTR_TYPE = "type"; // userType
private static final String ATTR_ICON_PATH = "icon";
private static final String ATTR_ID = "id";
private static final String ATTR_CREATION_TIME = "created";
@@ -215,6 +218,7 @@
private static final String TAG_ENTRY = "entry";
private static final String TAG_VALUE = "value";
private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions";
+ private static final String TAG_USER_PROPERTIES = "userProperties";
private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL =
"lastRequestQuietModeEnabledCall";
private static final String TAG_IGNORE_PREPARE_STORAGE_ERRORS =
@@ -259,7 +263,7 @@
@VisibleForTesting
static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
- private static final int USER_VERSION = 9;
+ private static final int USER_VERSION = 10;
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
@@ -325,6 +329,9 @@
// Whether to perist the seed account information to be available after a boot
boolean persistSeedData;
+ /** Properties of the user whose default values originate from its user type. */
+ UserProperties userProperties;
+
/** Elapsed realtime since boot when the user started. */
long startRealtime;
@@ -620,6 +627,16 @@
@GuardedBy("mUserStates")
private final WatchedUserStates mUserStates = new WatchedUserStates();
+ /**
+ * Used on devices that support background users (key) running on secondary displays (value).
+ *
+ * <p>Is {@code null} by default and instantiated on demand when the users are started on
+ * secondary displays.
+ */
+ @Nullable
+ @GuardedBy("mUsersLock")
+ private SparseIntArray mUsersOnSecondaryDisplays;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -1487,6 +1504,36 @@
return userTypeDetails != null && userTypeDetails.isSystem();
}
+ /**
+ * Returns a *copy* of the given user's UserProperties, stripping out any information for which
+ * the caller lacks permission.
+ */
+ @Override
+ public @NonNull UserProperties getUserPropertiesCopy(@UserIdInt int userId) {
+ checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserProperties");
+ final UserProperties origProperties = getUserPropertiesInternal(userId);
+ if (origProperties != null) {
+ int callingUid = Binder.getCallingUid();
+ boolean exposeAllFields = callingUid == Process.SYSTEM_UID;
+ boolean hasManage = hasPermissionGranted(Manifest.permission.MANAGE_USERS, callingUid);
+ boolean hasQuery = hasPermissionGranted(Manifest.permission.QUERY_USERS, callingUid);
+ return new UserProperties(origProperties, exposeAllFields, hasManage, hasQuery);
+ }
+ // A non-existent or partial user will reach here.
+ throw new IllegalArgumentException("Cannot access properties for user " + userId);
+ }
+
+ /** Returns the user's actual, canonical UserProperties object. Do not edit it externally. */
+ private @Nullable UserProperties getUserPropertiesInternal(@UserIdInt int userId) {
+ synchronized (mUsersLock) {
+ final UserData userData = getUserDataLU(userId);
+ if (userData != null) {
+ return userData.userProperties;
+ }
+ }
+ return null;
+ }
+
@Override
public boolean hasBadge(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasBadge");
@@ -1576,6 +1623,10 @@
public boolean isProfile(@UserIdInt int userId) {
checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
+ return isProfileUnchecked(userId);
+ }
+
+ private boolean isProfileUnchecked(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isProfile();
@@ -1663,15 +1714,33 @@
+ ") is visible");
}
- // First check current foreground user (on main display)
+ return isUserVisibleUnchecked(userId);
+ }
+
+ private boolean isUserVisibleUnchecked(@UserIdInt int userId) {
+ // First check current foreground user and their profiles (on main display)
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ return true;
+ }
+
+ // TODO(b/239824814): STOPSHIP - add CTS tests (requires change on testing infra)
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays != null) {
+ // TODO(b/239824814): make sure it handles profile as well
+ return (mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0);
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
if (currentUserId == userId) {
return true;
}
- // Then profiles of current user
- // TODO(b/239824814): consider using AMInternal.isCurrentProfile() instead
if (isProfile(userId)) {
int parentId = Binder.withCleanCallingIdentity(() -> getProfileParentId(userId));
if (parentId == currentUserId) {
@@ -1679,10 +1748,24 @@
}
}
- // TODO(b/239824814): support background users on secondary display (and their profiles)
return false;
}
+ // TODO(b/239982558): currently used just by shell command, might need to move to
+ // UserManagerInternal if needed by other components (like WindowManagerService)
+ // TODO(b/239982558): add unit test
+ // TODO(b/239982558): try to merge with isUserVisibleUnchecked()
+ private boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ return isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ }
+ synchronized (mUsersLock) {
+ // TODO(b/239824814): make sure it handles profile as well
+ return mUsersOnSecondaryDisplays != null && mUsersOnSecondaryDisplays.get(userId,
+ Display.INVALID_DISPLAY) == displayId;
+ }
+ }
+
@Override
public @NonNull String getUserName() {
final int callingUid = Binder.getCallingUid();
@@ -1803,6 +1886,19 @@
}
@Override
+ public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
+ boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.USER_SWITCHER_ENABLED,
+ Resources.getSystem().getBoolean(com.android.internal
+ .R.bool.config_showUserSwitcherByDefault) ? 1 : 0) != 0;
+
+ return UserManager.supportsMultipleUsers()
+ && !hasUserRestriction(DISALLOW_USER_SWITCH, mUserId)
+ && !UserManager.isDeviceInDemoMode(mContext)
+ && multiUserSettingOn;
+ }
+
+ @Override
public boolean isRestricted(@UserIdInt int userId) {
if (userId != UserHandle.getCallingUserId()) {
checkCreateUsersPermission("query isRestricted for user " + userId);
@@ -2195,7 +2291,6 @@
originatingUserId, local);
localChanged = updateLocalRestrictionsForTargetUsersLR(originatingUserId, local,
updatedLocalTargetUserIds);
-
if (isDeviceOwner) {
// Remember the global restriction owner userId to be able to make a distinction
// in getUserRestrictionSource on who set local policies.
@@ -3268,6 +3363,7 @@
@VisibleForTesting
void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion,
int userTypeVersion) {
+ Slog.i(LOG_TAG, "Upgrading users from userVersion " + userVersion + " to " + USER_VERSION);
Set<Integer> userIdsToWrite = new ArraySet<>();
final int originalVersion = mUserVersion;
final int originalUserTypeVersion = mUserTypeVersion;
@@ -3403,6 +3499,27 @@
userVersion = 9;
}
+ if (userVersion < 10) {
+ // Add UserProperties.
+ synchronized (mUsersLock) {
+ for (int i = 0; i < mUsers.size(); i++) {
+ final UserData userData = mUsers.valueAt(i);
+ final UserTypeDetails userTypeDetails = mUserTypes.get(userData.info.userType);
+ if (userTypeDetails == null) {
+ throw new IllegalStateException(
+ "Cannot upgrade user because " + userData.info.userType
+ + " isn't defined on this device!");
+ }
+ userData.userProperties = new UserProperties(
+ userTypeDetails.getDefaultUserPropertiesReference());
+ userIdsToWrite.add(userData.info.id);
+ }
+ }
+ userVersion = 10;
+ }
+
+ // Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
+
// Done with userVersion changes, moving on to deal with userTypeVersion upgrades
// Upgrade from previous user type to a new user type
final int newUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3417,6 +3534,11 @@
Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
+ USER_VERSION);
} else {
+ if (userVersion > USER_VERSION) {
+ Slog.wtf(LOG_TAG, "Upgraded user version " + mUserVersion + " is higher the SDK's "
+ + "one of " + USER_VERSION + ". Someone forgot to update USER_VERSION?");
+ }
+
mUserVersion = userVersion;
mUserTypeVersion = newUserTypeVersion;
@@ -3536,6 +3658,8 @@
flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags, systemUserType);
UserData userData = putUserInfo(system);
+ userData.userProperties = new UserProperties(
+ mUserTypes.get(userData.info.userType).getDefaultUserPropertiesReference());
mNextSerialNumber = MIN_USER_ID;
mUserVersion = USER_VERSION;
mUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3710,6 +3834,12 @@
serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
}
+ if (userData.userProperties != null) {
+ serializer.startTag(null, TAG_USER_PROPERTIES);
+ userData.userProperties.writeToXml(serializer);
+ serializer.endTag(null, TAG_USER_PROPERTIES);
+ }
+
if (userData.getLastRequestQuietModeEnabledMillis() != 0L) {
serializer.startTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL);
serializer.text(String.valueOf(userData.getLastRequestQuietModeEnabledMillis()));
@@ -3829,6 +3959,7 @@
String seedAccountName = null;
String seedAccountType = null;
PersistableBundle seedAccountOptions = null;
+ UserProperties userProperties = null;
Bundle baseRestrictions = null;
Bundle legacyLocalRestrictions = null;
RestrictionsSet localRestrictions = null;
@@ -3907,6 +4038,17 @@
} else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
seedAccountOptions = PersistableBundle.restoreFromXml(parser);
persistSeedData = true;
+ } else if (TAG_USER_PROPERTIES.equals(tag)) {
+ // We already got the userType above (if it exists), so we can use it.
+ // And it must exist, since ATTR_TYPE historically predates PROPERTIES.
+ final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
+ if (userTypeDetails == null) {
+ Slog.e(LOG_TAG, "User has properties but no user type!");
+ return null;
+ }
+ final UserProperties defaultProps
+ = userTypeDetails.getDefaultUserPropertiesReference();
+ userProperties = new UserProperties(parser, defaultProps);
} else if (TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL.equals(tag)) {
type = parser.next();
if (type == XmlPullParser.TEXT) {
@@ -3943,6 +4085,7 @@
userData.seedAccountType = seedAccountType;
userData.persistSeedData = persistSeedData;
userData.seedAccountOptions = seedAccountOptions;
+ userData.userProperties = userProperties;
userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
if (ignorePrepareStorageErrors) {
userData.setIgnorePrepareStorageErrors();
@@ -4278,6 +4421,8 @@
}
userData = new UserData();
userData.info = userInfo;
+ userData.userProperties = new UserProperties(
+ userTypeDetails.getDefaultUserPropertiesReference());
mUsers.put(userId, userData);
}
writeUserLP(userData);
@@ -4672,41 +4817,59 @@
null, // use default PullAtomMetadata values
BackgroundThread.getExecutor(),
this::onPullAtom);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.MULTI_USER_INFO,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ this::onPullAtom);
}
/** Writes a UserInfo pulled atom for each user on the device. */
private int onPullAtom(int atomTag, List<StatsEvent> data) {
- if (atomTag != FrameworkStatsLog.USER_INFO) {
- Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag);
- return android.app.StatsManager.PULL_SKIP;
- }
- final List<UserInfo> users = getUsersInternal(true, true, true);
- final int size = users.size();
- if (size > 1) {
- for (int idx = 0; idx < size; idx++) {
- final UserInfo user = users.get(idx);
- final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
- final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
- .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
- ?
- user.userType : null;
+ if (atomTag == FrameworkStatsLog.USER_INFO) {
+ final List<UserInfo> users = getUsersInternal(true, true, true);
+ final int size = users.size();
+ if (size > 1) {
+ for (int idx = 0; idx < size; idx++) {
+ final UserInfo user = users.get(idx);
+ final int userTypeStandard = UserManager.getUserTypeForStatsd(user.userType);
+ final String userTypeCustom = (userTypeStandard == FrameworkStatsLog
+ .USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN)
+ ?
+ user.userType : null;
- boolean isUserRunningUnlocked;
- synchronized (mUserStates) {
- isUserRunningUnlocked =
- mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+ boolean isUserRunningUnlocked;
+ synchronized (mUserStates) {
+ isUserRunningUnlocked =
+ mUserStates.get(user.id, -1) == UserState.STATE_RUNNING_UNLOCKED;
+ }
+
+ data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
+ user.id,
+ userTypeStandard,
+ userTypeCustom,
+ user.flags,
+ user.creationTime,
+ user.lastLoggedInTime,
+ isUserRunningUnlocked
+ ));
+ }
+ }
+ } else if (atomTag == FrameworkStatsLog.MULTI_USER_INFO) {
+ if (UserManager.getMaxSupportedUsers() > 1) {
+ int deviceOwnerUserId = UserHandle.USER_NULL;
+
+ synchronized (mRestrictionsLock) {
+ deviceOwnerUserId = mDeviceOwnerUserId;
}
- data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.USER_INFO,
- user.id,
- userTypeStandard,
- userTypeCustom,
- user.flags,
- user.creationTime,
- user.lastLoggedInTime,
- isUserRunningUnlocked
- ));
+ data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.MULTI_USER_INFO,
+ UserManager.getMaxSupportedUsers(),
+ isUserSwitcherEnabled(deviceOwnerUserId)));
}
+ } else {
+ Slogf.e(LOG_TAG, "Unexpected atom tag: %d", atomTag);
+ return android.app.StatsManager.PULL_SKIP;
}
return android.app.StatsManager.PULL_SUCCESS;
}
@@ -5604,6 +5767,7 @@
}
// If we got here, we probably recycled user ids, so invalidate any caches.
UserManager.invalidateStaticUserProperties();
+ UserManager.invalidateUserPropertiesCache();
if (nextId < 0) {
throw new IllegalStateException("No user id available!");
}
@@ -5776,6 +5940,11 @@
pw.println(" --reboot (which does a full reboot) or");
pw.println(" --no-restart (which requires a manual restart)");
pw.println();
+ pw.println(" is-user-visible [--display DISPLAY_ID] <USER_ID>");
+ pw.println(" Checks if the given user is visible in the given display.");
+ pw.println(" If the display option is not set, it uses the user's context to check");
+ pw.println(" (so it emulates what apps would get from UserManager.isUserVisible())");
+ pw.println();
}
@Override
@@ -5792,6 +5961,8 @@
return runReportPackageAllowlistProblems();
case "set-system-user-mode-emulation":
return runSetSystemUserModeEmulation();
+ case "is-user-visible":
+ return runIsUserVisible();
default:
return handleDefaultCommands(cmd);
}
@@ -5841,9 +6012,6 @@
for (int i = 0; i < size; i++) {
final UserInfo user = users.get(i);
final boolean running = am.isUserRunning(user.id, 0);
- final boolean current = user.id == currentUser;
- final boolean hasParent = user.profileGroupId != user.id
- && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
if (verbose) {
final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
String deviceOwner = "";
@@ -5862,7 +6030,11 @@
Binder.restoreCallingIdentity(ident);
}
}
- pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
+ final boolean current = user.id == currentUser;
+ final boolean hasParent = user.profileGroupId != user.id
+ && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+ final boolean visible = isUserVisibleUnchecked(user.id);
+ pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s%s\n",
i,
user.id,
user.name,
@@ -5874,7 +6046,9 @@
user.preCreated ? " (pre-created)" : "",
user.convertedFromPreCreated ? " (converted)" : "",
deviceOwner, profileOwner,
- current ? " (current)" : "");
+ current ? " (current)" : "",
+ visible ? " (visible)" : ""
+ );
} else {
// NOTE: the standard "list users" command is used by integration tests and
// hence should not be changed. If you need to add more info, use the
@@ -6031,6 +6205,56 @@
return 0;
}
+ private int runIsUserVisible() {
+ PrintWriter pw = getOutPrintWriter();
+ int displayId = Display.INVALID_DISPLAY;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ boolean invalidOption = false;
+ switch (opt) {
+ case "--display":
+ displayId = Integer.parseInt(getNextArgRequired());
+ invalidOption = displayId == Display.INVALID_DISPLAY;
+ break;
+ default:
+ invalidOption = true;
+ }
+ if (invalidOption) {
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+ int userId = UserHandle.parseUserArg(getNextArgRequired());
+ switch (userId) {
+ case UserHandle.USER_ALL:
+ case UserHandle.USER_CURRENT_OR_SELF:
+ case UserHandle.USER_NULL:
+ pw.printf("invalid value (%d) for --user option\n", userId);
+ return -1;
+ case UserHandle.USER_CURRENT:
+ userId = ActivityManager.getCurrentUser();
+ break;
+ }
+
+ boolean isVisible;
+ if (displayId != Display.INVALID_DISPLAY) {
+ isVisible = isUserVisibleOnDisplay(userId, displayId);
+ } else {
+ isVisible = getUserManagerForUser(userId).isUserVisible();
+ }
+ pw.println(isVisible);
+ return 0;
+ }
+
+ /**
+ * Gets the {@link UserManager} associated with the context of the given user.
+ */
+ private UserManager getUserManagerForUser(int userId) {
+ UserHandle user = UserHandle.of(userId);
+ Context context = mContext.createContextAsUser(user, /* flags= */ 0);
+ return context.getSystemService(UserManager.class);
+ }
+
/**
* Confirms if the build is debuggable
*
@@ -6130,12 +6354,17 @@
pw.print(" Cached user IDs (including pre-created): ");
pw.println(Arrays.toString(mUserIdsIncludingPreCreated));
}
-
} // synchronized (mPackagesLock)
// Multiple Users on Multiple Display info
pw.println(" Supports users on secondary displays: "
+ UserManager.isUsersOnSecondaryDisplaysEnabled());
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays != null) {
+ pw.print(" Users on secondary displays: ");
+ pw.println(mUsersOnSecondaryDisplays);
+ }
+ }
// Dump some capabilities
pw.println();
@@ -6300,6 +6529,10 @@
}
}
+ if (userData.userProperties != null) {
+ userData.userProperties.println(pw, " ");
+ }
+
pw.println(" Ignore errors preparing storage: "
+ userData.getIgnorePrepareStorageErrors());
}
@@ -6686,7 +6919,98 @@
return userData != null && userData.getIgnorePrepareStorageErrors();
}
}
- }
+
+ @Override
+ public @Nullable UserProperties getUserProperties(@UserIdInt int userId) {
+ final UserProperties props = getUserPropertiesInternal(userId);
+ if (props == null) {
+ Slog.w(LOG_TAG, "A null UserProperties was returned for user " + userId);
+ }
+ return props;
+ }
+
+ @Override
+ public void assignUserToDisplay(int userId, int displayId) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplays=%s",
+ userId, displayId, mUsersOnSecondaryDisplays);
+ }
+ // TODO(b/240613396) throw exception if feature not supported
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays == null) {
+ if (false) {
+ // TODO(b/240613396): remove this if once we check for support above
+ Slogf.wtf(LOG_TAG, "assignUserToDisplay(%d, %d): no "
+ + "mUsersOnSecondaryDisplays", userId, displayId);
+ }
+ return;
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays", userId);
+ }
+ mUsersOnSecondaryDisplays.delete(userId);
+ if (mUsersOnSecondaryDisplays.size() == 0) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing mUsersOnSecondaryDisplays");
+ }
+ mUsersOnSecondaryDisplays = null;
+ }
+ }
+ return;
+ }
+
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays == null) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Creating mUsersOnSecondaryDisplays");
+ }
+ mUsersOnSecondaryDisplays = new SparseIntArray();
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Adding %d->%d to mUsersOnSecondaryDisplays",
+ userId, displayId);
+ }
+
+ if (isProfileUnchecked(userId)) {
+ // Profile can only start in the same display as parent
+ int parentUserId = getProfileParentId(userId);
+ int parentDisplayId = mUsersOnSecondaryDisplays.get(parentUserId);
+ if (displayId != parentDisplayId) {
+ throw new IllegalStateException("Cannot assign profile " + userId + " to "
+ + "display " + displayId + " as its parent (user " + parentUserId
+ + ") is assigned to display " + parentDisplayId);
+ }
+ } else {
+ // Check if display is available
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ // Make sure display is not used by other users...
+ // TODO(b/240736142); currently, if a user was started in a display, it
+ // would need to be stopped first, so "switching" a user on secondary
+ // diplay requires 2 non-atomic operations (stop and start). Once this logic
+ // is refactored, it should be atomic.
+ if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
+ throw new IllegalStateException("Cannot assign " + userId + " to "
+ + "display " + displayId + " as it's already assigned to "
+ + "user " + mUsersOnSecondaryDisplays.keyAt(i));
+ }
+ // TODO(b/239982558) also check that user is not already assigned to other
+ // display (including 0). That would be harder to tested under CTS though
+ // (for example, would need to add a new AM method to start user in bg on
+ // main display), so it's better to test on unit tests
+ }
+ }
+
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+ }
+
+ @Override
+ public boolean isUserVisible(int userId, int displayId) {
+ return isUserVisibleOnDisplay(userId, displayId);
+ }
+ } // class LocalService
/**
* Check if user has restrictions
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 2f3ca66..ebb9f98 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -23,6 +23,7 @@
import android.annotation.StringRes;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserManager;
@@ -171,6 +172,12 @@
private final @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl;
+ /**
+ * The default {@link UserProperties} for the user type.
+ * <p> The uninitialized value of each property is implied by {@link UserProperties.Builder}.
+ */
+ private final @NonNull UserProperties mDefaultUserProperties;
+
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -183,7 +190,8 @@
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
boolean isMediaSharedWithParent,
boolean isCredentialSharableWithParent,
- @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
+ @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel,
+ @NonNull UserProperties defaultUserProperties) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -205,6 +213,7 @@
this.mIsMediaSharedWithParent = isMediaSharedWithParent;
this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
this.mCrossProfileIntentFilterAccessControl = accessControlLevel;
+ this.mDefaultUserProperties = defaultUserProperties;
}
/**
@@ -310,18 +319,6 @@
return mDarkThemeBadgeColors[Math.min(badgeIndex, mDarkThemeBadgeColors.length - 1)];
}
- public boolean isProfile() {
- return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
- }
-
- public boolean isFull() {
- return (mBaseType & UserInfo.FLAG_FULL) != 0;
- }
-
- public boolean isSystem() {
- return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
- }
-
/**
* Returns true if the user has shared media with parent user or false otherwise.
*/
@@ -347,6 +344,26 @@
return mCrossProfileIntentFilterAccessControl;
}
+ /**
+ * Returns the reference to the default {@link UserProperties} for this type of user.
+ * This is not a copy. Do NOT modify this object.
+ */
+ public @NonNull UserProperties getDefaultUserPropertiesReference() {
+ return mDefaultUserProperties;
+ }
+
+ public boolean isProfile() {
+ return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
+ }
+
+ public boolean isFull() {
+ return (mBaseType & UserInfo.FLAG_FULL) != 0;
+ }
+
+ public boolean isSystem() {
+ return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
+ }
+
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -384,6 +401,7 @@
pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
+ mDefaultUserProperties.println(pw, prefix);
final String restrictionsPrefix = prefix + " ";
if (isSystem()) {
@@ -442,6 +460,9 @@
private boolean mIsCredentialSharableWithParent = false;
private @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl = CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
+ // Default UserProperties cannot be null but for efficiency we don't initialize it now.
+ // If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
+ private @Nullable UserProperties mDefaultUserProperties = null;
public Builder setName(String name) {
mName = name;
@@ -560,6 +581,23 @@
return this;
}
+ /**
+ * Sets (replacing if necessary) the default UserProperties object for this user type.
+ * Takes a builder, rather than a built object, to efficiently ensure that a fresh copy of
+ * properties is stored (since it later might be modified by UserProperties#updateFromXml).
+ */
+ public Builder setDefaultUserProperties(UserProperties.Builder userPropertiesBuilder) {
+ mDefaultUserProperties = userPropertiesBuilder.build();
+ return this;
+ }
+
+ public @NonNull UserProperties getDefaultUserProperties() {
+ if (mDefaultUserProperties == null) {
+ mDefaultUserProperties = new UserProperties.Builder().build();
+ }
+ return mDefaultUserProperties;
+ }
+
@UserInfoFlag int getBaseType() {
return mBaseType;
}
@@ -604,7 +642,8 @@
mDefaultCrossProfileIntentFilters,
mIsMediaSharedWithParent,
mIsCredentialSharableWithParent,
- mCrossProfileIntentFilterAccessControl);
+ mCrossProfileIntentFilterAccessControl,
+ getDefaultUserProperties());
}
private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 857a975..b98d20e 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -37,6 +37,7 @@
import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build;
@@ -124,7 +125,10 @@
.setIsMediaSharedWithParent(true)
.setCrossProfileIntentFilterAccessControl(
CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
- .setIsCredentialSharableWithParent(true);
+ .setIsCredentialSharableWithParent(true)
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
}
/**
@@ -156,7 +160,10 @@
.setDefaultRestrictions(getDefaultManagedProfileRestrictions())
.setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
.setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
- .setIsCredentialSharableWithParent(true);
+ .setIsCredentialSharableWithParent(true)
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
}
/**
@@ -396,6 +403,9 @@
setResAttributeArray(parser, builder::setBadgeColors);
} else if (isProfile && "badge-colors-dark".equals(childName)) {
setResAttributeArray(parser, builder::setDarkThemeBadgeColors);
+ } else if ("user-properties".equals(childName)) {
+ builder.getDefaultUserProperties()
+ .updateFromXml(XmlUtils.makeTyped(parser));
} else {
Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
+ parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 0a39e64..47a3705 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -25,6 +25,7 @@
import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
import static android.os.PowerWhitelistManager.REASON_PACKAGE_VERIFIER;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
@@ -77,6 +78,7 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
final class VerifyingSession {
@@ -353,7 +355,7 @@
}
final int verifierUserId = verifierUser.getIdentifier();
- String[] requiredVerifierPackages = mPm.mRequiredVerifierPackages;
+ List<String> requiredVerifierPackages = Arrays.asList(mPm.mRequiredVerifierPackages);
boolean requiredVerifierPackagesOverridden = false;
// Allow verifier override for ADB installations which could already be unverified using
@@ -377,8 +379,7 @@
// are not adding a new way to disable verifications.
if (!isAdbVerificationEnabled(pkgLite, verifierUserId,
requestedDisableVerification)) {
- requiredVerifierPackages = adbVerifierOverridePackages.toArray(
- new String[adbVerifierOverridePackages.size()]);
+ requiredVerifierPackages = adbVerifierOverridePackages;
requiredVerifierPackagesOverridden = true;
}
}
@@ -397,6 +398,16 @@
*/
final Computer snapshot = mPm.snapshotComputer();
+ final int numRequiredVerifierPackages = requiredVerifierPackages.size();
+ for (int i = numRequiredVerifierPackages - 1; i >= 0; i--) {
+ if (!snapshot.isApplicationEffectivelyEnabled(requiredVerifierPackages.get(i),
+ SYSTEM_UID)) {
+ Slog.w(TAG,
+ "Required verifier: " + requiredVerifierPackages.get(i) + " is disabled");
+ requiredVerifierPackages.remove(i);
+ }
+ }
+
for (String requiredVerifierPackage : requiredVerifierPackages) {
final int requiredUid = snapshot.getPackageUid(requiredVerifierPackage,
MATCH_DEBUG_TRIAGED_MISSING, verifierUserId);
@@ -514,7 +525,7 @@
}
}
- if (requiredVerifierPackages.length == 0) {
+ if (requiredVerifierPackages.size() == 0) {
Slog.e(TAG, "No required verifiers");
return;
}
@@ -532,7 +543,7 @@
final Intent requiredIntent;
final String receiverPermission;
- if (!requiredVerifierPackagesOverridden || requiredVerifierPackages.length == 1) {
+ if (!requiredVerifierPackagesOverridden || requiredVerifierPackages.size() == 1) {
// Prod code OR test code+single verifier.
requiredIntent = new Intent(verification);
if (!requiredVerifierPackagesOverridden) {
@@ -657,7 +668,7 @@
* @return true if verification should be performed
*/
private boolean isVerificationEnabled(PackageInfoLite pkgInfoLite, int userId,
- String[] requiredVerifierPackages) {
+ List<String> requiredVerifierPackages) {
if (!DEFAULT_VERIFY_ENABLE) {
return false;
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 9c95769..7602d33 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -354,7 +354,7 @@
* Get op that controls the access related to the permission.
*
* <p>Usually the permission-op relationship is 1:1 but some permissions (e.g. fine location)
- * {@link AppOpsManager#sOpToSwitch share an op} to control the access.
+ * {@link AppOpsManager#opToSwitch(int)} share an op} to control the access.
*
* @param permission The permission
* @return The op that controls the access of the permission
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5816e98..7dcfd8b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5121,18 +5121,10 @@
/** {@inheritDoc} */
@Override
- public void userActivity() {
- // ***************************************
- // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
- // ***************************************
- // THIS IS CALLED FROM DEEP IN THE POWER MANAGER
- // WITH ITS LOCKS HELD.
- //
- // This code must be VERY careful about the locks
- // it acquires.
- // In fact, the current code acquires way too many,
- // and probably has lurking deadlocks.
-
+ public void userActivity(int displayGroupId, int event) {
+ if (displayGroupId == DEFAULT_DISPLAY && event == PowerManager.USER_ACTIVITY_EVENT_TOUCH) {
+ mDefaultDisplayPolicy.onUserActivityEventTouch();
+ }
synchronized (mScreenLockTimeout) {
if (mLockScreenTimerActive) {
// reset the timer
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index e8a3dcd..4f00992 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -1006,7 +1006,7 @@
* Called when userActivity is signalled in the power manager.
* This is safe to call from any thread, with any window manager locks held or not.
*/
- public void userActivity();
+ void userActivity(int displayGroupId, int event);
/**
* Called when we have finished booting and can now display the home
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 685b744..dad9584 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,7 +571,8 @@
/**
* Called when there has been user activity.
*/
- public void onUserActivity(int displayGroupId, int event, int uid) {
+ public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
+ int uid) {
if (DEBUG) {
Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
}
@@ -712,7 +713,7 @@
}
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
tm.notifyUserActivity();
- mPolicy.userActivity();
+ mPolicy.userActivity(displayGroupId, event);
mFaceDownDetector.userActivity(event);
mScreenUndimDetector.userActivity(displayGroupId);
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index fec61ac..9fe53fb 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,6 +74,8 @@
private long mLastPowerOnTime;
private long mLastUserActivityTime;
private long mLastUserActivityTimeNoChangeLights;
+ @PowerManager.UserActivityEvent
+ private int mLastUserActivityEvent;
/** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -244,7 +246,7 @@
return true;
}
- boolean dozeLocked(long eventTime, int uid, int reason) {
+ boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
return false;
}
@@ -253,9 +255,14 @@
try {
reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+ long millisSinceLastUserActivity = eventTime - Math.max(
+ mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
Slog.i(TAG, "Powering off display group due to "
- + PowerManager.sleepReasonToString(reason) + " (groupId= " + getGroupId()
- + ", uid= " + uid + ")...");
+ + PowerManager.sleepReasonToString(reason)
+ + " (groupId= " + getGroupId() + ", uid= " + uid
+ + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
+ + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
+ mLastUserActivityEvent) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -266,14 +273,16 @@
return true;
}
- boolean sleepLocked(long eventTime, int uid, int reason) {
+ boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
return false;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
try {
- Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+ Slog.i(TAG,
+ "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
+ + PowerManager.sleepReasonToString(reason) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
/* opPackageName= */ null, /* details= */ null);
@@ -287,16 +296,20 @@
return mLastUserActivityTime;
}
- void setLastUserActivityTimeLocked(long lastUserActivityTime) {
+ void setLastUserActivityTimeLocked(long lastUserActivityTime,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTime = lastUserActivityTime;
+ mLastUserActivityEvent = event;
}
public long getLastUserActivityTimeNoChangeLightsLocked() {
return mLastUserActivityTimeNoChangeLights;
}
- public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
+ public void setLastUserActivityTimeNoChangeLightsLocked(long time,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTimeNoChangeLights = time;
+ mLastUserActivityEvent = event;
}
public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2a1748c..ca3599c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1216,6 +1216,7 @@
return;
}
+ Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
mIsFaceDown = isFaceDown;
if (isFaceDown) {
final long currentTime = mClock.uptimeMillis();
@@ -1937,12 +1938,13 @@
// Called from native code.
@SuppressWarnings("unused")
- private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
+ private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
+ int displayId, int flags) {
userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
- private void userActivityInternal(int displayId, long eventTime, int event, int flags,
- int uid) {
+ private void userActivityInternal(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
synchronized (mLock) {
if (displayId == Display.INVALID_DISPLAY) {
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1993,11 +1995,12 @@
@GuardedBy("mLock")
private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
- int event, int flags, int uid) {
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
final int groupId = powerGroup.getGroupId();
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
- + ", eventTime=" + eventTime + ", event=" + event
+ + ", eventTime=" + eventTime
+ + ", event=" + PowerManager.userActivityEventToString(event)
+ ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
}
@@ -2032,7 +2035,7 @@
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
&& eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
+ powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2042,7 +2045,7 @@
}
} else {
if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeLocked(eventTime);
+ powerGroup.setLastUserActivityTimeLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2069,7 +2072,8 @@
@WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
}
if (mForceSuspendActive || !mSystemReady) {
return;
@@ -2092,11 +2096,11 @@
@GuardedBy("mLock")
private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
- int reason, int uid) {
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
- + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mSystemReady || !mBootCompleted) {
@@ -2107,10 +2111,12 @@
}
@GuardedBy("mLock")
- private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
- int uid) {
+ private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
+ Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mBootCompleted || !mSystemReady) {
return false;
@@ -2172,7 +2178,11 @@
case WAKEFULNESS_DOZING:
traceMethodName = "goToSleep";
Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
- + " (uid " + uid + ")...");
+ + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
+ + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
+ + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
+ + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
+
mLastGlobalSleepTime = eventTime;
mLastGlobalSleepReason = reason;
mLastGlobalSleepTimeRealtime = mClock.elapsedRealtime();
@@ -4257,7 +4267,7 @@
void onUserActivity() {
synchronized (mLock) {
mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
- mClock.uptimeMillis());
+ mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
}
}
@@ -5645,7 +5655,8 @@
}
@Override // Binder call
- public void userActivity(int displayId, long eventTime, int event, int flags) {
+ public void userActivity(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags) {
final long now = mClock.uptimeMillis();
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 96823c8a..f378588 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -72,6 +72,12 @@
public class ThermalManagerService extends SystemService {
private static final String TAG = ThermalManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /** Input range limits for getThermalHeadroom API */
+ public static final int MIN_FORECAST_SEC = 0;
+ public static final int MAX_FORECAST_SEC = 60;
+
/** Lock to protect listen list. */
private final Object mLock = new Object();
@@ -478,6 +484,13 @@
return Float.NaN;
}
+ if (forecastSeconds < MIN_FORECAST_SEC || forecastSeconds > MAX_FORECAST_SEC) {
+ if (DEBUG) {
+ Slog.d(TAG, "Invalid forecastSeconds: " + forecastSeconds);
+ }
+ return Float.NaN;
+ }
+
return mTemperatureWatcher.getForecast(forecastSeconds);
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 0c9ada8..202beb3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -5911,7 +5911,8 @@
}
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
+ long elapsedRealtimeMs, long uptimeMs) {
if (mOnBatteryInternal) {
uid = mapUid(uid);
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -9416,14 +9417,14 @@
}
@Override
- public void noteUserActivityLocked(int type) {
+ public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
}
- if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
- mUserActivityCounters[type].stepAtomic();
+ if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
+ mUserActivityCounters[event].stepAtomic();
} else {
- Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+ Slog.w(TAG, "Unknown user activity event " + event + " was specified.",
new Throwable());
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index d48af21..f8cbd8b3d 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -70,6 +70,7 @@
import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__CLICKED_REVERT_TREATMENT;
import static com.android.server.am.MemoryStatUtil.MemoryStat;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -277,6 +278,8 @@
final boolean mProcessSwitch;
/** The process state of the launching activity prior to the launch */
final int mProcessState;
+ /** The oom adj score of the launching activity prior to the launch */
+ final int mProcessOomAdj;
/** Whether the last launched activity has reported drawn. */
boolean mIsDrawn;
/** The latest activity to have been launched. */
@@ -312,7 +315,7 @@
@Nullable
static TransitionInfo create(@NonNull ActivityRecord r,
@NonNull LaunchingState launchingState, @Nullable ActivityOptions options,
- boolean processRunning, boolean processSwitch, int processState,
+ boolean processRunning, boolean processSwitch, int processState, int processOomAdj,
boolean newActivityCreated, int startResult) {
if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) {
return null;
@@ -328,19 +331,20 @@
transitionType = TYPE_TRANSITION_COLD_LAUNCH;
}
return new TransitionInfo(r, launchingState, options, transitionType, processRunning,
- processSwitch, processState);
+ processSwitch, processState, processOomAdj);
}
/** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
private TransitionInfo(ActivityRecord r, LaunchingState launchingState,
ActivityOptions options, int transitionType, boolean processRunning,
- boolean processSwitch, int processState) {
+ boolean processSwitch, int processState, int processOomAdj) {
mLaunchingState = launchingState;
mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
mTransitionType = transitionType;
mProcessRunning = processRunning;
mProcessSwitch = processSwitch;
mProcessState = processState;
+ mProcessOomAdj = processOomAdj;
mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs;
setLatestLaunchedActivity(r);
// The launching state can be reused by consecutive launch. Its original association
@@ -644,9 +648,15 @@
// interesting.
final boolean processSwitch = !processRunning
|| !processRecord.hasStartedActivity(launchedActivity);
- final int processState = processRunning
- ? processRecord.getCurrentProcState()
- : PROCESS_STATE_NONEXISTENT;
+ final int processState;
+ final int processOomAdj;
+ if (processRunning) {
+ processState = processRecord.getCurrentProcState();
+ processOomAdj = processRecord.getCurrentAdj();
+ } else {
+ processState = PROCESS_STATE_NONEXISTENT;
+ processOomAdj = INVALID_ADJ;
+ }
final TransitionInfo info = launchingState.mAssociatedTransitionInfo;
if (DEBUG_METRICS) {
@@ -654,6 +664,7 @@
+ " launchedActivity=" + launchedActivity + " processRunning=" + processRunning
+ " processSwitch=" + processSwitch
+ " processState=" + processState
+ + " processOomAdj=" + processOomAdj
+ " newActivityCreated=" + newActivityCreated + " info=" + info);
}
@@ -689,8 +700,8 @@
}
final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
- options, processRunning, processSwitch, processState, newActivityCreated,
- resultCode);
+ options, processRunning, processSwitch, processState, processOomAdj,
+ newActivityCreated, resultCode);
if (newInfo == null) {
abort(launchingState, "unrecognized launch");
return;
@@ -1006,8 +1017,10 @@
final long uptime = info.mTransitionDeviceUptimeMs;
final int transitionDelay = info.mCurrentTransitionDelayMs;
final int processState = info.mProcessState;
+ final int processOomAdj = info.mProcessOomAdj;
mLoggerHandler.post(() -> logAppTransition(
- timestamp, uptime, transitionDelay, infoSnapshot, isHibernating, processState));
+ timestamp, uptime, transitionDelay, infoSnapshot, isHibernating,
+ processState, processOomAdj));
}
mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
if (info.mPendingFullyDrawn != null) {
@@ -1020,7 +1033,7 @@
// This gets called on another thread without holding the activity manager lock.
private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs,
int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
- int processState) {
+ int processState, int processOomAdj) {
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.packageName);
builder.setType(info.type);
@@ -1087,7 +1100,8 @@
isLoading,
info.launchedActivityName.hashCode(),
TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
- processState);
+ processState,
+ processOomAdj);
if (DEBUG_METRICS) {
Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d11adc1..7e94743 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -659,6 +659,9 @@
boolean mUseTransferredAnimation;
+ /** Whether we need to setup the animation to animate only within the letterbox. */
+ private boolean mNeedsLetterboxedAnimation;
+
/**
* @see #currentLaunchCanTurnScreenOn()
*/
@@ -1566,7 +1569,7 @@
if (task == mLastParentBeforePip && task != null) {
// Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
- .onActivityReparentToTask(this);
+ .onActivityReparentedToTask(this);
// Activity's reparented back from pip, clear the links once established
clearLastParentBeforePip();
}
@@ -1762,8 +1765,12 @@
mLetterboxUiController.layoutLetterbox(winHint);
}
- boolean hasWallpaperBackgroudForLetterbox() {
- return mLetterboxUiController.hasWallpaperBackgroudForLetterbox();
+ boolean hasWallpaperBackgroundForLetterbox() {
+ return mLetterboxUiController.hasWallpaperBackgroundForLetterbox();
+ }
+
+ void updateLetterboxSurface(WindowState winHint, Transaction t) {
+ mLetterboxUiController.updateLetterboxSurface(winHint, t);
}
void updateLetterboxSurface(WindowState winHint) {
@@ -5325,6 +5332,18 @@
commitVisibility(visible, performLayout, false /* fromTransition */);
}
+ void setNeedsLetterboxedAnimation(boolean needsLetterboxedAnimation) {
+ mNeedsLetterboxedAnimation = needsLetterboxedAnimation;
+ }
+
+ boolean isNeedsLetterboxedAnimation() {
+ return mNeedsLetterboxedAnimation;
+ }
+
+ boolean isInLetterboxAnimation() {
+ return mNeedsLetterboxedAnimation && isAnimating();
+ }
+
/**
* Post process after applying an app transition animation.
*
@@ -7252,6 +7271,10 @@
.setParent(getAnimationLeashParent())
.setName(getSurfaceControl() + " - animation-bounds")
.setCallsite("ActivityRecord.createAnimationBoundsLayer");
+ if (mNeedsLetterboxedAnimation) {
+ // Needs to be an effect layer to support rounded corners
+ builder.setEffectLayer();
+ }
final SurfaceControl boundsLayer = builder.build();
t.show(boundsLayer);
return boundsLayer;
@@ -7289,6 +7312,11 @@
mAnimatingActivityRegistry.notifyStarting(this);
}
+ if (mNeedsLetterboxedAnimation) {
+ updateLetterboxSurface(findMainWindow(), t);
+ mNeedsAnimationBoundsLayer = true;
+ }
+
// If the animation needs to be cropped then an animation bounds layer is created as a
// child of the root pinned task or animation layer. The leash is then reparented to this
// new layer.
@@ -7311,6 +7339,17 @@
t.setLayer(leash, 0);
t.setLayer(mAnimationBoundsLayer, getLastLayer());
+ if (mNeedsLetterboxedAnimation) {
+ final int cornerRadius = mLetterboxUiController
+ .getRoundedCornersRadius(findMainWindow());
+
+ final Rect letterboxInnerBounds = new Rect();
+ getLetterboxInnerBounds(letterboxInnerBounds);
+
+ t.setCornerRadius(mAnimationBoundsLayer, cornerRadius)
+ .setCrop(mAnimationBoundsLayer, letterboxInnerBounds);
+ }
+
// Reparent leash to animation bounds layer.
t.reparent(leash, mAnimationBoundsLayer);
}
@@ -7424,6 +7463,12 @@
mAnimationBoundsLayer = null;
}
+ mNeedsAnimationBoundsLayer = false;
+ if (mNeedsLetterboxedAnimation) {
+ mNeedsLetterboxedAnimation = false;
+ updateLetterboxSurface(findMainWindow(), t);
+ }
+
if (mAnimatingActivityRegistry != null) {
mAnimatingActivityRegistry.notifyFinished(this);
}
@@ -7436,7 +7481,6 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished");
mTransit = TRANSIT_OLD_UNSET;
mTransitFlags = 0;
- mNeedsAnimationBoundsLayer = false;
setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
"ActivityRecord");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 87a4fe9..de3b2a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -311,7 +311,7 @@
/**
* The duration to keep a process in animating state (top scheduling group) when the
- * wakefulness is changing from awake to doze or sleep.
+ * wakefulness is dozing (unlocking) or changing from awake to doze or sleep (locking).
*/
private static final long DOZE_ANIMATING_STATE_RETAIN_TIME_MS = 2000;
@@ -1486,6 +1486,7 @@
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ a.configChanges = ActivityInfo.CONFIG_ORIENTATION;
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
@@ -2927,12 +2928,14 @@
mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
final WindowState notificationShade = mRootWindowContainer.getDefaultDisplay()
.getDisplayPolicy().getNotificationShade();
- proc = notificationShade != null
- ? mProcessMap.getProcess(notificationShade.mSession.mPid) : null;
+ proc = notificationShade != null ? notificationShade.getProcess() : null;
}
- if (proc == null) {
- return;
- }
+ setProcessAnimatingWhileDozing(proc);
+ }
+
+ // The caller MUST NOT hold the global lock because it calls AM method directly.
+ void setProcessAnimatingWhileDozing(WindowProcessController proc) {
+ if (proc == null) return;
// Set to activity manager directly to make sure the state can be seen by the subsequent
// update of scheduling group.
proc.setRunningAnimationUnsafe();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 5a1afc4..20032d6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -789,7 +789,7 @@
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
-
+ r.lastLaunchTime = SystemClock.uptimeMillis();
r.setProcess(proc);
// Ensure activity is allowed to be resumed after process has set.
@@ -835,8 +835,6 @@
final IActivityClientController activityClientController =
proc.hasEverLaunchedActivity() ? null : mService.mActivityClientController;
r.launchCount++;
- r.lastLaunchTime = SystemClock.uptimeMillis();
- proc.setLastActivityLaunchTime(r.lastLaunchTime);
if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 963345f..44f388b 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -81,9 +81,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import android.view.Display;
import android.view.RemoteAnimationAdapter;
@@ -1037,6 +1039,32 @@
return;
}
+ if (AppTransition.isActivityTransitOld(transit)) {
+ final ArrayList<Pair<ActivityRecord, Rect>> closingLetterboxes = new ArrayList();
+ for (int i = 0; i < closingApps.size(); ++i) {
+ ActivityRecord closingApp = closingApps.valueAt(i);
+ if (closingApp.areBoundsLetterboxed()) {
+ final Rect insets = closingApp.getLetterboxInsets();
+ closingLetterboxes.add(new Pair(closingApp, insets));
+ }
+ }
+
+ for (int i = 0; i < openingApps.size(); ++i) {
+ ActivityRecord openingApp = openingApps.valueAt(i);
+ if (openingApp.areBoundsLetterboxed()) {
+ final Rect openingInsets = openingApp.getLetterboxInsets();
+ for (Pair<ActivityRecord, Rect> closingLetterbox : closingLetterboxes) {
+ final Rect closingInsets = closingLetterbox.second;
+ if (openingInsets.equals(closingInsets)) {
+ ActivityRecord closingApp = closingLetterbox.first;
+ openingApp.setNeedsLetterboxedAnimation(true);
+ closingApp.setNeedsLetterboxedAnimation(true);
+ }
+ }
+ }
+ }
+ }
+
final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
openingApps, closingApps, true /* visible */);
final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 994f079..5a24099 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -17,8 +17,12 @@
package com.android.server.wm;
import android.annotation.UiThread;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
@@ -305,21 +309,21 @@
private void hideDialogsForPackageUiThread(String name) {
// Hides the "unsupported display" dialog if necessary.
if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
- mUnsupportedDisplaySizeDialog.getPackageName()))) {
+ mUnsupportedDisplaySizeDialog.mPackageName))) {
mUnsupportedDisplaySizeDialog.dismiss();
mUnsupportedDisplaySizeDialog = null;
}
// Hides the "unsupported compile SDK" dialog if necessary.
if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
- mUnsupportedCompileSdkDialog.getPackageName()))) {
+ mUnsupportedCompileSdkDialog.mPackageName))) {
mUnsupportedCompileSdkDialog.dismiss();
mUnsupportedCompileSdkDialog = null;
}
// Hides the "deprecated target sdk version" dialog if necessary.
if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
- mDeprecatedTargetSdkVersionDialog.getPackageName()))) {
+ mDeprecatedTargetSdkVersionDialog.mPackageName))) {
mDeprecatedTargetSdkVersionDialog.dismiss();
mDeprecatedTargetSdkVersionDialog = null;
}
@@ -431,6 +435,49 @@
}
}
+ static class BaseDialog {
+ final AppWarnings mManager;
+ final String mPackageName;
+ AlertDialog mDialog;
+ private BroadcastReceiver mCloseReceiver;
+
+ BaseDialog(AppWarnings manager, String packageName) {
+ mManager = manager;
+ mPackageName = packageName;
+ }
+
+ @UiThread
+ void show() {
+ if (mDialog == null) return;
+ if (mCloseReceiver == null) {
+ mCloseReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ mManager.mUiHandler.hideDialogsForPackage(mPackageName);
+ }
+ }
+ };
+ mManager.mUiContext.registerReceiver(mCloseReceiver,
+ new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+ Context.RECEIVER_EXPORTED);
+ }
+ Slog.w(TAG, "Showing " + getClass().getSimpleName() + " for package " + mPackageName);
+ mDialog.show();
+ }
+
+ @UiThread
+ void dismiss() {
+ if (mDialog == null) return;
+ if (mCloseReceiver != null) {
+ mManager.mUiContext.unregisterReceiver(mCloseReceiver);
+ mCloseReceiver = null;
+ }
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ }
+
/**
* Handles messages on the ActivityTaskManagerService thread.
*/
diff --git a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
index 37244bd..1a7a9b2 100644
--- a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
+++ b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
@@ -16,31 +16,23 @@
package com.android.server.wm;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
-import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
import com.android.internal.R;
import com.android.server.utils.AppInstallerUtil;
-public class DeprecatedTargetSdkVersionDialog {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "DeprecatedTargetSdkVersionDialog" : TAG_ATM;
+class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog {
- private final AlertDialog mDialog;
- private final String mPackageName;
-
- public DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
+ DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
ApplicationInfo appInfo) {
- mPackageName = appInfo.packageName;
+ super(manager, appInfo.packageName);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -75,17 +67,4 @@
// DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
window.getAttributes().setTitle("DeprecatedTargetSdkVersionDialog");
}
-
- public String getPackageName() {
- return mPackageName;
- }
-
- public void show() {
- Log.w(TAG, "Showing SDK deprecation warning for package " + mPackageName);
- mDialog.show();
- }
-
- public void dismiss() {
- mDialog.dismiss();
- }
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 0422906..b033dca 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -504,6 +504,7 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
+ mTransitionController.collectForDisplayAreaChange(this);
mTmpConfiguration.setTo(getConfiguration());
super.onConfigurationChanged(newParentConfig);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fa3fc9f..8a34af3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5946,6 +5946,9 @@
if (changes != 0) {
Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
+ mTempConfig + " for displayId=" + mDisplayId);
+ if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
+ requestChangeTransitionIfNeeded(changes, null /* displayChange */);
+ }
onRequestedOverrideConfigurationChanged(mTempConfig);
final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
@@ -5962,9 +5965,6 @@
}
mWmService.mDisplayNotificationController.dispatchDisplayChanged(
this, getConfiguration());
- if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
- requestChangeTransitionIfNeeded(changes, null /* displayChange */);
- }
}
return changes;
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5221072..1a34c93 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -258,7 +258,7 @@
private volatile boolean mWindowManagerDrawComplete;
private WindowState mStatusBar = null;
- private WindowState mNotificationShade = null;
+ private volatile WindowState mNotificationShade;
private final int[] mStatusBarHeightForRotation = new int[4];
private WindowState mNavigationBar = null;
@NavigationBarPosition
@@ -794,11 +794,11 @@
}
public void setAwake(boolean awake) {
- if (awake == mAwake) {
- return;
- }
- mAwake = awake;
- synchronized (mService.mGlobalLock) {
+ synchronized (mLock) {
+ if (awake == mAwake) {
+ return;
+ }
+ mAwake = awake;
if (!mDisplayContent.isDefaultDisplay) {
return;
}
@@ -2371,18 +2371,19 @@
return;
}
- // The immersive mode confirmation should never affect the system bar visibility, otherwise
+ // Immersive mode confirmation should never affect the system bar visibility, otherwise
// it will unhide the navigation bar and hide itself.
if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
-
- // The immersive mode confirmation took the focus from mLastFocusedWindow which was
- // controlling the system ui visibility. So if mLastFocusedWindow can still receive
- // keys, we let it keep controlling the visibility.
- final boolean lastFocusCanReceiveKeys =
- (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys());
- winCandidate = isKeyguardShowing() && !isKeyguardOccluded() ? mNotificationShade
- : lastFocusCanReceiveKeys ? mLastFocusedWindow
- : mTopFullscreenOpaqueWindowState;
+ if (mNotificationShade != null && mNotificationShade.canReceiveKeys()) {
+ // Let notification shade control the system bar visibility.
+ winCandidate = mNotificationShade;
+ } else if (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys()) {
+ // Immersive mode confirmation took the focus from mLastFocusedWindow which was
+ // controlling the system bar visibility. Let it keep controlling the visibility.
+ winCandidate = mLastFocusedWindow;
+ } else {
+ winCandidate = mTopFullscreenOpaqueWindowState;
+ }
if (winCandidate == null) {
return;
}
@@ -2745,6 +2746,19 @@
mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState);
}
+ /** Called when a {@link android.os.PowerManager#USER_ACTIVITY_EVENT_TOUCH} is sent. */
+ public void onUserActivityEventTouch() {
+ // If there is keyguard, it may use INPUT_FEATURE_DISABLE_USER_ACTIVITY (InputDispatcher
+ // won't trigger user activity for touch). So while the device is not interactive, the user
+ // event is only sent explicitly from SystemUI.
+ if (mAwake) return;
+ // If the event is triggered while the display is not awake, the screen may be showing
+ // dozing UI such as AOD or overlay UI of under display fingerprint. Then set the animating
+ // state temporarily to make the process more responsive.
+ final WindowState w = mNotificationShade;
+ mService.mAtmService.setProcessAnimatingWhileDozing(w != null ? w.getProcess() : null);
+ }
+
boolean onSystemUiSettingsChanged() {
return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId);
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8dd5850..97609a7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -513,19 +513,6 @@
return true;
}
- /**
- * Utility to get a rotating displaycontent from a Transition.
- * @return null if the transition doesn't contain a rotating display.
- */
- static DisplayContent getDisplayFromTransition(Transition transition) {
- for (int i = transition.mParticipants.size() - 1; i >= 0; --i) {
- final WindowContainer wc = transition.mParticipants.valueAt(i);
- if (!(wc instanceof DisplayContent)) continue;
- return (DisplayContent) wc;
- }
- return null;
- }
-
private void startRemoteRotation(int fromRotation, int toRotation) {
mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
fromRotation, toRotation, null /* newDisplayAreaInfo */,
@@ -545,11 +532,6 @@
throw new IllegalStateException("Trying to rotate outside a transition");
}
mDisplayContent.mTransitionController.collect(mDisplayContent);
- // Go through all tasks and collect them before the rotation
- // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
- // handling is synchronized.
- mDisplayContent.mTransitionController.collectForDisplayAreaChange(mDisplayContent,
- null /* use collecting transition */);
}
mService.mAtmService.deferWindowLayout();
try {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7a9e6a9..f3bd1a1 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -761,7 +761,7 @@
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- false /* disable */, 0 /* floatingImeBottomInsets */);
+ false /* disable */, 0 /* floatingImeBottomInsets */, null);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index df3109a..27550d9 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -56,6 +56,7 @@
private final Supplier<Boolean> mHasWallpaperBackgroundSupplier;
private final Supplier<Integer> mBlurRadiusSupplier;
private final Supplier<Float> mDarkScrimAlphaSupplier;
+ private final Supplier<SurfaceControl> mParentSurfaceSupplier;
private final Rect mOuter = new Rect();
private final Rect mInner = new Rect();
@@ -87,7 +88,8 @@
Supplier<Integer> blurRadiusSupplier,
Supplier<Float> darkScrimAlphaSupplier,
IntConsumer doubleTapCallbackX,
- IntConsumer doubleTapCallbackY) {
+ IntConsumer doubleTapCallbackY,
+ Supplier<SurfaceControl> parentSurface) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAreCornersRounded = areCornersRounded;
@@ -97,6 +99,7 @@
mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
mDoubleTapCallbackX = doubleTapCallbackX;
mDoubleTapCallbackY = doubleTapCallbackY;
+ mParentSurfaceSupplier = parentSurface;
}
/**
@@ -121,7 +124,6 @@
mFullWindowSurface.layout(outer.left, outer.top, outer.right, outer.bottom, surfaceOrigin);
}
-
/**
* Gets the insets between the outer and inner rects.
*/
@@ -333,6 +335,7 @@
private SurfaceControl mSurface;
private Color mColor;
private boolean mHasWallpaperBackground;
+ private SurfaceControl mParentSurface;
private final Rect mSurfaceFrameRelative = new Rect();
private final Rect mLayoutFrameGlobal = new Rect();
@@ -403,10 +406,12 @@
}
mColor = mColorSupplier.get();
+ mParentSurface = mParentSurfaceSupplier.get();
t.setColor(mSurface, getRgbColorArray());
t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
mSurfaceFrameRelative.height());
+ t.reparent(mSurface, mParentSurface);
mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get();
updateAlphaAndBlur(t);
@@ -452,12 +457,13 @@
public boolean needsApplySurfaceChanges() {
return !mSurfaceFrameRelative.equals(mLayoutFrameRelative)
- // If mSurfaceFrameRelative is empty then mHasWallpaperBackground and mColor
- // may never be updated in applySurfaceChanges but this doesn't mean that
- // update is needed.
+ // If mSurfaceFrameRelative is empty then mHasWallpaperBackground, mColor,
+ // and mParentSurface may never be updated in applySurfaceChanges but this
+ // doesn't mean that update is needed.
|| !mSurfaceFrameRelative.isEmpty()
&& (mHasWallpaperBackgroundSupplier.get() != mHasWallpaperBackground
- || !mColorSupplier.get().equals(mColor));
+ || !mColorSupplier.get().equals(mColor)
+ || mParentSurfaceSupplier.get() != mParentSurface);
}
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 57c60f4..a469c6b 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -265,7 +265,7 @@
}
/**
- * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
+ * Overrides corners radius for activities presented in the letterbox mode. If given value < 0,
* both it and a value of {@link
* com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
* corners of the activity won't be rounded.
@@ -275,7 +275,7 @@
}
/**
- * Resets corners raidus for activities presented in the letterbox mode to {@link
+ * Resets corners radius for activities presented in the letterbox mode to {@link
* com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
*/
void resetLetterboxActivityCornersRadius() {
@@ -291,7 +291,7 @@
}
/**
- * Gets corners raidus for activities presented in the letterbox mode.
+ * Gets corners radius for activities presented in the letterbox mode.
*/
int getLetterboxActivityCornersRadius() {
return mLetterboxActivityCornersRadius;
@@ -318,7 +318,7 @@
/**
* Sets color of letterbox background which is used when {@link
* #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
- * fallback for other backfround types.
+ * fallback for other background types.
*/
void setLetterboxBackgroundColor(Color color) {
mLetterboxBackgroundColorOverride = color;
@@ -327,7 +327,7 @@
/**
* Sets color ID of letterbox background which is used when {@link
* #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
- * fallback for other backfround types.
+ * fallback for other background types.
*/
void setLetterboxBackgroundColorResourceId(int colorId) {
mLetterboxBackgroundColorResourceIdOverride = colorId;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c8ed602..317c93e 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -89,7 +89,7 @@
// Taskbar expanded height. Used to determine whether to crop an app window to display rounded
// corners above the taskbar.
- private float mExpandedTaskBarHeight;
+ private final float mExpandedTaskBarHeight;
private boolean mShowWallpaperForLetterboxBackground;
@@ -120,7 +120,7 @@
}
}
- boolean hasWallpaperBackgroudForLetterbox() {
+ boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
@@ -137,6 +137,11 @@
void getLetterboxInnerBounds(Rect outBounds) {
if (mLetterbox != null) {
outBounds.set(mLetterbox.getInnerFrame());
+ final WindowState w = mActivityRecord.findMainWindow();
+ if (w == null) {
+ return;
+ }
+ adjustBoundsForTaskbar(w, outBounds);
} else {
outBounds.setEmpty();
}
@@ -160,13 +165,17 @@
}
void updateLetterboxSurface(WindowState winHint) {
+ updateLetterboxSurface(winHint, mActivityRecord.getSyncTransaction());
+ }
+
+ void updateLetterboxSurface(WindowState winHint, Transaction t) {
final WindowState w = mActivityRecord.findMainWindow();
if (w != winHint && winHint != null && w != null) {
return;
}
layoutLetterbox(winHint);
if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
- mLetterbox.applySurfaceChanges(mActivityRecord.getSyncTransaction());
+ mLetterbox.applySurfaceChanges(t);
}
}
@@ -191,14 +200,22 @@
mActivityRecord.mWmService.mTransactionFactory,
this::shouldLetterboxHaveRoundedCorners,
this::getLetterboxBackgroundColor,
- this::hasWallpaperBackgroudForLetterbox,
+ this::hasWallpaperBackgroundForLetterbox,
this::getLetterboxWallpaperBlurRadius,
this::getLetterboxWallpaperDarkScrimAlpha,
this::handleHorizontalDoubleTap,
- this::handleVerticalDoubleTap);
+ this::handleVerticalDoubleTap,
+ this::getLetterboxParentSurface);
mLetterbox.attachInput(w);
}
- mActivityRecord.getPosition(mTmpPoint);
+
+ if (mActivityRecord.isInLetterboxAnimation()) {
+ // In this case we attach the letterbox to the task instead of the activity.
+ mActivityRecord.getTask().getPosition(mTmpPoint);
+ } else {
+ mActivityRecord.getPosition(mTmpPoint);
+ }
+
// Get the bounds of the "space-to-fill". The transformed bounds have the highest
// priority because the activity is launched in a rotated environment. In multi-window
// mode, the task-level represents this. In fullscreen-mode, the task container does
@@ -215,6 +232,13 @@
}
}
+ SurfaceControl getLetterboxParentSurface() {
+ if (mActivityRecord.isInLetterboxAnimation()) {
+ return mActivityRecord.getTask().getSurfaceControl();
+ }
+ return mActivityRecord.getSurfaceControl();
+ }
+
private boolean shouldLetterboxHaveRoundedCorners() {
// TODO(b/214030873): remove once background is drawn for transparent activities
// Letterbox shouldn't have rounded corners if the activity is transparent
@@ -436,7 +460,7 @@
}
break;
case LETTERBOX_BACKGROUND_WALLPAPER:
- if (hasWallpaperBackgroudForLetterbox()) {
+ if (hasWallpaperBackgroundForLetterbox()) {
// Color is used for translucent scrim that dims wallpaper.
return Color.valueOf(Color.BLACK);
}
@@ -459,15 +483,14 @@
private void updateRoundedCorners(WindowState mainWindow) {
final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
if (windowSurface != null && windowSurface.isValid()) {
- Transaction transaction = mActivityRecord.getSyncTransaction();
+ final Transaction transaction = mActivityRecord.getSyncTransaction();
- final InsetsState insetsState = mainWindow.getInsetsState();
- final InsetsSource taskbarInsetsSource =
- insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-
- if (!isLetterboxedNotForDisplayCutout(mainWindow)
- || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()
- || taskbarInsetsSource == null) {
+ if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+ // We don't want corner radius on the window.
+ // In the case the ActivityRecord requires a letterboxed animation we never want
+ // rounded corners on the window because rounded corners are applied at the
+ // animation-bounds surface level and rounded corners on the window would interfere
+ // with that leading to unexpected rounded corner positioning during the animation.
transaction
.setWindowCrop(windowSurface, null)
.setCornerRadius(windowSurface, 0);
@@ -476,48 +499,89 @@
Rect cropBounds = null;
- // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
- // an insets frame is equal to a navigation bar which shouldn't affect position of
- // rounded corners since apps are expected to handle navigation bar inset.
- // This condition checks whether the taskbar is visible.
- // Do not crop the taskbar inset if the window is in immersive mode - the user can
- // swipe to show/hide the taskbar as an overlay.
- if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
- && taskbarInsetsSource.isVisible()) {
+ if (hasVisibleTaskbar(mainWindow)) {
cropBounds = new Rect(mActivityRecord.getBounds());
// 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 cornerners should be displayed above the taskbar.
- cropBounds.bottom =
- Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top);
- if (mActivityRecord.inSizeCompatMode()
- && mActivityRecord.getSizeCompatScale() < 1.0f) {
- cropBounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
- }
+ // Rounded corners should be displayed above the taskbar.
+ adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
}
transaction
.setWindowCrop(windowSurface, cropBounds)
- .setCornerRadius(windowSurface, getRoundedCorners(insetsState));
+ .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
}
}
- // Returns rounded corners radius based on override in
+ private boolean requiresRoundedCorners(WindowState mainWindow) {
+ final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
+
+ return isLetterboxedNotForDisplayCutout(mainWindow)
+ && mLetterboxConfiguration.isLetterboxActivityCornersRounded()
+ && taskbarInsetsSource != null;
+ }
+
+ // Returns rounded corners radius the letterboxed activity should have based on override in
// R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
// Device corners can be different on the right and left sides but we use the same radius
// for all corners for consistency and pick a minimal bottom one for consistency with a
// taskbar rounded corners.
- private int getRoundedCorners(InsetsState insetsState) {
+ int getRoundedCornersRadius(WindowState mainWindow) {
+ if (!requiresRoundedCorners(mainWindow)) {
+ return 0;
+ }
+
if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) {
return mLetterboxConfiguration.getLetterboxActivityCornersRadius();
}
+
+ final InsetsState insetsState = mainWindow.getInsetsState();
return Math.min(
getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
}
+ /**
+ * Returns whether the taskbar is visible. Returns false if the window is in immersive mode,
+ * since the user can swipe to show/hide the taskbar as an overlay.
+ */
+ private boolean hasVisibleTaskbar(WindowState mainWindow) {
+ final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
+
+ return taskbarInsetsSource != null
+ && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
+ && taskbarInsetsSource.isVisible();
+ }
+
+ private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) {
+ final InsetsState insetsState = mainWindow.getInsetsState();
+ return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ }
+
+ private void adjustBoundsForTaskbar(WindowState mainWindow, Rect bounds) {
+ // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
+ // an insets frame is equal to a navigation bar which shouldn't affect position of
+ // rounded corners since apps are expected to handle navigation bar inset.
+ // This condition checks whether the taskbar is visible.
+ // Do not crop the taskbar inset if the window is in immersive mode - the user can
+ // swipe to show/hide the taskbar as an overlay.
+ if (hasVisibleTaskbar(mainWindow)) {
+ adjustBoundsForTaskbarUnchecked(mainWindow, bounds);
+ }
+ }
+
+ private void adjustBoundsForTaskbarUnchecked(WindowState mainWindow, Rect bounds) {
+ // Rounded corners should be displayed above the taskbar.
+ bounds.bottom =
+ Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
+ if (mActivityRecord.inSizeCompatMode()
+ && mActivityRecord.getSizeCompatScale() < 1.0f) {
+ bounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+ }
+ }
+
private int getInsetsStateCornerRadius(
InsetsState insetsState, @RoundedCorner.Position int position) {
RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position);
@@ -592,7 +656,7 @@
+ letterboxBackgroundTypeToString(
mLetterboxConfiguration.getLetterboxBackgroundType()));
pw.println(prefix + " letterboxCornerRadius="
- + getRoundedCorners(mainWin.getInsetsState()));
+ + getRoundedCornersRadius(mainWin));
if (mLetterboxConfiguration.getLetterboxBackgroundType()
== LETTERBOX_BACKGROUND_WALLPAPER) {
pw.println(prefix + " isLetterboxWallpaperBlurSupported="
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 64749cf..a89894d 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -101,7 +101,6 @@
if (t != null) {
mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- mTransitionController.collectForDisplayAreaChange(mDisplayContent, t);
mTransition = t;
}
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index c7f8a1e..f3670e4 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -27,7 +27,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
import android.graphics.Insets;
+import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.power.Boost;
@@ -374,43 +377,45 @@
final int targetSurfaceWidth = bounds.width();
if (maxExtensionInsets.left < 0) {
- final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight);
+ final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1,
+ bounds.bottom);
final Rect extensionRect = new Rect(0, 0,
-maxExtensionInsets.left, targetSurfaceHeight);
- final int xPos = maxExtensionInsets.left;
- final int yPos = 0;
+ final int xPos = bounds.left + maxExtensionInsets.left;
+ final int yPos = bounds.top;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Left Edge Extension", transaction);
}
if (maxExtensionInsets.top < 0) {
- final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1);
+ final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth,
+ bounds.top + 1);
final Rect extensionRect = new Rect(0, 0,
targetSurfaceWidth, -maxExtensionInsets.top);
- final int xPos = 0;
- final int yPos = maxExtensionInsets.top;
+ final int xPos = bounds.left;
+ final int yPos = bounds.top + maxExtensionInsets.top;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Top Edge Extension", transaction);
}
if (maxExtensionInsets.right < 0) {
- final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0,
- targetSurfaceWidth, targetSurfaceHeight);
+ final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right,
+ bounds.bottom);
final Rect extensionRect = new Rect(0, 0,
-maxExtensionInsets.right, targetSurfaceHeight);
- final int xPos = targetSurfaceWidth;
- final int yPos = 0;
+ final int xPos = bounds.right;
+ final int yPos = bounds.top;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Right Edge Extension", transaction);
}
if (maxExtensionInsets.bottom < 0) {
- final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1,
- targetSurfaceWidth, targetSurfaceHeight);
+ final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1,
+ bounds.right, bounds.bottom);
final Rect extensionRect = new Rect(0, 0,
targetSurfaceWidth, -maxExtensionInsets.bottom);
- final int xPos = maxExtensionInsets.left;
- final int yPos = targetSurfaceHeight;
+ final int xPos = bounds.left;
+ final int yPos = bounds.bottom;
createExtensionSurface(leash, edgeBounds,
extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
}
@@ -453,16 +458,20 @@
.setHidden(true)
.setCallsite("DefaultTransitionHandler#startAnimation")
.setOpaque(true)
- .setBufferSize(edgeBounds.width(), edgeBounds.height())
+ .setBufferSize(extensionRect.width(), extensionRect.height())
.build();
- final Surface surface = new Surface(edgeExtensionLayer);
- surface.attachAndQueueBufferWithColorSpace(edgeBuffer.getHardwareBuffer(),
- edgeBuffer.getColorSpace());
- surface.release();
+ BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
+ android.graphics.Shader.TileMode.CLAMP,
+ android.graphics.Shader.TileMode.CLAMP);
+ final Paint paint = new Paint();
+ paint.setShader(shader);
- final float scaleX = getScaleXForExtensionSurface(edgeBounds, extensionRect);
- final float scaleY = getScaleYForExtensionSurface(edgeBounds, extensionRect);
+ final Surface surface = new Surface(edgeExtensionLayer);
+ Canvas c = surface.lockHardwareCanvas();
+ c.drawRect(extensionRect, paint);
+ surface.unlockCanvasAndPost(c);
+ surface.release();
synchronized (mEdgeExtensionLock) {
if (!mEdgeExtensions.containsKey(leash)) {
@@ -472,7 +481,6 @@
return;
}
- startTransaction.setScale(edgeExtensionLayer, scaleX, scaleY);
startTransaction.reparent(edgeExtensionLayer, leash);
startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
@@ -508,8 +516,6 @@
throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
}
-
-
private static final class RunningAnimation {
final AnimationSpec mAnimSpec;
final SurfaceControl mLeash;
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c1f06e4..44b5b88 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2340,7 +2340,10 @@
return false;
}
- return !startBounds.equals(getBounds());
+ // Only take snapshot if the bounds are resized.
+ final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
+ return endBounds.width() != startBounds.width()
+ || endBounds.height() != startBounds.height();
}
boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 6ca5648..88059e1 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -18,7 +18,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -277,7 +277,7 @@
}
@Nullable
- TaskFragmentTransaction.Change prepareActivityReparentToTask(
+ TaskFragmentTransaction.Change prepareActivityReparentedToTask(
@NonNull ActivityRecord activity) {
if (activity.finishing) {
Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
@@ -315,7 +315,7 @@
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
activity.token, task.mTaskId);
- return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENT_TO_TASK)
+ return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
.setTaskId(task.mTaskId)
.setActivityIntent(activity.intent)
.setActivityToken(activityToken);
@@ -521,7 +521,7 @@
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
- void onActivityReparentToTask(@NonNull ActivityRecord activity) {
+ void onActivityReparentedToTask(@NonNull ActivityRecord activity) {
final ITaskFragmentOrganizer organizer;
if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
// If the activity is previously embedded in an organized TaskFragment.
@@ -547,7 +547,7 @@
return;
}
addPendingEvent(new PendingTaskFragmentEvent.Builder(
- PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK, organizer)
+ PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK, organizer)
.setActivity(activity)
.build());
}
@@ -601,7 +601,7 @@
static final int EVENT_INFO_CHANGED = 2;
static final int EVENT_PARENT_INFO_CHANGED = 3;
static final int EVENT_ERROR = 4;
- static final int EVENT_ACTIVITY_REPARENT_TO_TASK = 5;
+ static final int EVENT_ACTIVITY_REPARENTED_TO_TASK = 5;
@IntDef(prefix = "EVENT_", value = {
EVENT_APPEARED,
@@ -609,7 +609,7 @@
EVENT_INFO_CHANGED,
EVENT_PARENT_INFO_CHANGED,
EVENT_ERROR,
- EVENT_ACTIVITY_REPARENT_TO_TASK
+ EVENT_ACTIVITY_REPARENTED_TO_TASK
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
@@ -900,8 +900,8 @@
case PendingTaskFragmentEvent.EVENT_ERROR:
return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
event.mOpType, event.mException);
- case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
- return state.prepareActivityReparentToTask(event.mActivity);
+ case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK:
+ return state.prepareActivityReparentedToTask(event.mActivity);
default:
throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 584a40e..803890b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1205,7 +1205,14 @@
return false;
}
- final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target);
+ final ChangeInfo change = changes.get(target);
+ if (change.mStartParent != null && target.getParent() != change.mStartParent) {
+ // When a window is reparented, the state change won't fit into any of the parents.
+ // Don't promote such change so that we can animate the reparent if needed.
+ return false;
+ }
+
+ final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target);
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer<?> sibling = parent.getChildAt(i);
if (target == sibling) continue;
@@ -1345,14 +1352,14 @@
// Intermediate parents must be those that has window to be managed by Shell.
continue;
}
- if (parentChange.mParent != null && !skipIntermediateReports) {
- changes.get(wc).mParent = p;
+ if (parentChange.mEndParent != null && !skipIntermediateReports) {
+ changes.get(wc).mEndParent = p;
// The chain above the parent was processed.
break;
}
if (targetList.contains(p)) {
if (skipIntermediateReports) {
- changes.get(wc).mParent = p;
+ changes.get(wc).mEndParent = p;
} else {
intermediates.add(p);
}
@@ -1364,10 +1371,10 @@
}
if (!foundParentInTargets || intermediates.isEmpty()) continue;
// Add any always-report parents along the way.
- changes.get(wc).mParent = intermediates.get(0);
+ changes.get(wc).mEndParent = intermediates.get(0);
for (int j = 0; j < intermediates.size() - 1; j++) {
final WindowContainer<?> intermediate = intermediates.get(j);
- changes.get(intermediate).mParent = intermediates.get(j + 1);
+ changes.get(intermediate).mEndParent = intermediates.get(j + 1);
targets.add(intermediate);
}
}
@@ -1480,8 +1487,8 @@
target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
: null, getLeashSurface(target, startT));
// TODO(shell-transitions): Use leash for non-organized windows.
- if (info.mParent != null) {
- change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
+ if (info.mEndParent != null) {
+ change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
}
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
@@ -1634,6 +1641,19 @@
return mainWin.getAttrs().rotationAnimation;
}
+ /** Applies the new configuration and returns {@code true} if there is a display change. */
+ boolean applyDisplayChangeIfNeeded() {
+ boolean changed = false;
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WindowContainer<?> wc = mParticipants.valueAt(i);
+ final DisplayContent dc = wc.asDisplayContent();
+ if (dc == null || !mChanges.get(dc).hasChanged(dc)) continue;
+ dc.sendNewConfiguration();
+ changed = true;
+ }
+ return changed;
+ }
+
boolean getLegacyIsReady() {
return isCollecting() && mSyncId >= 0;
}
@@ -1664,7 +1684,9 @@
@interface Flag {}
// Usually "post" change state.
- WindowContainer mParent;
+ WindowContainer mEndParent;
+ // Parent before change state.
+ WindowContainer mStartParent;
// State tracking
boolean mExistenceChanged = false;
@@ -1685,6 +1707,7 @@
mAbsoluteBounds.set(origState.getBounds());
mShowWallpaper = origState.showWallpaper();
mRotation = origState.getWindowConfiguration().getRotation();
+ mStartParent = origState.getParent();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 4f324f2..846aa3e 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -463,13 +463,12 @@
}
/**
- * Collects the window containers which need to be synced with the changing display (e.g.
- * rotating) to the given transition or the current collecting transition.
+ * Collects the window containers which need to be synced with the changing display area into
+ * the current collecting transition.
*/
- void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc, @Nullable Transition incoming) {
- if (incoming == null) incoming = mCollectingTransition;
- if (incoming == null) return;
- final Transition transition = incoming;
+ void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc) {
+ final Transition transition = mCollectingTransition;
+ if (transition == null || !transition.mParticipants.contains(wc)) return;
// Collect all visible tasks.
wc.forAllLeafTasks(task -> {
if (task.isVisible()) {
diff --git a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
index 6a878b9..f376e8b 100644
--- a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
@@ -29,13 +29,11 @@
import com.android.internal.R;
import com.android.server.utils.AppInstallerUtil;
-public class UnsupportedCompileSdkDialog {
- private final AlertDialog mDialog;
- private final String mPackageName;
+class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog {
- public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
+ UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
ApplicationInfo appInfo) {
- mPackageName = appInfo.packageName;
+ super(manager, appInfo.packageName);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -72,16 +70,4 @@
alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
}
-
- public String getPackageName() {
- return mPackageName;
- }
-
- public void show() {
- mDialog.show();
- }
-
- public void dismiss() {
- mDialog.dismiss();
- }
}
diff --git a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
index 4a800c4..b11c22d 100644
--- a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import com.android.internal.R;
-
import android.app.AlertDialog;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -27,13 +25,13 @@
import android.view.WindowManager;
import android.widget.CheckBox;
-public class UnsupportedDisplaySizeDialog {
- private final AlertDialog mDialog;
- private final String mPackageName;
+import com.android.internal.R;
- public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
+class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog {
+
+ UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
ApplicationInfo appInfo) {
- mPackageName = appInfo.packageName;
+ super(manager, appInfo.packageName);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -63,16 +61,4 @@
alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
}
-
- public String getPackageName() {
- return mPackageName;
- }
-
- public void show() {
- mDialog.show();
- }
-
- public void dismiss() {
- mDialog.dismiss();
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 70dd9f3..73658b2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3019,6 +3019,10 @@
final float windowCornerRadius = !inMultiWindowMode()
? getDisplayContent().getWindowCornerRadius()
: 0;
+ if (asActivityRecord() != null
+ && asActivityRecord().isNeedsLetterboxedAnimation()) {
+ asActivityRecord().getLetterboxInnerBounds(mTmpRect);
+ }
AnimationAdapter adapter = new LocalAnimationAdapter(
new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
getDisplayContent().mAppTransition.canSkipFirstFrame(),
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3ee4be0..22411bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,6 +88,7 @@
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -1326,15 +1327,10 @@
}, UserHandle.ALL, suspendPackagesFilter, null, null);
// Get persisted window scale setting
- mWindowAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- context.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
- setAnimatorDurationScale(Settings.Global.getFloat(resolver,
- Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+ setAnimatorDurationScale(getAnimatorDurationScaleSetting());
mForceDesktopModeOnExternalDisplays = Settings.Global.getInt(resolver,
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
@@ -1408,6 +1404,22 @@
lightRadius);
}
+ private float getTransitionAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
+ private float getAnimatorDurationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+ }
+
+ private float getWindowAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting));
+ }
+
/**
* Called after all entities (such as the {@link ActivityManagerService}) have been set up and
* associated with the {@link WindowManagerService}.
@@ -3407,11 +3419,6 @@
}
}
- static float fixScale(float scale) {
- if (scale < 0) scale = 0;
- else if (scale > 20) scale = 20;
- return Math.abs(scale);
- }
@Override
public void setAnimationScale(int which, float scale) {
@@ -5339,24 +5346,16 @@
final int mode = msg.arg1;
switch (mode) {
case WINDOW_ANIMATION_SCALE: {
- mWindowAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.WINDOW_ANIMATION_SCALE,
- mWindowAnimationScaleSetting);
+ mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
break;
}
case TRANSITION_ANIMATION_SCALE: {
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting =
+ getTransitionAnimationScaleSetting();
break;
}
case ANIMATION_DURATION_SCALE: {
- mAnimatorDurationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.ANIMATOR_DURATION_SCALE,
- mAnimatorDurationScaleSetting);
+ mAnimatorDurationScaleSetting = getAnimatorDurationScaleSetting();
dispatchNewAnimatorScaleLocked(null);
break;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3b9cd36..4f03264 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -397,17 +397,8 @@
mService.deferWindowLayout();
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
try {
- if (transition != null) {
- // First check if we have a display rotation transition and if so, update it.
- final DisplayContent dc = DisplayRotation.getDisplayFromTransition(transition);
- if (dc != null && transition.mChanges.get(dc).hasChanged(dc)) {
- // Go through all tasks and collect them before the rotation
- // TODO(shell-transitions): move collect() to onConfigurationChange once
- // wallpaper handling is synchronized.
- dc.mTransitionController.collectForDisplayAreaChange(dc, transition);
- dc.sendNewConfiguration();
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
- }
+ if (transition != null && transition.applyDisplayChangeIfNeeded()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
final int hopSize = hops.size();
@@ -428,15 +419,6 @@
addToSyncSet(syncId, wc);
}
if (transition != null) transition.collect(wc);
- final DisplayArea da = wc.asDisplayArea();
- // Only check DisplayArea here as a similar thing is done for DisplayContent above.
- if (da != null && wc.asDisplayContent() == null
- && entry.getValue().getWindowingMode() != da.getWindowingMode()) {
- // Go through all tasks and collect them before changing the windowing mode of a
- // display-level container.
- // TODO(shell-transitions): handle this more elegantly.
- da.mTransitionController.collectForDisplayAreaChange(da, transition);
- }
if ((entry.getValue().getChangeMask()
& WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 95b0645..8f63e93 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -27,6 +27,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -125,6 +126,8 @@
private volatile int mCurProcState = PROCESS_STATE_NONEXISTENT;
// Last reported process state;
private volatile int mRepProcState = PROCESS_STATE_NONEXISTENT;
+ // Currently computed oom adj score
+ private volatile int mCurAdj = INVALID_ADJ;
// are we in the process of crashing?
private volatile boolean mCrashing;
// does the app have a not responding dialog?
@@ -319,6 +322,14 @@
return mCurProcState;
}
+ public void setCurrentAdj(int curAdj) {
+ mCurAdj = curAdj;
+ }
+
+ int getCurrentAdj() {
+ return mCurAdj;
+ }
+
/**
* Sets the computed process state from the oom adjustment calculation. This is frequently
* called in activity manager's lock, so don't use window manager lock here.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d222a56..d2b9bda 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3802,6 +3802,10 @@
return wpc != null && wpc.registeredForDisplayAreaConfigChanges();
}
+ WindowProcessController getProcess() {
+ return mWpcForDisplayAreaConfigChanges;
+ }
+
/**
* Fills the given window frames and merged configuration for the client.
*
@@ -6037,7 +6041,7 @@
}
boolean hasWallpaperForLetterboxBackground() {
- return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox();
+ return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroundForLetterbox();
}
/**
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
new file mode 100644
index 0000000..477625a
--- /dev/null
+++ b/services/core/xsd/display-device-config/autobrightness.xsd
@@ -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.
+-->
+<xs:schema version="2.0"
+ elementFormDefault="qualified"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 073b131c..bea5e2c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,6 +23,7 @@
<xs:schema version="2.0"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:include schemaLocation="autobrightness.xsd" />
<xs:element name="displayConfiguration">
<xs:complexType>
<xs:sequence>
@@ -102,22 +103,6 @@
</xs:element>
<!-- Type definitions -->
-
- <xs:complexType name="autoBrightness">
- <xs:sequence>
- <!-- Sets the debounce for autoBrightness brightening in millis-->
- <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- <!-- Sets the debounce for autoBrightness darkening in millis-->
- <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
-
<xs:complexType name="displayQuirks">
<xs:sequence>
<xs:element name="quirk" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c1c7bce..0018a52 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8363,6 +8363,7 @@
+ PackageManager.FEATURE_DEVICE_ADMIN + " feature.");
}
+ // TODO(b/240562946): Remove owner name from API parameters.
@Override
public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId,
boolean setProfileOwnerOnCurrentUserIfNecessary) {
@@ -8397,7 +8398,7 @@
.write();
}
- mOwners.setDeviceOwner(admin, ownerName, userId);
+ mOwners.setDeviceOwner(admin, userId);
mOwners.writeDeviceOwner();
setDeviceOwnershipSystemPropertyLocked();
@@ -8649,6 +8650,7 @@
}
}
+ // TODO(b/240562946): Remove api as owner name is not used.
/**
* Returns the "name" of the device owner. It'll work for non-DO users too, but requires
* MANAGE_USERS.
@@ -8819,6 +8821,7 @@
});
}
+ // TODO(b/240562946): Remove owner name from API parameters.
@Override
public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
if (!mHasFeature) {
@@ -8866,7 +8869,7 @@
// Shutting down backup manager service permanently.
toggleBackupServiceActive(userHandle, /* makeActive= */ false);
- mOwners.setProfileOwner(who, ownerName, userHandle);
+ mOwners.setProfileOwner(who, userHandle);
mOwners.writeProfileOwner(userHandle);
Slogf.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
@@ -9337,6 +9340,7 @@
return who.getPackageName().equals(configPackage);
}
+ // TODO(b/240562946): Remove api as owner name is not used.
@Override
public String getProfileOwnerName(int userHandle) {
if (!mHasFeature) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 08bd3e4..3b46d52 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -193,12 +193,6 @@
}
}
- String getDeviceOwnerName() {
- synchronized (mData) {
- return mData.mDeviceOwner != null ? mData.mDeviceOwner.name : null;
- }
- }
-
ComponentName getDeviceOwnerComponent() {
synchronized (mData) {
return mData.mDeviceOwner != null ? mData.mDeviceOwner.admin : null;
@@ -217,7 +211,7 @@
}
}
- void setDeviceOwner(ComponentName admin, String ownerName, int userId) {
+ void setDeviceOwner(ComponentName admin, int userId) {
if (userId < 0) {
Slog.e(TAG, "Invalid user id for device owner user: " + userId);
return;
@@ -226,7 +220,7 @@
// A device owner is allowed to access device identifiers. Even though this flag
// is not currently checked for device owner, it is set to true here so that it is
// semantically compatible with the meaning of this flag.
- mData.mDeviceOwner = new OwnerInfo(ownerName, admin, /* remoteBugreportUri =*/ null,
+ mData.mDeviceOwner = new OwnerInfo(admin, /* remoteBugreportUri =*/ null,
/* remoteBugreportHash =*/ null, /* isOrganizationOwnedDevice =*/ true);
mData.mDeviceOwnerUserId = userId;
@@ -248,10 +242,10 @@
}
}
- void setProfileOwner(ComponentName admin, String ownerName, int userId) {
+ void setProfileOwner(ComponentName admin, int userId) {
synchronized (mData) {
// For a newly set PO, there's no need for migration.
- mData.mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
+ mData.mProfileOwners.put(userId, new OwnerInfo(admin,
/* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null,
/* isOrganizationOwnedDevice =*/ false));
mUserManagerInternal.setUserManaged(userId, true);
@@ -270,7 +264,7 @@
void transferProfileOwner(ComponentName target, int userId) {
synchronized (mData) {
final OwnerInfo ownerInfo = mData.mProfileOwners.get(userId);
- final OwnerInfo newOwnerInfo = new OwnerInfo(target.getPackageName(), target,
+ final OwnerInfo newOwnerInfo = new OwnerInfo(target,
ownerInfo.remoteBugreportUri, ownerInfo.remoteBugreportHash,
ownerInfo.isOrganizationOwnedDevice);
mData.mProfileOwners.put(userId, newOwnerInfo);
@@ -282,9 +276,7 @@
synchronized (mData) {
Integer previousDeviceOwnerType = mData.mDeviceOwnerTypes.remove(
mData.mDeviceOwner.packageName);
- // We don't set a name because it's not used anyway.
- // See DevicePolicyManagerService#getDeviceOwnerName
- mData.mDeviceOwner = new OwnerInfo(null, target,
+ mData.mDeviceOwner = new OwnerInfo(target,
mData.mDeviceOwner.remoteBugreportUri,
mData.mDeviceOwner.remoteBugreportHash,
mData.mDeviceOwner.isOrganizationOwnedDevice);
@@ -305,13 +297,6 @@
}
}
- String getProfileOwnerName(int userId) {
- synchronized (mData) {
- OwnerInfo profileOwner = mData.mProfileOwners.get(userId);
- return profileOwner != null ? profileOwner.name : null;
- }
- }
-
String getProfileOwnerPackage(int userId) {
synchronized (mData) {
OwnerInfo profileOwner = mData.mProfileOwners.get(userId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 6948420..2ab5464 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -493,16 +493,14 @@
}
static class OwnerInfo {
- public final String name;
public final String packageName;
public final ComponentName admin;
public String remoteBugreportUri;
public String remoteBugreportHash;
public boolean isOrganizationOwnedDevice;
- OwnerInfo(String name, ComponentName admin, String remoteBugreportUri,
+ OwnerInfo(ComponentName admin, String remoteBugreportUri,
String remoteBugreportHash, boolean isOrganizationOwnedDevice) {
- this.name = name;
this.admin = admin;
this.packageName = admin.getPackageName();
this.remoteBugreportUri = remoteBugreportUri;
@@ -512,9 +510,6 @@
public void writeToXml(TypedXmlSerializer out, String tag) throws IOException {
out.startTag(null, tag);
- if (name != null) {
- out.attribute(null, ATTR_NAME, name);
- }
if (admin != null) {
out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString());
}
@@ -532,7 +527,6 @@
}
public static OwnerInfo readFromXml(TypedXmlPullParser parser) {
- final String name = parser.getAttributeValue(null, ATTR_NAME);
final String componentName = parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
final String remoteBugreportUri =
parser.getAttributeValue(null, ATTR_REMOTE_BUGREPORT_URI);
@@ -556,13 +550,11 @@
return null;
}
- return new OwnerInfo(
- name, admin, remoteBugreportUri, remoteBugreportHash, isOrgOwnedDevice);
+ return new OwnerInfo(admin, remoteBugreportUri, remoteBugreportHash, isOrgOwnedDevice);
}
public void dump(IndentingPrintWriter pw) {
pw.println("admin=" + admin);
- pw.println("name=" + name);
pw.println("package=" + packageName);
pw.println("isOrganizationOwnedDevice=" + isOrganizationOwnedDevice);
}
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 297538a..159285a 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -61,8 +61,8 @@
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.backup.transport.TransportNotRegisteredException;
-import com.android.server.testing.shadows.ShadowBackupEligibilityRules;
import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowBackupEligibilityRules;
import com.android.server.testing.shadows.ShadowBinder;
import com.android.server.testing.shadows.ShadowKeyValueBackupJob;
import com.android.server.testing.shadows.ShadowKeyValueBackupTask;
@@ -361,6 +361,26 @@
}
/**
+ * Test verifying that {@link UserBackupManagerService#selectBackupTransport(String)} does not
+ * switch the current transport to the inputted transport, when the inputted transport is not
+ * registered.
+ */
+ @Test
+ public void testSelectBackupTransport_nonRegisteredTransport() throws Exception {
+ setUpForSelectTransport();
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ when(mTransportManager.isTransportRegistered(eq(mNewTransport.transportName)))
+ .thenReturn(false);
+ UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks();
+
+ String oldTransport = backupManagerService.selectBackupTransport(
+ mNewTransport.transportName);
+
+ assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
+ assertThat(oldTransport).isEqualTo(null);
+ }
+
+ /**
* Test verifying that {@link UserBackupManagerService#selectBackupTransport(String)} throws a
* {@link SecurityException} if the caller does not have backup permission.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index b53a2c6..9022db8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -17,8 +17,10 @@
package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -35,11 +37,15 @@
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.annotation.Nullable;
import android.app.GameManager;
import android.app.GameModeInfo;
import android.app.GameState;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -97,6 +103,7 @@
private PowerManagerInternal mMockPowerManager;
@Mock
private UserManager mMockUserManager;
+ private BroadcastReceiver mShutDownActionReceiver;
// Stolen from ConnectivityServiceTest.MockContext
class MockContext extends ContextWrapper {
@@ -165,6 +172,12 @@
}
throw new UnsupportedOperationException("Couldn't find system service: " + name);
}
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
+ mShutDownActionReceiver = receiver;
+ return null;
+ }
}
@Before
@@ -200,15 +213,16 @@
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PowerManagerInternal.class);
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
+ deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
private void startUser(GameManagerService gameManagerService, int userId) {
UserInfo userInfo = new UserInfo(userId, "name", 0);
- gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo));
+ gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo),
+ InstrumentationRegistry.getContext().getFilesDir());
mTestLooper.dispatchAll();
}
@@ -584,7 +598,7 @@
gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
}
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f);
}
@@ -594,7 +608,7 @@
// Validate GamePackageConfiguration returns the correct value.
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
// Validate GameManagerService.isAngleEnabled() returns the correct value.
@@ -607,7 +621,7 @@
// Validate GamePackageConfiguration returns the correct value.
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(
loadingBoost, config.getGameModeConfiguration(gameMode).getLoadingBoostDuration());
@@ -623,7 +637,7 @@
gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
}
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
}
@@ -1049,7 +1063,7 @@
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(90,
config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1064,7 +1078,7 @@
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(0,
config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1092,7 +1106,7 @@
startUser(gameManagerService, USER_ID_1);
gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
@@ -1339,10 +1353,15 @@
mTestLooper.dispatchAll();
/* Expected fileOutput (order may vary)
+ # user 1001:
com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.5,fps=60
com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
com.android.app0 <UID> 0 2 angle=0,scaling=0.6,fps=120 3 angle=0,scaling=0.7,fps=30
+ # user 1002:
+ com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
+ com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
+ com.android.app0 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
The current game mode would only be set to non-zero if the current user have that game
installed.
*/
@@ -1386,7 +1405,7 @@
assertEquals(splitLine[3], "2");
assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
assertEquals(splitLine[5], "3");
- assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
+ assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
splitLine = fileOutput.get(1).split("\\s+");
assertEquals(splitLine[0], "com.android.app1");
assertEquals(splitLine[2], "3");
@@ -1398,7 +1417,7 @@
assertEquals(splitLine[0], "com.android.app0");
assertEquals(splitLine[2], "0");
assertEquals(splitLine[3], "2");
- assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
+ assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
assertEquals(splitLine[5], "3");
assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
@@ -1493,12 +1512,52 @@
@Test
public void testGetResolutionScalingFactor_noUserId() {
- mockModifyGameModeDenied();
+ mockModifyGameModeGranted();
mockDeviceConfigAll();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_2);
- assertEquals(-1f, gameManagerService.getResolutionScalingFactor(mPackageName,
- GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
+ assertThrows(IllegalArgumentException.class, () -> {
+ gameManagerService.getResolutionScalingFactor(mPackageName,
+ GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ });
+ }
+
+ @Test
+ public void testWritingSettingFile_onShutdown() throws InterruptedException {
+ mockModifyGameModeGranted();
+ mockDeviceConfigAll();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onBootCompleted();
+ startUser(gameManagerService, USER_ID_1);
+ Thread.sleep(500);
+ gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
+ GameManager.GAME_MODE_BATTERY, "60", "0.5");
+ gameManagerService.setGameMode("com.android.app1", USER_ID_1,
+ GameManager.GAME_MODE_PERFORMANCE);
+ GameManagerSettings settings = new GameManagerSettings(
+ InstrumentationRegistry.getContext().getFilesDir());
+ Thread.sleep(500);
+ // no data written as delayed messages are queued
+ assertFalse(settings.readPersistentDataLocked());
+ assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
+ Intent shutdown = new Intent();
+ shutdown.setAction(Intent.ACTION_SHUTDOWN);
+ mShutDownActionReceiver.onReceive(mMockContext, shutdown);
+ Thread.sleep(500);
+ // data is written on processing new message with no delay on shutdown,
+ // and all queued messages should be removed
+ assertTrue(settings.readPersistentDataLocked());
+ assertFalse(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
+ }
+
+ private static void deleteFolder(File folder) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ deleteFolder(file);
+ }
+ }
+ folder.delete();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 427c3b3..4a631a12 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -34,7 +34,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
import android.os.BatteryManagerInternal;
import android.os.RemoteException;
import android.util.ArraySet;
@@ -190,7 +189,7 @@
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
- js.serviceInfo = mock(ServiceInfo.class);
+ js.serviceProcessName = "testProcess";
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
js.setStandbyBucket(FREQUENT_INDEX);
return js;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 1b39add..9d039bd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -209,7 +209,8 @@
public void testOnConstantsUpdated_DeadlineProximity() {
JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0));
setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE);
- mFlexibilityController.mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+ mFlexibilityController.mFlexibilityAlarmQueue
+ .scheduleDropNumConstraintsAlarm(js, FROZEN_TIME);
assertEquals(0, js.getNumRequiredFlexibleConstraints());
}
@@ -336,26 +337,31 @@
@Test
public void testCurPercent() {
long deadline = 1000;
+ long nowElapsed;
JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
JobStatus js = createJobStatus("time", jb);
assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(deadline + FROZEN_TIME,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
+ nowElapsed = 600 + FROZEN_TIME;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME), ZoneOffset.UTC);
- assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+ assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+ nowElapsed = 1400;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
- assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+ assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+ nowElapsed = 950 + FROZEN_TIME;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME), ZoneOffset.UTC);
- assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+ assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+ nowElapsed = FROZEN_TIME;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
long delay = 100;
deadline = 1100;
jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
@@ -366,18 +372,21 @@
assertEquals(deadline + FROZEN_TIME,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
+ nowElapsed = 600 + FROZEN_TIME + delay;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(600 + FROZEN_TIME + delay), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+ nowElapsed = 1400;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(1400), ZoneOffset.UTC);
- assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+ assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+ nowElapsed = 950 + FROZEN_TIME + delay;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(950 + FROZEN_TIME + delay), ZoneOffset.UTC);
- assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+ assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
}
@Test
@@ -491,19 +500,19 @@
assertEquals(3, trackedJobs.get(2).size());
assertEquals(0, trackedJobs.get(3).size());
- flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
assertEquals(0, trackedJobs.get(0).size());
assertEquals(1, trackedJobs.get(1).size());
assertEquals(2, trackedJobs.get(2).size());
assertEquals(0, trackedJobs.get(3).size());
- flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
assertEquals(1, trackedJobs.get(0).size());
assertEquals(0, trackedJobs.get(1).size());
assertEquals(2, trackedJobs.get(2).size());
assertEquals(0, trackedJobs.get(3).size());
- flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
assertEquals(0, trackedJobs.get(0).size());
assertEquals(0, trackedJobs.get(1).size());
assertEquals(2, trackedJobs.get(2).size());
@@ -515,24 +524,25 @@
assertEquals(1, trackedJobs.get(2).size());
assertEquals(0, trackedJobs.get(3).size());
- flexTracker.resetJobNumDroppedConstraints(jobs[0]);
+ flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME);
assertEquals(0, trackedJobs.get(0).size());
assertEquals(0, trackedJobs.get(1).size());
assertEquals(2, trackedJobs.get(2).size());
assertEquals(0, trackedJobs.get(3).size());
- flexTracker.adjustJobsRequiredConstraints(jobs[0], -2);
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME);
assertEquals(1, trackedJobs.get(0).size());
assertEquals(0, trackedJobs.get(1).size());
assertEquals(1, trackedJobs.get(2).size());
assertEquals(0, trackedJobs.get(3).size());
+ final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
+ + HOUR_IN_MILLIS);
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
- + HOUR_IN_MILLIS), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- flexTracker.resetJobNumDroppedConstraints(jobs[0]);
+ flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed);
assertEquals(0, trackedJobs.get(0).size());
assertEquals(1, trackedJobs.get(1).size());
assertEquals(1, trackedJobs.get(2).size());
@@ -615,13 +625,13 @@
@Test
public void testSetConstraintSatisfied_Constraints() {
- mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
- mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, FROZEN_TIME);
assertTrue(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
- mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
}
@@ -651,20 +661,21 @@
createJobStatus(String.valueOf(i), jb), null);
}
}
- mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false);
- mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
- mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, false);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false, FROZEN_TIME);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
+ mFlexibilityController.setConstraintSatisfied(
+ CONSTRAINT_BATTERY_NOT_LOW, false, FROZEN_TIME);
assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints);
for (int i = 0; i < constraintCombinations.length; i++) {
constraints = constraintCombinations[i];
mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING,
- (constraints & CONSTRAINT_CHARGING) != 0);
+ (constraints & CONSTRAINT_CHARGING) != 0, FROZEN_TIME);
mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE,
- (constraints & CONSTRAINT_IDLE) != 0);
+ (constraints & CONSTRAINT_IDLE) != 0, FROZEN_TIME);
mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW,
- (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
+ (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0, FROZEN_TIME);
assertEquals(constraints, mFlexibilityController.mSatisfiedFlexibleConstraints);
synchronized (mFlexibilityController.mLock) {
@@ -679,6 +690,7 @@
JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L);
JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
js.adjustNumRequiredFlexibleConstraints(3);
+ long nowElapsed;
mFlexibilityController.mFlexibilityTracker.add(js);
@@ -687,45 +699,51 @@
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
- JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(155L), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1);
+ nowElapsed = 155L;
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
+
+ mFlexibilityController.mFlexibilityTracker
+ .adjustJobsRequiredConstraints(js, -1, nowElapsed);
assertEquals(2, js.getNumRequiredFlexibleConstraints());
assertEquals(1, js.getNumDroppedFlexibleConstraints());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
- mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
assertEquals(2, js.getNumRequiredFlexibleConstraints());
assertEquals(1, js.getNumDroppedFlexibleConstraints());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+ nowElapsed = 140L;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(140L), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
assertEquals(3, js.getNumRequiredFlexibleConstraints());
assertEquals(0, js.getNumDroppedFlexibleConstraints());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ nowElapsed = 175L;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(175), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
assertEquals(0, js.getNumRequiredFlexibleConstraints());
assertEquals(3, js.getNumDroppedFlexibleConstraints());
+ nowElapsed = 165L;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(165L), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
assertEquals(1, js.getNumRequiredFlexibleConstraints());
assertEquals(2, js.getNumDroppedFlexibleConstraints());
@@ -745,12 +763,14 @@
mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+ final long nowElapsed = 150L;
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(150L), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
mFlexibilityController.mPrefetchChangedListener.onPrefetchCacheUpdated(
jobs, js.getUserId(), js.getSourcePackageName(), Long.MAX_VALUE,
- 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+ 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
+ nowElapsed);
assertEquals(150L,
(long) mFlexibilityController.mPrefetchLifeCycleStart
@@ -758,7 +778,7 @@
assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(1150L,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
- assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
+ assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
assertEquals(650L, mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js));
assertEquals(3, js.getNumRequiredFlexibleConstraints());
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index df523fe..149ae0b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -51,7 +51,6 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.MediaStore;
@@ -887,7 +886,7 @@
private static JobStatus createJobStatus(JobInfo job) {
JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
- jobStatus.serviceInfo = mock(ServiceInfo.class);
+ jobStatus.serviceProcessName = "testProcess";
return jobStatus;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 7242b1b..bb477b1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -47,7 +47,6 @@
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ServiceInfo;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
@@ -185,7 +184,7 @@
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
- js.serviceInfo = mock(ServiceInfo.class);
+ js.serviceProcessName = "testProcess";
js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
@@ -491,9 +490,9 @@
final PrefetchController.PrefetchChangedListener prefetchChangedListener =
new PrefetchController.PrefetchChangedListener() {
@Override
- public void onPrefetchCacheUpdated(
- ArraySet<JobStatus> jobs, int userId, String pkgName,
- long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
+ public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs,
+ int userId, String pkgName, long prevEstimatedLaunchTime,
+ long newEstimatedLaunchTime, long nowElapsed) {
onPrefetchCacheChangedCalled[0] = true;
}
};
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 7048fb2..9407968 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -70,7 +70,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
import android.os.BatteryManagerInternal;
import android.os.Handler;
import android.os.Looper;
@@ -385,7 +384,7 @@
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
- js.serviceInfo = mock(ServiceInfo.class);
+ js.serviceProcessName = "testProcess";
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index d7fef60..5b92706 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -58,8 +58,10 @@
void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
- void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
- TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration);
+ void onRegistrationReplaced(Consumer<TestListenerRegistration> oldConsumer,
+ TestListenerRegistration oldRegistration,
+ Consumer<TestListenerRegistration> newConsumer,
+ TestListenerRegistration newRegistration);
void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
@@ -93,10 +95,10 @@
assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
mMultiplexer.addListener(1, consumer);
- mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer),
- any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
- any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+ any(TestListenerRegistration.class),
+ eq(consumer),
+ any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -115,10 +117,10 @@
any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onActive();
mMultiplexer.replaceListener(1, oldConsumer, consumer);
- mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer),
+ mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(oldConsumer),
+ any(TestListenerRegistration.class),
+ eq(consumer),
any(TestListenerRegistration.class));
- mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
- any(TestListenerRegistration.class), any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -352,13 +354,19 @@
}
private static class TestListenerRegistration extends
- RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
+ ListenerRegistration<Consumer<TestListenerRegistration>> {
+ private final Integer mInteger;
boolean mActive = true;
protected TestListenerRegistration(Integer integer,
Consumer<TestListenerRegistration> consumer) {
- super(DIRECT_EXECUTOR, integer, consumer);
+ super(DIRECT_EXECUTOR, consumer);
+ mInteger = integer;
+ }
+
+ public Integer getInteger() {
+ return mInteger;
}
}
@@ -375,11 +383,6 @@
mCallbacks = callbacks;
}
- @Override
- public String getTag() {
- return "TestMultiplexer";
- }
-
public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
putRegistration(consumer, new TestListenerRegistration(request, consumer));
}
@@ -399,9 +402,9 @@
removeRegistration(consumer, registration);
}
- public void setActive(Integer request, boolean active) {
+ public void setActive(Integer integer, boolean active) {
updateRegistrations(testRegistration -> {
- if (testRegistration.getRequest().equals(request)) {
+ if (testRegistration.getInteger().equals(integer)) {
testRegistration.mActive = active;
return true;
}
@@ -458,10 +461,11 @@
}
@Override
- protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+ protected void onRegistrationReplaced(Consumer<TestListenerRegistration> oldKey,
TestListenerRegistration oldRegistration,
+ Consumer<TestListenerRegistration> newKey,
TestListenerRegistration newRegistration) {
- mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration);
+ mCallbacks.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
}
@Override
@@ -475,8 +479,8 @@
Collection<TestListenerRegistration> testRegistrations) {
int max = Integer.MIN_VALUE;
for (TestListenerRegistration registration : testRegistrations) {
- if (registration.getRequest() > max) {
- max = registration.getRequest();
+ if (registration.getInteger() > max) {
+ max = registration.getInteger();
}
}
mMergeCount++;
@@ -493,7 +497,7 @@
@Override
protected void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration) {
- addListener(registration.getRequest(), consumer);
+ addListener(registration.getInteger(), consumer);
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 71cc65b..0ac1443 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -32,7 +32,6 @@
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationUtils.createLocation;
import static com.android.server.location.LocationUtils.createLocationResult;
-import static com.android.server.location.listeners.RemoteListenerRegistration.IN_PROCESS_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
@@ -534,7 +533,7 @@
listener);
CountDownLatch blocker = new CountDownLatch(1);
- IN_PROCESS_EXECUTOR.execute(() -> {
+ FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
@@ -661,7 +660,7 @@
listener);
CountDownLatch blocker = new CountDownLatch(1);
- IN_PROCESS_EXECUTOR.execute(() -> {
+ FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
deleted file mode 100644
index d25649e..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
+++ /dev/null
@@ -1,117 +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.server.pm
-
-import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@RunWith(JUnit4::class)
-class BroadcastHelperTest {
-
- companion object {
- const val TEST_PACKAGE_1 = "com.android.test.package1"
- const val TEST_PACKAGE_2 = "com.android.test.package2"
- const val TEST_UID_1 = 10100
- const val TEST_UID_2 = 10101
- const val TEST_USER_ID = 0
- }
-
- lateinit var broadcastHelper: BroadcastHelper
- lateinit var packagesToChange: Array<String>
- lateinit var uidsToChange: IntArray
-
- @Mock
- lateinit var snapshot: Computer
-
- @Rule
- @JvmField
- val rule = MockSystemRule()
-
- @Before
- open fun setup() {
- MockitoAnnotations.initMocks(this)
- rule.system().stageNominalSystemState()
- broadcastHelper = BroadcastHelper(rule.mocks().injector)
- packagesToChange = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
- uidsToChange = intArrayOf(TEST_UID_1, TEST_UID_2)
- }
-
- @Test
- fun getBroadcastParams_withSameVisibilityAllowList_shouldGroup() {
- val allowList = intArrayOf(10001, 10002, 10003)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
- mockVisibilityAllowList(TEST_PACKAGE_2, allowList)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(1)
- assertThat(broadcastParams[0].packageNames).asList().containsExactlyElementsIn(
- packagesToChange.toCollection(ArrayList()))
- assertThat(broadcastParams[0].uids).asList().containsExactlyElementsIn(
- uidsToChange.toCollection(ArrayList()))
- }
-
- @Test
- fun getBroadcastParams_withDifferentVisibilityAllowList_shouldNotGroup() {
- val allowList1 = intArrayOf(10001, 10002, 10003)
- val allowList2 = intArrayOf(10001, 10002, 10007)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList1)
- mockVisibilityAllowList(TEST_PACKAGE_2, allowList2)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(2)
- broadcastParams.forEachIndexed { i, params ->
- val changedPackages = params.packageNames
- val changedUids = params.uids
- assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
- assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- fun getBroadcastParams_withNullVisibilityAllowList_shouldNotGroup() {
- val allowList = intArrayOf(10001, 10002, 10003)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
- mockVisibilityAllowList(TEST_PACKAGE_2, null)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(2)
- broadcastParams.forEachIndexed { i, params ->
- val changedPackages = params.packageNames
- val changedUids = params.uids
- assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
- assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
- }
- }
-
- private fun mockVisibilityAllowList(pkgName: String, list: IntArray?) {
- whenever(snapshot.getVisibilityAllowList(pkgName, TEST_USER_ID))
- .thenReturn(list ?: IntArray(0))
- }
-}
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index d8770e5..9f1cec3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -29,7 +29,6 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@RunWith(JUnit4::class)
@@ -53,7 +52,7 @@
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
@@ -78,7 +77,8 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(
eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
assertThat(unactionedPackages).isEmpty()
}
@@ -156,7 +156,7 @@
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -172,7 +172,7 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
}
@Test
@@ -183,7 +183,7 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
arrayOfNulls(0), TEST_USER_ID)
@@ -191,18 +191,17 @@
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
}
@Test
- fun sendDistractingPackagesChanged_withSameVisibilityAllowList() {
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
+ fun sendDistractingPackagesChanged() {
+ distractingPackageHelper.sendDistractingPackagesChanged(packagesToChange, uidsToChange,
+ TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -210,49 +209,4 @@
assertThat(changedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
}
-
- @Test
- fun sendDistractingPackagesChanged_withDifferentVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(
- intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- fun sendDistractingPackagesChanged_withNullVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index 5f9ef58..4ac5e86 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -28,7 +28,6 @@
import org.junit.Before
import org.junit.Rule
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
@@ -102,7 +101,6 @@
whenever(rule.mocks().userManagerService.hasUserRestriction(
eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
mockKnownPackages(pms)
- mockUnifiedSeparatedBroadcastList()
}
private fun mockKnownPackages(pms: PackageManagerService) {
@@ -139,26 +137,4 @@
rule.system().validateFinalState()
return pms
}
-
- protected fun mockUnifiedSeparatedBroadcastList() {
- whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
- any() as Array<String>, any(IntArray::class.java), anyInt()
- )).thenReturn(ArrayList<BroadcastParams>().apply {
- this.add(BroadcastParams(packagesToChange[0], uidsToChange[0], IntArray(0),
- TEST_USER_ID).apply {
- this.addPackage(packagesToChange[1], uidsToChange[1])
- })
- })
- }
-
- protected fun mockDividedSeparatedBroadcastList(allowlist1: IntArray?, allowlist2: IntArray?) {
- whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
- any() as Array<String>, any(IntArray::class.java), anyInt()
- )).thenReturn(ArrayList<BroadcastParams>().apply {
- this.add(BroadcastParams(packagesToChange[0], uidsToChange[0],
- allowlist1 ?: IntArray(0), TEST_USER_ID))
- this.add(BroadcastParams(packagesToChange[1], uidsToChange[1],
- allowlist2 ?: IntArray(0), TEST_USER_ID))
- })
- }
-}
\ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index df53dfe..dc74469 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -45,11 +45,13 @@
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable())
+ nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(),
+ nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable())
+ nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(),
+ nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -135,13 +137,13 @@
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -217,13 +219,13 @@
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
@@ -297,12 +299,13 @@
@Test
@Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
+ fun sendPackagesSuspendedForUser() {
+ suspendPackageHelper.sendPackagesSuspendedForUser(
Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -313,59 +316,14 @@
@Test
@Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(
- intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), any(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- @Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- @Throws(Exception::class)
fun sendPackagesSuspendModifiedForUser() {
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange,
- TEST_USER_ID)
+ suspendPackageHelper.sendPackagesSuspendedForUser(
+ Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(
eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index daa7d7b..b27f49d 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -30,6 +30,10 @@
<item res='@*android:color/profile_badge_2' />
</badge-colors>
<default-restrictions no_remove_user='true' no_bluetooth='true' />
+ <user-properties
+ showInLauncher='2020'
+ startWithParent='false'
+ />
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
index bce99a0..2b6be37 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -430,7 +430,8 @@
userId,
false /* allowBackgroundActivityStarts */,
null /* activityStartsToken */,
- false /* timeoutExempt */ );
+ false /* timeoutExempt */,
+ null /* filterExtrasForReceiver */);
}
private static int getAppId(int i) {
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 5be05d8..e8dd541 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -16,9 +16,13 @@
package com.android.server.app;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import android.app.GameManager;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.util.AtomicFile;
@@ -28,6 +32,9 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.app.GameManagerService.GamePackageConfiguration;
+import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
+
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +73,9 @@
+ " <package name=\"com.android.app1\" gameMode=\"1\">\n"
+ " </package>\n"
+ " <package name=\"com.android.app2\" gameMode=\"2\">\n"
+ + " <gameModeConfig gameMode=\"2\" scaling=\"0.99\" "
+ + "useAngle=\"true\" fps=\"90\" loadingBoost=\"123\"></gameModeConfig>\n"
+ + " <gameModeConfig gameMode=\"3\"></gameModeConfig>\n"
+ " </package>\n"
+ " <package name=\"com.android.app3\" gameMode=\"3\">\n"
+ " </package>\n"
@@ -92,40 +102,159 @@
writeGameServiceXml();
}
- private void verifyGameServiceSettingsData(GameManagerSettings settings) {
- assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
- assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
- assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
- }
-
@After
public void tearDown() throws Exception {
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
- /** read in data and verify */
@Test
public void testReadGameServiceSettings() {
- /* write out files and read */
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
- assertThat(settings.readPersistentDataLocked(), is(true));
- verifyGameServiceSettingsData(settings);
+ assertTrue(settings.readPersistentDataLocked());
+
+ // test game modes
+ assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1));
+ assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+ assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+ // test game mode configs
+ assertNull(settings.getConfigOverride(PACKAGE_NAME_1));
+ assertNull(settings.getConfigOverride(PACKAGE_NAME_3));
+ final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
+ assertNotNull(config);
+
+ assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
+ final GameModeConfiguration performanceConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_PERFORMANCE);
+ assertNotNull(performanceConfig);
+ assertEquals(performanceConfig.getScaling(), 0.99, 0.01f);
+ assertEquals(performanceConfig.getLoadingBoostDuration(), 123);
+ assertEquals(performanceConfig.getFpsStr(), "90");
+ assertTrue(performanceConfig.getUseAngle());
+ final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getScaling(), GameModeConfiguration.DEFAULT_SCALING, 0.01f);
+ assertEquals(batteryConfig.getLoadingBoostDuration(),
+ GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+ assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
+ assertFalse(batteryConfig.getUseAngle());
}
- /** read in data, write it out, and read it back in. Verify same. */
@Test
- public void testWriteGameServiceSettings() {
- // write out files and read
- writeOldFiles();
+ public void testReadGameServiceSettings_invalidConfigAttributes() {
+ writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "system/game-manager-service.xml"),
+ ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ + "<packages>\n"
+ + " <package name=\"com.android.app1\" gameMode=\"1\">\n"
+ + " <gameModeConfig gameMode=\"3\" scaling=\"invalid\" "
+ + "useAngle=\"invalid\" fps=\"invalid\" "
+ + "loadingBoost=\"invalid\"></gameModeConfig>\n"
+ + " </package>\n"
+ + "</packages>\n").getBytes());
final Context context = InstrumentationRegistry.getContext();
GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
- assertThat(settings.readPersistentDataLocked(), is(true));
+ assertTrue(settings.readPersistentDataLocked());
- // write out, read back in and verify the same
+ final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_1);
+ assertNotNull(config);
+ final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getScaling(), GameModeConfiguration.DEFAULT_SCALING, 0.01f);
+ assertEquals(batteryConfig.getLoadingBoostDuration(),
+ GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+ assertEquals(batteryConfig.getFpsStr(), "invalid");
+ assertFalse(batteryConfig.getUseAngle());
+ }
+
+ @Test
+ public void testReadGameServiceSettings_invalidTags() {
+ writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "system/game-manager-service.xml"),
+ ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ + "<packages>\n"
+ + " <package gameMode=\"1\">\n"
+ + " </package>\n"
+ + " <package name=\"com.android.app2\" gameMode=\"2\">\n"
+ + " <unknown></unknown>"
+ + " <gameModeConfig gameMode=\"3\" fps=\"90\"></gameModeConfig>\n"
+ + " foo bar"
+ + " </package>\n"
+ + " <unknownTag></unknownTag>\n"
+ + " foo bar\n"
+ + " <package name=\"com.android.app3\" gameMode=\"3\">\n"
+ + " </package>\n"
+ + "</packages>\n").getBytes());
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
+ assertTrue(settings.readPersistentDataLocked());
+ assertEquals(0, settings.getGameModeLocked(PACKAGE_NAME_1));
+ assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+ assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+ final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
+ assertNotNull(config);
+ final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getFpsStr(), "90");
+ }
+
+
+ @Test
+ public void testWriteGameServiceSettings() {
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
+
+ // set package settings and write out to file
+ settings.setGameModeLocked(PACKAGE_NAME_1, GameManager.GAME_MODE_BATTERY);
+ settings.setGameModeLocked(PACKAGE_NAME_2, GameManager.GAME_MODE_PERFORMANCE);
+ settings.setGameModeLocked(PACKAGE_NAME_3, GameManager.GAME_MODE_STANDARD);
+ GamePackageConfiguration config = new GamePackageConfiguration(PACKAGE_NAME_2);
+ GameModeConfiguration performanceConfig = config.getOrAddDefaultGameModeConfiguration(
+ GameManager.GAME_MODE_PERFORMANCE);
+ performanceConfig.setLoadingBoostDuration(321);
+ performanceConfig.setScaling(0.66f);
+ performanceConfig.setUseAngle(true);
+ performanceConfig.setFpsStr("60");
+ GameModeConfiguration batteryConfig = config.getOrAddDefaultGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ batteryConfig.setScaling(0.77f);
+ settings.setConfigOverride(PACKAGE_NAME_2, config);
settings.writePersistentDataLocked();
- assertThat(settings.readPersistentDataLocked(), is(true));
- verifyGameServiceSettingsData(settings);
+
+ // clear the settings in memory
+ settings.removeGame(PACKAGE_NAME_1);
+ settings.removeGame(PACKAGE_NAME_2);
+ settings.removeGame(PACKAGE_NAME_3);
+
+ // read back in and verify
+ assertTrue(settings.readPersistentDataLocked());
+ assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_1));
+ assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+ assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+ config = settings.getConfigOverride(PACKAGE_NAME_1);
+ assertNull(config);
+ config = settings.getConfigOverride(PACKAGE_NAME_2);
+ assertNotNull(config);
+ batteryConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getScaling(), 0.77f, 0.01f);
+ assertEquals(batteryConfig.getLoadingBoostDuration(),
+ GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+ assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
+ assertFalse(batteryConfig.getUseAngle());
+
+ performanceConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE);
+ assertNotNull(performanceConfig);
+ assertEquals(performanceConfig.getScaling(), 0.66f, 0.01f);
+ assertEquals(performanceConfig.getLoadingBoostDuration(), 321);
+ assertEquals(performanceConfig.getFpsStr(), "60");
+ assertTrue(performanceConfig.getUseAngle());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
new file mode 100644
index 0000000..903ed90
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.biometrics.sensors.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FaceServiceRegistryTest {
+
+ private static final int SENSOR_ID_1 = 1;
+ private static final int SENSOR_ID_2 = 2;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private IFaceService mFaceService;
+ @Mock
+ private ServiceProvider mProvider1;
+ @Mock
+ private ServiceProvider mProvider2;
+ @Captor
+ private ArgumentCaptor<Integer> mIdCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mStrengthCaptor;
+
+ private FaceSensorPropertiesInternal mProvider1Props;
+ private FaceSensorPropertiesInternal mProvider2Props;
+ private FaceServiceRegistry mRegistry;
+
+ @Before
+ public void setup() {
+ mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
+ STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FaceSensorProperties.TYPE_RGB,
+ true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
+ STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FaceSensorProperties.TYPE_IR,
+ true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+
+ when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+ when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+ when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+ when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+ mRegistry = new FaceServiceRegistry(mFaceService, () -> mBiometricService);
+ }
+
+ @Test
+ public void registersAllProviders() throws Exception {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+ assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+ verify(mBiometricService, times(2)).registerAuthenticator(
+ mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
+ assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+ assertThat(mStrengthCaptor.getAllValues())
+ .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void getsProviderById() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+ assertThat(mRegistry.getProviderForSensor(500)).isNull();
+ }
+
+ @Test
+ public void getsSingleProvider() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+ assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ }
+
+ @Test
+ public void getSingleProviderFindsFirstWhenMultiple() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+ }
+
+ @Test
+ public void registersListenerBeforeAllRegistered() {
+ final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+
+ @Test
+ public void registersListenerAfterAllRegistered() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ final List<FaceSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 5f88c99..0e30782 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -24,38 +26,73 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.BiometricServiceProvider;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.EnrollClient;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
@Presubmit
@SmallTest
public class BiometricStateCallbackTest {
- private BiometricStateCallback mCallback;
+ private static final int USER_ID = 10;
+ private static final int SENSOR_ID = 2;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ private BiometricStateCallback<FakeProvider, SensorPropertiesInternal> mCallback;
@Mock
- BiometricStateListener mBiometricStateListener;
+ private UserManager mUserManager;
+ @Mock
+ private BiometricStateListener mBiometricStateListener;
+ @Mock
+ private FakeProvider mFakeProvider;
+
+ private SensorPropertiesInternal mFakeProviderProps;
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
+ mFakeProviderProps = new SensorPropertiesInternal(SENSOR_ID, STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */, List.of(),
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ false /* resetLockoutRequiresChallenge */);
+ when(mFakeProvider.getSensorProperties()).thenReturn(List.of(mFakeProviderProps));
+ when(mFakeProvider.getSensorProperties(eq(SENSOR_ID))).thenReturn(mFakeProviderProps);
+ when(mFakeProvider.hasEnrollments(eq(SENSOR_ID), eq(USER_ID))).thenReturn(true);
+ when(mUserManager.getAliveUsers()).thenReturn(
+ List.of(new UserInfo(USER_ID, "name", 0)));
- mCallback = new BiometricStateCallback();
+ mCallback = new BiometricStateCallback<>(mUserManager);
mCallback.registerBiometricStateListener(mBiometricStateListener);
}
@Test
+ public void startNotifiesEnrollments() {
+ mCallback.start(List.of(mFakeProvider));
+
+ verify(mBiometricStateListener).onEnrollmentsChanged(eq(USER_ID), eq(SENSOR_ID), eq(true));
+ }
+
+ @Test
public void testNoEnrollmentsToEnrollments_callbackNotified() {
testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
true /* expectCallback */, true /* expectedCallbackValue */);
@@ -102,4 +139,6 @@
verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
anyBoolean());
}
+
+ private interface FakeProvider extends BiometricServiceProvider<SensorPropertiesInternal> {}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
new file mode 100644
index 0000000..13c3f64
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.biometrics.sensors.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.hardware.fingerprint.IFingerprintService;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FingerprintServiceRegistryTest {
+
+ private static final int SENSOR_ID_1 = 1;
+ private static final int SENSOR_ID_2 = 2;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private IFingerprintService mFingerprintService;
+ @Mock
+ private ServiceProvider mProvider1;
+ @Mock
+ private ServiceProvider mProvider2;
+ @Captor
+ private ArgumentCaptor<Integer> mIdCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mStrengthCaptor;
+
+ private FingerprintSensorPropertiesInternal mProvider1Props;
+ private FingerprintSensorPropertiesInternal mProvider2Props;
+ private FingerprintServiceRegistry mRegistry;
+
+ @Before
+ public void setup() {
+ mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
+ STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
+ STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+
+ when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
+ when(mProvider1.containsSensor(eq(SENSOR_ID_1))).thenReturn(true);
+ when(mProvider2.getSensorProperties()).thenReturn(List.of(mProvider2Props));
+ when(mProvider2.containsSensor(eq(SENSOR_ID_2))).thenReturn(true);
+ mRegistry = new FingerprintServiceRegistry(mFingerprintService, () -> mBiometricService);
+ }
+
+ @Test
+ public void registersAllProviders() throws Exception {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
+ assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
+ verify(mBiometricService, times(2)).registerAuthenticator(
+ mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
+ assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+ assertThat(mStrengthCaptor.getAllValues())
+ .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void getsProviderById() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_2)).isSameInstanceAs(mProvider2);
+ assertThat(mRegistry.getProviderForSensor(500)).isNull();
+ }
+
+ @Test
+ public void getsSingleProvider() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1));
+
+ assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+ assertThat(mRegistry.getProviders()).containsExactly(mProvider1);
+ assertThat(mRegistry.getProviderForSensor(SENSOR_ID_1)).isSameInstanceAs(mProvider1);
+ }
+
+ @Test
+ public void getSingleProviderFindsFirstWhenMultiple() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(mRegistry.getSingleProvider().second).isSameInstanceAs(mProvider1);
+ }
+
+ @Test
+ public void registersListenerBeforeAllRegistered() {
+ final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+
+ @Test
+ public void registersListenerAfterAllRegistered() {
+ mRegistry.registerAllInBackground(() -> List.of(mProvider1, mProvider2));
+
+ final List<FingerprintSensorPropertiesInternal> all = new ArrayList<>();
+ mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ all.addAll(sensors);
+ }
+ });
+
+ assertThat(all).containsExactly(mProvider1Props, mProvider2Props);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index ca3677e..a4048a2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -32,7 +32,8 @@
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
-import android.os.RemoteException;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.testing.TestableContext;
@@ -52,6 +53,8 @@
import org.mockito.junit.MockitoRule;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@Presubmit
@SmallTest
@@ -94,9 +97,12 @@
mContext.getTestablePermissions().setPermission(
USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
+ }
+ private void initServiceWith(String... aidlInstances) {
mService = new FingerprintService(mContext, mBiometricContext,
() -> mIBiometricService,
+ () -> aidlInstances,
(fqName) -> {
if (fqName.endsWith(NAME_DEFAULT)) return mIFingerprintDefault;
if (fqName.endsWith(NAME_VIRTUAL)) return mIFingerprintVirtual;
@@ -105,29 +111,50 @@
}
@Test
- public void registerAuthenticators_defaultOnly() throws RemoteException {
- mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+ public void registerAuthenticators_defaultOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
}
@Test
- public void registerAuthenticators_virtualOnly() throws RemoteException {
+ public void registerAuthenticators_virtualOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mService.registerAuthenticatorsForService(List.of(NAME_DEFAULT, NAME_VIRTUAL), List.of());
+ mService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
@Test
- public void registerAuthenticators_virtualAlwaysWhenNoOther() throws RemoteException {
- mService.registerAuthenticatorsForService(List.of(NAME_VIRTUAL), List.of());
+ public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
+ initServiceWith(NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
+ private void waitForRegistration() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+ new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
+ @Override
+ public void onAllAuthenticatorsRegistered(
+ List<FingerprintSensorPropertiesInternal> sensors) {
+ latch.countDown();
+ }
+ });
+ latch.await(5, TimeUnit.SECONDS);
+ }
+
private static SensorProps createProps(int id, byte strength, byte type) {
final SensorProps props = new SensorProps();
props.commonProps = new CommonProps();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index e2c3a94..4c939f0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -83,6 +83,7 @@
VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
/* activityListener= */ null,
/* activityBlockedCallback= */ null,
+ /* secureWindowCallback= */ null,
/* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index c4435b7..f69c5c2 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -63,7 +63,7 @@
BrightnessEvent secondBrightnessEvent = new BrightnessEvent(1);
secondBrightnessEvent.copyFrom(mBrightnessEvent);
secondBrightnessEvent.setTime(0);
- assertEquals(secondBrightnessEvent.equalsMainData(mBrightnessEvent), true);
+ assertEquals(true, secondBrightnessEvent.equalsMainData(mBrightnessEvent));
}
@Test
@@ -74,9 +74,28 @@
+ " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
+ " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
+ " [ low_pwr ], autoBrightness=true";
- assertEquals(actualString, expectedString);
+ assertEquals(expectedString, actualString);
}
+ @Test
+ public void testFlagsToString() {
+ mBrightnessEvent.reset();
+ mBrightnessEvent.setFlags(mBrightnessEvent.getFlags() | BrightnessEvent.FLAG_IDLE_CURVE);
+ String actualString = mBrightnessEvent.flagsToString();
+ String expectedString = "idle_curve ";
+ assertEquals(expectedString, actualString);
+ }
+
+ @Test
+ public void testFlagsToString_multipleFlags() {
+ mBrightnessEvent.reset();
+ mBrightnessEvent.setFlags(mBrightnessEvent.getFlags()
+ | BrightnessEvent.FLAG_IDLE_CURVE
+ | BrightnessEvent.FLAG_LOW_POWER_MODE);
+ String actualString = mBrightnessEvent.flagsToString();
+ String expectedString = "idle_curve low_power_mode ";
+ assertEquals(expectedString, actualString);
+ }
private BrightnessReason getReason(int reason, int modifier) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 0f6addb..fe9e0b6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -47,7 +47,6 @@
import org.junit.runners.JUnit4;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.concurrent.TimeUnit;
@SmallTest
@@ -79,6 +78,7 @@
private TestLooper mTestLooper = new TestLooper();
private FakePowerManagerWrapper mPowerManager;
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+ private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>();
private int mPlaybackPhysicalAddress;
private int mPlaybackLogicalAddress;
private boolean mWokenUp;
@@ -91,9 +91,11 @@
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
+ mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
+ mLocalDeviceTypes, new FakeAudioDeviceVolumeManagerWrapper()) {
+
@Override
void wakeUp() {
mWokenUp = true;
@@ -121,16 +123,6 @@
}
@Override
- boolean isPowerStandby() {
- return false;
- }
-
- @Override
- boolean isPowerStandbyOrTransient() {
- return false;
- }
-
- @Override
boolean canGoToStandby() {
return true;
}
@@ -165,6 +157,7 @@
mNativeWrapper.clearResultMessages();
mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
HdmiProperties.playback_device_action_on_routing_control_values.NONE;
+ mHdmiControlService.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
}
@Test
@@ -1199,6 +1192,9 @@
HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
mPlaybackPhysicalAddress);
mHdmiCecLocalDevicePlayback.dispatchMessage(setStreamPath);
+ // The ActiveSourceAction created from the message above is deferred until the device wakes
+ // up.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
HdmiCecMessage activeSource =
HdmiCecMessageBuilder.buildActiveSource(
@@ -2088,4 +2084,111 @@
assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
.isEmpty();
}
+
+ @Test
+ public void handleRoutingChange_addressNotAllocated_removeActiveSourceAction() {
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecLocalDevicePlayback.mPlaybackDeviceActionOnRoutingControl =
+ HdmiProperties
+ .playback_device_action_on_routing_control_values
+ .WAKE_UP_AND_SEND_ACTIVE_SOURCE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ HdmiCecMessage routingChangeToPlayback =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ mPlaybackPhysicalAddress);
+ HdmiCecMessage routingChangeToTv =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, mPlaybackPhysicalAddress,
+ 0x0000);
+ HdmiCecMessage unexpectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ // 1. DUT goes to sleep.
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ // Delay allocate logical address in order to trigger message buffering.
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mNativeWrapper.onCecMessage(routingChangeToPlayback);
+ mTestLooper.dispatchAll();
+ // 2. DUT wakes up and defer ActiveSourceAction.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+ // 3. DUT buffers <Routing Change> message to TV.
+ mNativeWrapper.onCecMessage(routingChangeToTv);
+ mTestLooper.dispatchAll();
+ // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+ // No <Active Source> message is sent by the DUT.
+ mTestLooper.moveTimeForward(allocationDelay);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+ }
+
+ @Test
+ public void handleSetStreamPath_addressNotAllocated_removeActiveSourceAction() {
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ HdmiCecMessage setStreamPathToPlayback =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+ HdmiCecMessage setStreamPathToTv =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x0000);
+ HdmiCecMessage unexpectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ // 1. DUT goes to sleep.
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ // Delay allocate logical address in order to trigger message buffering.
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mNativeWrapper.onCecMessage(setStreamPathToPlayback);
+ mTestLooper.dispatchAll();
+ // 2. DUT wakes up and defer ActiveSourceAction.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+ // 3. DUT buffers <Set Stream Path> message to TV.
+ mNativeWrapper.onCecMessage(setStreamPathToTv);
+ mTestLooper.dispatchAll();
+ // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+ // No <Active Source> message is sent by the DUT.
+ mTestLooper.moveTimeForward(allocationDelay);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+ }
+
+ @Test
+ public void handleActiveSource_addressNotAllocated_removeActiveSourceAction() {
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+
+ HdmiCecMessage setStreamPathToPlayback =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, mPlaybackPhysicalAddress);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage unexpectedMessage =
+ HdmiCecMessageBuilder.buildActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress);
+ // 1. DUT goes to sleep.
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ // Delay allocate logical address in order to trigger message buffering.
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mNativeWrapper.onCecMessage(setStreamPathToPlayback);
+ mTestLooper.dispatchAll();
+ // 2. DUT wakes up and defer ActiveSourceAction.
+ mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isNotEmpty();
+ // 3. DUT buffers <Active Source> message from TV.
+ mNativeWrapper.onCecMessage(activeSourceFromTv);
+ mTestLooper.dispatchAll();
+ // 4. Allocation is finished and the ActiveSourceAction is removed from the queue.
+ // No <Active Source> message is sent by the DUT.
+ mTestLooper.moveTimeForward(allocationDelay);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActions(ActiveSourceAction.class)).isEmpty();
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpectedMessage);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/input/OWNERS b/services/tests/servicestests/src/com/android/server/input/OWNERS
index d701f23..6e9aa1d 100644
--- a/services/tests/servicestests/src/com/android/server/input/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/input/OWNERS
@@ -1 +1,2 @@
-include /core/java/android/hardware/input/OWNERS
+include /services/core/java/com/android/server/input/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index a4cccb3..3477288 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -369,6 +369,8 @@
throws Exception {
final LockscreenCredential parentPassword = newPassword("parentPassword");
final LockscreenCredential profilePassword = newPattern("12345");
+ mService.setSeparateProfileChallengeEnabled(
+ MANAGED_PROFILE_USER_ID, true, profilePassword);
initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
// Create and verify separate profile credentials.
testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
@@ -550,11 +552,12 @@
throws RemoteException {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
synchronized (mService.mSpManager) {
- mService.initializeSyntheticPasswordLocked(credential, userId);
+ mService.initializeSyntheticPasswordLocked(userId);
}
if (credential.isNone()) {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
} else {
+ assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 34b40c7..b1ad8ec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -16,53 +16,79 @@
package com.android.server.pm;
+import static android.os.UserManager.DISALLOW_USER_SWITCH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.FileUtils;
+import android.os.Looper;
import android.os.Parcelable;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
import android.support.test.uiautomator.UiDevice;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
+/** Test {@link UserManagerService} functionality. */
@Postsubmit
-@SmallTest
-public class UserManagerServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class UserManagerServiceTest {
private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["};
private File restrictionsFile;
private int tempUserId = UserHandle.USER_NULL;
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private UserManagerService mUserManagerService;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setup() throws Exception {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ // Disable binder caches in this process.
+ PropertyInvalidatedCache.disableForTestMode();
+
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+
restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
restrictionsFile.delete();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void teardown() throws Exception {
restrictionsFile.delete();
if (tempUserId != UserHandle.USER_NULL) {
UserManager.get(mContext).removeUser(tempUserId);
}
- super.tearDown();
}
+ @Test
public void testWriteReadApplicationRestrictions() throws IOException {
AtomicFile atomicFile = new AtomicFile(restrictionsFile);
Bundle bundle = createBundle();
UserManagerService.writeApplicationRestrictionsLAr(bundle, atomicFile);
- assertTrue(atomicFile.getBaseFile().exists());
+ assertThat(atomicFile.getBaseFile().exists()).isTrue();
String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
System.out.println("restrictionsFile: " + s);
bundle = UserManagerService.readApplicationRestrictionsLAr(atomicFile);
@@ -70,22 +96,22 @@
assertBundle(bundle);
}
+ @Test
public void testAddUserWithAccount() {
UserManager um = UserManager.get(mContext);
UserInfo user = um.createUser("Test User", 0);
- assertNotNull(user);
+ assertThat(user).isNotNull();
tempUserId = user.id;
String accountName = "Test Account";
um.setUserAccount(tempUserId, accountName);
- assertEquals(accountName, um.getUserAccount(tempUserId));
+ assertThat(um.getUserAccount(tempUserId)).isEqualTo(accountName);
}
+ @Test
public void testUserSystemPackageWhitelist() throws Exception {
String cmd = "cmd user report-system-user-package-whitelist-problems --critical-only";
final String result = runShellCommand(cmd);
- if (!TextUtils.isEmpty(result)) {
- fail("Command '" + cmd + " reported errors:\n" + result);
- }
+ assertThat(result).isEmpty();
}
private Bundle createBundle() {
@@ -114,26 +140,141 @@
}
private void assertBundle(Bundle bundle) {
- assertFalse(bundle.getBoolean("boolean_0"));
- assertTrue(bundle.getBoolean("boolean_1"));
- assertEquals(100, bundle.getInt("integer"));
- assertEquals("", bundle.getString("empty"));
- assertEquals("text", bundle.getString("string"));
- assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]")));
+ assertThat(bundle.getBoolean("boolean_0")).isFalse();
+ assertThat(bundle.getBoolean("boolean_1")).isTrue();
+ assertThat(bundle.getInt("integer")).isEqualTo(100);
+ assertThat(bundle.getString("empty")).isEqualTo("");
+ assertThat(bundle.getString("string")).isEqualTo("text");
+ assertThat(Arrays.asList(bundle.getStringArray("string[]")))
+ .isEqualTo(Arrays.asList(STRING_ARRAY));
Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array");
- assertEquals(2, bundle_array.length);
+ assertThat(bundle_array.length).isEqualTo(2);
Bundle bundle1 = (Bundle) bundle_array[0];
- assertEquals("bundle_array_string", bundle1.getString("bundle_array_string"));
- assertNotNull(bundle1.getBundle("bundle_array_bundle"));
+ assertThat(bundle1.getString("bundle_array_string"))
+ .isEqualTo("bundle_array_string");
+ assertThat(bundle1.getBundle("bundle_array_bundle")).isNotNull();
Bundle bundle2 = (Bundle) bundle_array[1];
- assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2"));
+ assertThat(bundle2.getString("bundle_array_string2"))
+ .isEqualTo("bundle_array_string2");
Bundle childBundle = bundle.getBundle("bundle");
- assertEquals("bundle_string", childBundle.getString("bundle_string"));
- assertEquals(1, childBundle.getInt("bundle_int"));
+ assertThat(childBundle.getString("bundle_string"))
+ .isEqualTo("bundle_string");
+ assertThat(childBundle.getInt("bundle_int")).isEqualTo(1);
}
+ @Test
+ public void assertHasUserRestriction() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId);
+ assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isTrue();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+ assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)).isFalse();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnMultiUserSettings() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ resetUserSwitcherEnabled();
+
+ setUserSwitch(false);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setUserSwitch(true);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnMaxSupportedUsers() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ setMaxSupportedUsers(1);
+
+ assertThat(UserManager.supportsMultipleUsers()).isFalse();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setMaxSupportedUsers(8);
+
+ assertThat(UserManager.supportsMultipleUsers()).isTrue();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnShowMultiuserUI() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ setShowMultiuserUI(false);
+
+ assertThat(UserManager.supportsMultipleUsers()).isFalse();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setShowMultiuserUI(true);
+
+ assertThat(UserManager.supportsMultipleUsers()).isTrue();
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnUserRestrictions() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ resetUserSwitcherEnabled();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, userId);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ @Test
+ public void assertIsUserSwitcherEnabledOnDemoMode() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ resetUserSwitcherEnabled();
+
+ setDeviceDemoMode(true);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isFalse();
+
+ setDeviceDemoMode(false);
+ assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
+ }
+
+ private void resetUserSwitcherEnabled() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+ setUserSwitch(true);
+ setShowMultiuserUI(true);
+ setDeviceDemoMode(false);
+ setMaxSupportedUsers(8);
+ mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, false, userId);
+ }
+
+ private void setUserSwitch(boolean enabled) {
+ android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.USER_SWITCHER_ENABLED, enabled ? 1 : 0);
+ }
+
+ private void setDeviceDemoMode(boolean enabled) {
+ android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.DEVICE_DEMO_MODE, enabled ? 1 : 0);
+ }
+
+
private static String runShellCommand(String cmd) throws Exception {
return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.executeShellCommand(cmd);
}
+
+ private static String setSystemProperty(String name, String value) throws Exception {
+ final String oldValue = runShellCommand("getprop " + name);
+ assertThat(runShellCommand("setprop " + name + " " + value))
+ .isEqualTo("");
+ return oldValue;
+ }
+
+ private static void setMaxSupportedUsers(int max) throws Exception {
+ setSystemProperty("fw.max_users", String.valueOf(max));
+ }
+
+ public static void setShowMultiuserUI(boolean show) throws Exception {
+ setSystemProperty("fw.show_multiuserui", String.valueOf(show));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
new file mode 100644
index 0000000..13a7a3e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.UserProperties;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Supplier;
+
+/**
+ * Tests for UserManager's {@link UserProperties}.
+ *
+ * Additional test coverage (that actually exercises the functionality) can be found in
+ * {@link UserManagerTest} and
+ * {@link UserManagerServiceUserTypeTest} (for {@link UserProperties#updateFromXml}).
+ *
+ * <p>Run with: atest UserManagerServiceUserPropertiesTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceUserPropertiesTest {
+
+ /** Test that UserProperties can properly read the xml information that it writes. */
+ @Test
+ public void testWriteReadXml() throws Exception {
+ final UserProperties defaultProps = new UserProperties.Builder()
+ .setShowInLauncher(21)
+ .setStartWithParent(false)
+ .build();
+ final UserProperties actualProps = new UserProperties(defaultProps);
+ actualProps.setShowInLauncher(14);
+
+ // Write the properties to xml.
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(baos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, "testTag");
+ actualProps.writeToXml(out);
+ out.endTag(null, "testTag");
+ out.endDocument();
+
+ // Now read those properties from xml.
+ final ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(input, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ final UserProperties readProps = new UserProperties(parser, defaultProps);
+
+ assertUserPropertiesEquals(actualProps, readProps);
+ }
+
+ /** Tests parcelling an object in which all properties are present. */
+ @Test
+ public void testParcelUnparcel() throws Exception {
+ final UserProperties originalProps = new UserProperties.Builder()
+ .setShowInLauncher(2145)
+ .build();
+ final UserProperties readProps = parcelThenUnparcel(originalProps);
+ assertUserPropertiesEquals(originalProps, readProps);
+ }
+
+ /** Tests copying a UserProperties object varying permissions. */
+ @Test
+ public void testCopyLacksPermissions() throws Exception {
+ final UserProperties defaultProps = new UserProperties.Builder()
+ .setShowInLauncher(2145)
+ .setStartWithParent(true)
+ .build();
+ final UserProperties orig = new UserProperties(defaultProps);
+ orig.setShowInLauncher(2841);
+ orig.setStartWithParent(false);
+
+ // Test every permission level. (Currently, it's linear so it's easy.)
+ for (int permLevel = 0; permLevel < 4; permLevel++) {
+ final boolean exposeAll = permLevel >= 3;
+ final boolean hasManage = permLevel >= 2;
+ final boolean hasQuery = permLevel >= 1;
+
+ // Make a possibly-not-full-permission (i.e. partial) copy and check that it is correct.
+ final UserProperties copy = new UserProperties(orig, exposeAll, hasManage, hasQuery);
+ verifyTestCopyLacksPermissions(orig, copy, exposeAll, hasManage, hasQuery);
+ if (permLevel < 1) {
+ // PropertiesPresent should definitely be different since not all items were copied.
+ assertThat(orig.getPropertiesPresent()).isNotEqualTo(copy.getPropertiesPresent());
+ }
+
+ // Now, just like in the SystemServer, parcel/unparcel the copy and make sure that the
+ // unparcelled version behaves just like the partial copy did.
+ final UserProperties readProps = parcelThenUnparcel(copy);
+ verifyTestCopyLacksPermissions(orig, readProps, exposeAll, hasManage, hasQuery);
+ }
+ }
+
+ /**
+ * Verifies that the copy of orig has the expected properties
+ * for the test {@link #testCopyLacksPermissions}.
+ */
+ private void verifyTestCopyLacksPermissions(
+ UserProperties orig,
+ UserProperties copy,
+ boolean exposeAll,
+ boolean hasManagePermission,
+ boolean hasQueryPermission) {
+
+ // Items requiring exposeAll.
+ assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
+
+ // Items requiring hasManagePermission - put them here using hasManagePermission.
+ // Items requiring hasQueryPermission - put them here using hasQueryPermission.
+
+ // Items with no permission requirements.
+ assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+ }
+
+ /**
+ * If hasPerm, then asserts that value of actualGetter equals value of expectedGetter.
+ * If !hasPerm, then asserts that actualGetter throws a SecurityException.
+ */
+ @SuppressWarnings("ReturnValueIgnored")
+ private void assertEqualGetterOrThrows(
+ Supplier expectedGetter,
+ Supplier actualGetter,
+ boolean hasPerm) {
+ if (hasPerm) {
+ assertThat(expectedGetter.get()).isEqualTo(actualGetter.get());
+ } else {
+ assertThrows(SecurityException.class, actualGetter::get);
+ }
+ }
+
+ private UserProperties parcelThenUnparcel(UserProperties originalProps) {
+ final Parcel out = Parcel.obtain();
+ originalProps.writeToParcel(out, 0);
+ final byte[] data = out.marshall();
+ out.recycle();
+
+ final Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ final UserProperties readProps = UserProperties.CREATOR.createFromParcel(in);
+ in.recycle();
+
+ return readProps;
+ }
+
+ /** Checks that two UserProperties get the same values. */
+ private void assertUserPropertiesEquals(UserProperties expected, UserProperties actual) {
+ assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
+ assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
+ assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 971b036..5f48004 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -35,6 +35,7 @@
import static org.testng.Assert.assertThrows;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
@@ -81,6 +82,8 @@
DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
/* flags= */0,
/* letsPersonalDataIntoProfile= */false).build());
+ final UserProperties.Builder userProps = new UserProperties.Builder()
+ .setShowInLauncher(17);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
.setEnabled(1)
@@ -98,6 +101,7 @@
.setDefaultSystemSettings(systemSettings)
.setDefaultSecureSettings(secureSettings)
.setDefaultCrossProfileIntentFilters(filters)
+ .setDefaultUserProperties(userProps)
.createUserTypeDetails();
assertEquals("a.name", type.getName());
@@ -135,6 +139,8 @@
assertEquals(filters.get(i), type.getDefaultCrossProfileIntentFilters().get(i));
}
+ assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
assertEquals(25, type.getBadgeLabel(2));
@@ -173,6 +179,11 @@
assertTrue(type.getDefaultSecureSettings().isEmpty());
assertTrue(type.getDefaultCrossProfileIntentFilters().isEmpty());
+ final UserProperties props = type.getDefaultUserPropertiesReference();
+ assertNotNull(props);
+ assertFalse(props.getStartWithParent());
+ assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
+
assertFalse(type.hasBadge());
}
@@ -250,19 +261,24 @@
// Mock some "AOSP defaults".
final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+ final UserProperties.Builder props = new UserProperties.Builder()
+ .setShowInLauncher(19)
+ .setStartWithParent(true);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(31)
- .setDefaultRestrictions(restrictions));
+ .setDefaultRestrictions(restrictions)
+ .setDefaultUserProperties(props));
builders.put(userTypeAosp2, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(32)
.setIconBadge(401)
.setBadgeColors(402, 403, 404)
- .setDefaultRestrictions(restrictions));
+ .setDefaultRestrictions(restrictions)
+ .setDefaultUserProperties(props));
final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_profile);
UserTypeFactory.customizeBuilders(builders, parser);
@@ -272,6 +288,8 @@
assertEquals(31, aospType.getMaxAllowedPerParent());
assertEquals(Resources.ID_NULL, aospType.getIconBadge());
assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
+ assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -300,6 +318,8 @@
assertTrue(UserRestrictionsUtils.areEqual(
makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
aospType.getDefaultRestrictions()));
+ assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5d48501..f567e80 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -31,6 +31,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
@@ -564,6 +565,33 @@
assertThat(userManagerForUser.isProfile()).isEqualTo(userTypeDetails.isProfile());
}
+ /** Test that UserManager returns the correct UserProperties for a new managed profile. */
+ @MediumTest
+ @Test
+ public void testUserProperties() throws Exception {
+ assumeManagedUsersSupported();
+
+ // Get the default properties for a user type.
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_MANAGED);
+ assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_MANAGED)
+ .that(userTypeDetails).isNotNull();
+ final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
+ // Create an actual user (of this user type) and get its properties.
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ final UserInfo userInfo = createProfileForUser("Managed",
+ UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
+ assertThat(userInfo).isNotNull();
+ final int userId = userInfo.id;
+ final UserProperties userProps = mUserManager.getUserProperties(UserHandle.of(userId));
+
+ // Check that this new user has the expected properties (relative to the defaults)
+ // provided that the test caller has the necessary permissions.
+ assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+ assertThrows(SecurityException.class, userProps::getStartWithParent);
+ }
+
// Make sure only max managed profiles can be created
@MediumTest
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 36e988f..3848bab 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -381,6 +381,7 @@
assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
Temperature.TYPE_SKIN)).size());
assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus());
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(0)));
}
@Test
@@ -397,6 +398,14 @@
}
@Test
+ public void testGetThermalHeadroomInputRange() throws RemoteException {
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+ ThermalManagerService.MIN_FORECAST_SEC - 1)));
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+ ThermalManagerService.MAX_FORECAST_SEC + 1)));
+ }
+
+ @Test
public void testTemperatureWatcherUpdateSevereThresholds() throws RemoteException {
ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
watcher.mSevereThresholds.erase();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 911fb6a..08c2c6e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -1301,6 +1301,21 @@
}
@Test
+ public void testA11yCrossUserEventNotSent() throws Exception {
+ final Notification n = new Builder(getContext(), "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ int userId = mUser.getIdentifier() + 1;
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
+ mPid, n, UserHandle.of(userId), null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn,
+ new NotificationChannel("test", "test", IMPORTANCE_HIGH));
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testLightsScreenOn() {
mService.mScreenOn = true;
NotificationRecord r = getLightsNotification();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 44ca9f4..ec48e23 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -150,6 +150,7 @@
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
+import android.media.IRingtonePlayer;
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Binder;
@@ -4238,6 +4239,59 @@
}
@Test
+ public void testSubstituteAppName_hasPermission() throws RemoteException {
+ String subName = "Substitute Name";
+ when(mPackageManager.checkPermission(
+ eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
+ .thenReturn(PERMISSION_GRANTED);
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName);
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .addExtras(extras);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testSubstituteAppNamePermission", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationRecord posted = mService.findNotificationLocked(
+ PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+ assertTrue(posted.getNotification().extras
+ .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME));
+ assertEquals(posted.getNotification().extras
+ .getString(Notification.EXTRA_SUBSTITUTE_APP_NAME), subName);
+ }
+
+ @Test
+ public void testSubstituteAppName_noPermission() throws RemoteException {
+ when(mPackageManager.checkPermission(
+ eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
+ .thenReturn(PERMISSION_DENIED);
+ Bundle extras = new Bundle();
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Substitute Name");
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .addExtras(extras);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testSubstituteAppNamePermission", mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationRecord posted = mService.findNotificationLocked(
+ PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+ assertFalse(posted.getNotification().extras
+ .containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME));
+ }
+
+ @Test
public void testGetNotificationCountLocked() {
String sampleTagToExclude = null;
int sampleIdToExclude = 0;
@@ -7738,6 +7792,34 @@
}
@Test
+ public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped()
+ throws RemoteException {
+ IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
+ when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
+ // Set up volume to be above 0 for the sound to actually play
+ when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+
+ setUpPrefsForBubbles(PKG, mUid,
+ true /* global */,
+ BUBBLE_PREFERENCE_ALL /* app */,
+ true /* channel */);
+
+ // Post a bubble notification
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Test: suppress notification via bubble metadata update
+ mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(),
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ waitForIdle();
+
+ // Check audio is stopped
+ verify(mockPlayer).stopAsync();
+ }
+
+ @Test
public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
int userId = UserManager.isHeadlessSystemUserMode()
? UserHandle.getUserId(UID_HEADLESS)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 598a22b..d62ac99 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -93,6 +93,7 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.Parcel;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -2447,6 +2448,35 @@
}
@Test
+ public void testGetNotificationChannelGroup() throws Exception {
+ NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
+ NotificationChannel base =
+ new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
+ base.setGroup("not");
+ NotificationChannel convo =
+ new NotificationChannel("convo", "belongs to notDeleted", IMPORTANCE_DEFAULT);
+ convo.setGroup("not");
+ convo.setConversationId("not deleted", "banana");
+
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, base, true, false);
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, convo, true, false);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, notDeleted, true);
+
+ NotificationChannelGroup g
+ = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1);
+ Parcel parcel = Parcel.obtain();
+ g.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ NotificationChannelGroup g2
+ = mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG_N_MR1, UID_N_MR1);
+ Parcel parcel2 = Parcel.obtain();
+ g2.writeToParcel(parcel2, 0);
+ parcel2.setDataPosition(0);
+ }
+
+ @Test
public void testOnUserRemoved() throws Exception {
int[] user0Uids = {98, 235, 16, 3782};
int[] user1Uids = new int[user0Uids.length];
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 7415460..f61effa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -474,7 +474,7 @@
}
@Test
- public void testActivityRecordReparentToTaskFragment() {
+ public void testActivityRecordReparentedToTaskFragment() {
final ActivityRecord activity = createActivityRecord(mDc);
final SurfaceControl activityLeash = mock(SurfaceControl.class);
doNothing().when(activity).setDropInputMode(anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7a73f08..7244d94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1916,10 +1916,10 @@
testPlayer.start();
assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
assertNotNull(testPlayer.mLastReady);
- assertEquals(dc, DisplayRotation.getDisplayFromTransition(testPlayer.mLastTransit));
WindowContainerToken dcToken = dc.mRemoteToken.toWindowContainerToken();
assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
testPlayer.mLastReady.getChange(dcToken).getStartRotation());
+ assertTrue(testPlayer.mLastTransit.applyDisplayChangeIfNeeded());
testPlayer.finish();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index e502f2f..d400a4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -56,6 +56,7 @@
private boolean mHasWallpaperBackground = false;
private int mBlurRadius = 0;
private float mDarkScrimAlpha = 0.5f;
+ private SurfaceControl mParentSurface = mock(SurfaceControl.class);
@Before
public void setUp() throws Exception {
@@ -63,7 +64,8 @@
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
() -> mAreCornersRounded, () -> Color.valueOf(mColor),
() -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
- /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {});
+ /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {},
+ () -> mParentSurface);
mTransaction = spy(StubTransaction.class);
}
@@ -205,6 +207,22 @@
}
@Test
+ public void testNeedsApplySurfaceChanges_setParentSurface() {
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ mLetterbox.applySurfaceChanges(mTransaction);
+
+ verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
+ assertFalse(mLetterbox.needsApplySurfaceChanges());
+
+ mParentSurface = mock(SurfaceControl.class);
+
+ assertTrue(mLetterbox.needsApplySurfaceChanges());
+
+ mLetterbox.applySurfaceChanges(mTransaction);
+ verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
+ }
+
+ @Test
public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() {
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
mLetterbox.applySurfaceChanges(mTransaction);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 8b3cff8..da72030 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -158,7 +158,7 @@
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentAppeared(any());
+ verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
// Send callback when the TaskFragment is attached.
setupMockParent(mTaskFragment, mTask);
@@ -166,7 +166,7 @@
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentAppeared(any());
+ verify(mOrganizer).onTaskFragmentAppeared(any(), any());
}
@Test
@@ -179,13 +179,13 @@
mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Call onTaskFragmentAppeared first.
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentAppeared(any());
+ verify(mOrganizer).onTaskFragmentAppeared(any(), any());
// No callback if the info is not changed.
doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
@@ -195,7 +195,7 @@
mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Trigger callback if the info is changed.
doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
@@ -204,7 +204,7 @@
mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(mTaskFragmentInfo);
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), eq(mTaskFragmentInfo));
}
@Test
@@ -215,7 +215,7 @@
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentVanished(any());
+ verify(mOrganizer).onTaskFragmentVanished(any(), any());
}
@Test
@@ -228,10 +228,10 @@
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentAppeared(any());
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
- verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
- verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+ verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
+ verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
// Not trigger onTaskFragmentInfoChanged.
// Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged.
@@ -244,10 +244,10 @@
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentAppeared(any());
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
- verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
- verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+ verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
+ verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
}
@Test
@@ -260,7 +260,7 @@
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
// No extra callback if the info is not changed.
clearInvocations(mOrganizer);
@@ -269,7 +269,7 @@
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
+ verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
// Trigger callback if the size is changed.
mTask.getConfiguration().smallestScreenWidthDp = 100;
@@ -277,7 +277,7 @@
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
// Trigger callback if the windowing mode is changed.
clearInvocations(mOrganizer);
@@ -286,7 +286,7 @@
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
}
@Test
@@ -298,11 +298,12 @@
mErrorToken, null /* taskFragment */, -1 /* opType */, exception);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), eq(exception));
+ verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), eq(null), eq(-1),
+ eq(exception));
}
@Test
- public void testOnActivityReparentToTask_activityInOrganizerProcess_useActivityToken() {
+ public void testOnActivityReparentedToTask_activityInOrganizerProcess_useActivityToken() {
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -314,17 +315,18 @@
task.effectiveUid = uid;
// No need to notify organizer if it is not embedded.
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onActivityReparentToTask(anyInt(), any(), any());
+ verify(mOrganizer, never()).onActivityReparentedToTask(any(), anyInt(), any(), any());
// Notify organizer if it was embedded before entered Pip.
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- verify(mOrganizer).onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+ verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
+ eq(activity.token));
// Notify organizer if there is any embedded in the Task.
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -335,15 +337,16 @@
DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
activity.reparent(taskFragment, POSITION_TOP);
activity.mLastTaskFragmentOrganizerBeforePip = null;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
verify(mOrganizer, times(2))
- .onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+ .onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
+ eq(activity.token));
}
@Test
- public void testOnActivityReparentToTask_activityNotInOrganizerProcess_useTemporaryToken() {
+ public void testOnActivityReparentedToTask_activityNotInOrganizerProcess_useTemporaryToken() {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
@@ -364,11 +367,11 @@
// Notify organizer if it was embedded before entered Pip.
// Create a temporary token since the activity doesn't belong to the same process.
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
// Allow organizer to reparent activity in other process using the temporary token.
- verify(mOrganizer).onActivityReparentToTask(eq(task.mTaskId), eq(activity.intent),
+ verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
token.capture());
final IBinder temporaryToken = token.getValue();
assertNotEquals(activity.token, temporaryToken);
@@ -798,7 +801,7 @@
mController.dispatchPendingEvents();
// Verifies that event was not sent
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
}
@Test
@@ -824,7 +827,7 @@
mController.dispatchPendingEvents();
// Verifies that event was not sent
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Mock the task becomes visible, and activity resumed
doReturn(true).when(task).shouldBeVisible(any());
@@ -832,7 +835,7 @@
// Verifies that event is sent.
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
}
/**
@@ -866,7 +869,7 @@
reset(mOrganizer);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
}
/**
@@ -884,8 +887,8 @@
.createActivityCount(1)
.build();
final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
- // Add another activity in the Task so that it always contains a non-finishing activitiy.
- final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ // Add another activity in the Task so that it always contains a non-finishing activity.
+ createActivityRecord(task);
assertTrue(task.shouldBeVisible(null));
// Dispatch pending info changed event from creating the activity
@@ -893,21 +896,21 @@
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
// Verify the info changed callback is not called when the task is invisible
reset(mOrganizer);
doReturn(false).when(task).shouldBeVisible(any());
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Finish the embedded activity, and verify the info changed callback is called because the
// TaskFragment is becoming empty.
embeddedActivity.finishing = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
}
/**
@@ -1017,7 +1020,7 @@
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
any(SecurityException.class));
}
@@ -1056,7 +1059,7 @@
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 88eadfc..83f1789 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -134,6 +134,23 @@
}
@Test
+ public void testStartChangeTransition_doNotFreezeWhenOnlyMoved() {
+ final Rect startBounds = new Rect(0, 0, 1000, 1000);
+ final Rect endBounds = new Rect(startBounds);
+ endBounds.offset(500, 0);
+ mTaskFragment.setBounds(startBounds);
+ doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+ clearInvocations(mTransaction);
+ mTaskFragment.setBounds(endBounds);
+
+ // No change transition, but update the organized surface position.
+ verify(mTaskFragment, never()).initializeChangeTransition(any(), any());
+ verify(mTransaction).setPosition(mLeash, endBounds.left, endBounds.top);
+ }
+
+ @Test
public void testNotOkToAnimate_doNotStartChangeTransition() {
mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
final Rect startBounds = new Rect(0, 0, 1000, 1000);
@@ -323,7 +340,7 @@
activity.reparent(task, POSITION_TOP);
// Notify the organizer about the reparent.
- verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentToTask(activity);
+ verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentedToTask(activity);
assertNull(activity.mLastTaskFragmentOrganizerBeforePip);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 851be9d..d2cb7ba 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -245,7 +245,7 @@
}
@Override
- public void userActivity() {
+ public void userActivity(int displayGroupId, int event) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 5a2d456..85ac7bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -36,8 +36,10 @@
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1082,6 +1084,39 @@
assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0);
}
+ @Test
+ public void testIncludeEmbeddedActivityReparent() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final Task task = createTask(mDisplayContent);
+ task.setBounds(new Rect(0, 0, 2000, 1000));
+ final ActivityRecord activity = createActivityRecord(task);
+ activity.mVisibleRequested = true;
+ // Skip manipulate the SurfaceControl.
+ doNothing().when(activity).setDropInputMode(anyInt());
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(organizer)
+ .build();
+ // TaskFragment with different bounds from Task.
+ embeddedTf.setBounds(new Rect(0, 0, 1000, 1000));
+
+ // Start states.
+ transition.collect(activity);
+ transition.collectExistenceChange(embeddedTf);
+
+ // End states.
+ activity.reparent(embeddedTf, POSITION_TOP);
+
+ // Verify that both activity and TaskFragment are included.
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ transition.mParticipants, transition.mChanges);
+ assertTrue(targets.contains(embeddedTf));
+ assertTrue(targets.contains(activity));
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f28408e1a..4fb6587 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -139,24 +139,19 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final Uri CONTENT_URI = SimInfo.CONTENT_URI;
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_sub_id";
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_data_sub_id";
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_sms_sub_id";
- /** @hide */
- public static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
"cache_key.telephony.get_active_data_sub_id";
- /** @hide */
- public static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
+ private static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
"cache_key.telephony.get_slot_index";
/** @hide */
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
index 6fc9a67..918e514 100644
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ b/tests/FlickerTests/libs/window-extensions-release.aar
Binary files differ
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
index 3b201f9..e4add80 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
@@ -16,6 +16,9 @@
package android.net.vcn.persistablebundleutils;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.isIkeOptionValid;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static android.telephony.TelephonyManager.APPTYPE_USIM;
@@ -134,15 +137,37 @@
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
+ private static IkeSessionParams.Builder createBuilderMinimumWithEap() throws Exception {
+ final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
+
+ final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
+ final int subId = 1;
+ final EapSessionConfig eapConfig =
+ new EapSessionConfig.Builder()
+ .setEapIdentity(eapId)
+ .setEapSimConfig(subId, APPTYPE_USIM)
+ .setEapAkaConfig(subId, APPTYPE_USIM)
+ .build();
+ return createBuilderMinimum().setAuthEap(serverCaCert, eapConfig);
+ }
+
@Test
public void testEncodeDecodeParamsWithIkeOptions() throws Exception {
- final IkeSessionParams params =
- createBuilderMinimum()
+ final IkeSessionParams.Builder builder =
+ createBuilderMinimumWithEap()
.addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID)
+ .addIkeOption(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH)
.addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
+ .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500)
.addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT)
- .build();
- verifyPersistableBundleEncodeDecodeIsLossless(params);
+ .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+ if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION)) {
+ builder.addIkeOption(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+ }
+ if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES)) {
+ builder.addIkeOption(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+ }
+ verifyPersistableBundleEncodeDecodeIsLossless(builder.build());
}
private static InputStream openAssetsFile(String fileName) throws Exception {
@@ -176,19 +201,7 @@
@Test
public void testEncodeRecodeParamsWithEapAuth() throws Exception {
- final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
-
- final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
- final int subId = 1;
- final EapSessionConfig eapConfig =
- new EapSessionConfig.Builder()
- .setEapIdentity(eapId)
- .setEapSimConfig(subId, APPTYPE_USIM)
- .setEapAkaConfig(subId, APPTYPE_USIM)
- .build();
-
- final IkeSessionParams params =
- createBuilderMinimum().setAuthEap(serverCaCert, eapConfig).build();
+ final IkeSessionParams params = createBuilderMinimumWithEap().build();
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
}