Merge "ImageReader: Fix rogue RuntimeException in #detachImage"
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 d3fcaa1..1e2650d 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -97,6 +97,10 @@
buffer.position(0);
Typeface.deserializeFontMap(buffer, out);
elapsedTime = System.nanoTime() - startTime;
+ for (Typeface typeface : out.values()) {
+ typeface.releaseNativeObjectForTest();
+ }
+ out.clear();
}
}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index 0620721..f844ba3 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -88,6 +88,7 @@
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
final Rect mOutAttachedFrame = new Rect();
+ final float[] mOutSizeCompatScale = { 1f };
TestWindow() {
mLayoutParams.setTitle(TestWindow.class.getName());
@@ -106,7 +107,7 @@
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel,
- mOutInsetsState, mOutControls, mOutAttachedFrame);
+ mOutInsetsState, mOutControls, mOutAttachedFrame, mOutSizeCompatScale);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 7393bcd..5a445d4 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -1257,6 +1257,17 @@
}
/**
+ * Remove all alarms previously set by the caller, if any.
+ */
+ public void cancelAll() {
+ try {
+ mService.removeAll(mContext.getOpPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set the system wall clock time.
* Requires the permission android.permission.SET_TIME.
*
diff --git a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
index 25caf4b..a46e697 100644
--- a/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl
@@ -37,6 +37,7 @@
boolean setTime(long millis);
void setTimeZone(String zone);
void remove(in PendingIntent operation, in IAlarmListener listener);
+ void removeAll(String packageName);
long getNextWakeFromIdleTime();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 57b6935..d8e25b6 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2970,6 +2970,24 @@
}
@Override
+ public void removeAll(String callingPackage) {
+ final int callingUid = mInjector.getCallingUid();
+ if (callingUid == Process.SYSTEM_UID) {
+ Slog.wtfStack(TAG, "Attempt to remove all alarms from the system uid package: "
+ + callingPackage);
+ return;
+ }
+ if (callingUid != mPackageManagerInternal.getPackageUid(callingPackage, 0,
+ UserHandle.getUserId(callingUid))) {
+ throw new SecurityException("Package " + callingPackage
+ + " does not belong to the calling uid " + callingUid);
+ }
+ synchronized (mLock) {
+ removeLocked(callingPackage, REMOVE_REASON_ALARM_CANCELLED);
+ }
+ }
+
+ @Override
public long getNextWakeFromIdleTime() {
return getNextWakeFromIdleTimeImpl();
}
@@ -4081,7 +4099,7 @@
}
@GuardedBy("mLock")
- void removeLocked(final String packageName) {
+ void removeLocked(final String packageName, int reason) {
if (packageName == null) {
if (localLOGV) {
Slog.w(TAG, "requested remove() of null packageName",
@@ -4089,7 +4107,7 @@
}
return;
}
- removeAlarmsInternalLocked(a -> a.matches(packageName), REMOVE_REASON_UNDEFINED);
+ removeAlarmsInternalLocked(a -> a.matches(packageName), reason);
}
// Only called for ephemeral apps
@@ -5132,7 +5150,7 @@
removeLocked(uid, REMOVE_REASON_UNDEFINED);
} else {
// external-applications-unavailable case
- removeLocked(pkg);
+ removeLocked(pkg, REMOVE_REASON_UNDEFINED);
}
mPriorities.remove(pkg);
for (int i = mBroadcastStats.size() - 1; i >= 0; i--) {
@@ -5200,13 +5218,15 @@
+ TareBill.getName(bill) + " changed to " + canAfford);
}
- ArrayMap<EconomyManagerInternal.ActionBill, Boolean> actionAffordability =
- mAffordabilityCache.get(userId, packageName);
- if (actionAffordability == null) {
- actionAffordability = new ArrayMap<>();
- mAffordabilityCache.add(userId, packageName, actionAffordability);
+ synchronized (mLock) {
+ ArrayMap<EconomyManagerInternal.ActionBill, Boolean> actionAffordability =
+ mAffordabilityCache.get(userId, packageName);
+ if (actionAffordability == null) {
+ actionAffordability = new ArrayMap<>();
+ mAffordabilityCache.add(userId, packageName, actionAffordability);
+ }
+ actionAffordability.put(bill, canAfford);
}
- actionAffordability.put(bill, canAfford);
mHandler.obtainMessage(AlarmHandler.TARE_AFFORDABILITY_CHANGED, userId,
canAfford ? 1 : 0, packageName)
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 f16d8cf1..73508c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -102,6 +102,7 @@
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.ContentObserverController;
import com.android.server.job.controllers.DeviceIdleJobsController;
+import com.android.server.job.controllers.FlexibilityController;
import com.android.server.job.controllers.IdleController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.PrefetchController;
@@ -1555,12 +1556,16 @@
// Create the controllers.
mControllers = new ArrayList<StateController>();
- final ConnectivityController connectivityController = new ConnectivityController(this);
+ final FlexibilityController flexibilityController = new FlexibilityController(this);
+ mControllers.add(flexibilityController);
+ final ConnectivityController connectivityController =
+ new ConnectivityController(this, flexibilityController);
mControllers.add(connectivityController);
mControllers.add(new TimeController(this));
- final IdleController idleController = new IdleController(this);
+ final IdleController idleController = new IdleController(this, flexibilityController);
mControllers.add(idleController);
- final BatteryController batteryController = new BatteryController(this);
+ final BatteryController batteryController =
+ new BatteryController(this, flexibilityController);
mControllers.add(batteryController);
mStorageController = new StorageController(this);
mControllers.add(mStorageController);
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 5ef6855..59cd82e 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
@@ -62,16 +62,19 @@
private final PowerTracker mPowerTracker;
+ private final FlexibilityController mFlexibilityController;
/**
* Helper set to avoid too much GC churn from frequent calls to
* {@link #maybeReportNewChargingStateLocked()}.
*/
private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>();
- public BatteryController(JobSchedulerService service) {
+ public BatteryController(JobSchedulerService service,
+ FlexibilityController flexibilityController) {
super(service);
mPowerTracker = new PowerTracker();
mPowerTracker.startTracking();
+ mFlexibilityController = flexibilityController;
}
@Override
@@ -173,6 +176,11 @@
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();
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.valueAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 892e0c0..d2dc2a7e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -106,6 +106,7 @@
private final ConnectivityManager mConnManager;
private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
+ private final FlexibilityController mFlexibilityController;
/** List of tracked jobs keyed by source UID. */
@GuardedBy("mLock")
@@ -231,12 +232,14 @@
private final Handler mHandler;
- public ConnectivityController(JobSchedulerService service) {
+ public ConnectivityController(JobSchedulerService service,
+ @NonNull FlexibilityController flexibilityController) {
super(service);
mHandler = new CcHandler(mContext.getMainLooper());
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
+ mFlexibilityController = flexibilityController;
// We're interested in all network changes; internally we match these
// network changes against the active network for each UID with jobs.
@@ -1058,6 +1061,15 @@
final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied);
+ if (jobStatus.getPreferUnmetered()) {
+ jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null
+ && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED));
+
+ jobStatus.setFlexibilityConstraintSatisfied(nowElapsed,
+ mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus));
+ }
+
+
// Pass along the evaluated network for job to use; prevents race
// conditions as default routes change over time, and opens the door to
// using non-default routes.
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 687693c..f4ee0ae 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
@@ -20,15 +20,22 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.content.Context;
import android.os.Looper;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,55 +51,80 @@
* Controller that tracks the number of flexible constraints being actively satisfied.
* Drops constraint for TOP apps and lowers number of required constraints with time.
*
- * TODO: Plug in to other controllers (b/239047584), handle prefetch (b/238887951)
+ * TODO(b/238887951): handle prefetch
*/
public final class FlexibilityController extends StateController {
- /**
- * List of all potential flexible constraints
- */
- @VisibleForTesting
- static final int FLEXIBLE_CONSTRAINTS = JobStatus.CONSTRAINT_BATTERY_NOT_LOW
- | JobStatus.CONSTRAINT_CHARGING
- | JobStatus.CONSTRAINT_CONNECTIVITY
- | JobStatus.CONSTRAINT_IDLE;
+ private static final String TAG = "JobScheduler.Flexibility";
- /** Hard cutoff to remove flexible constraints */
+ /** List of all system-wide flexible constraints whose satisfaction is independent of job. */
+ static final int SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW
+ | CONSTRAINT_CHARGING
+ | CONSTRAINT_IDLE;
+
+ /** List of all job flexible constraints whose satisfaction is job specific. */
+ private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;
+
+ /** List of all flexible constraints. */
+ private static final int FLEXIBLE_CONSTRAINTS =
+ JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
+
+ @VisibleForTesting
+ static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
+ Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
+
+ static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
+ Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
+
+ @VisibleForTesting
+ static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
+
+ /** Hard cutoff to remove flexible constraints. */
private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
/**
+ * The default deadline that all flexible constraints should be dropped by if a job lacks
+ * a deadline.
+ */
+ private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;
+
+ /**
* Keeps track of what flexible constraints are satisfied at the moment.
* Is updated by the other controllers.
*/
- private int mSatisfiedFlexibleConstraints;
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ int mSatisfiedFlexibleConstraints;
+ @GuardedBy("mLock")
+ private boolean mFlexibilityEnabled = FcConstants.DEFAULT_FLEXIBILITY_ENABLED;
@VisibleForTesting
@GuardedBy("mLock")
final FlexibilityTracker mFlexibilityTracker;
+ private final FcConstants mFcConstants;
private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
- private final long mMinTimeBetweenAlarmsMs = MINUTE_IN_MILLIS;
+ private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;
/**
* The percent of a Jobs lifecycle to drop number of required constraints.
- * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
+ * PERCENT_TO_DROP_CONSTRAINTS[i] denotes that at x% of a Jobs lifecycle,
* the controller should have i+1 constraints dropped.
*/
- private final int[] mPercentToDropConstraints = {50, 60, 70, 80};
-
- /** The default deadline that all flexible constraints should be dropped by. */
- private final long mDefaultFlexibleDeadline = 72 * HOUR_IN_MILLIS;
+ private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};
public FlexibilityController(JobSchedulerService service) {
super(service);
- mFlexibilityTracker = new FlexibilityTracker(FLEXIBLE_CONSTRAINTS);
+ mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
+ mFcConstants = new FcConstants();
mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
mContext, JobSchedulerBackgroundThread.get().getLooper());
}
/**
- * StateController interface
+ * StateController interface.
*/
@Override
+ @GuardedBy("mLock")
public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) {
if (js.hasFlexibilityConstraint()) {
mFlexibilityTracker.add(js);
@@ -104,6 +136,7 @@
}
@Override
+ @GuardedBy("mLock")
public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob, boolean forUpdate) {
if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
mFlexibilityAlarmQueue.removeAlarmForKey(js);
@@ -112,27 +145,26 @@
}
/** Checks if the flexibility constraint is actively satisfied for a given job. */
- @VisibleForTesting
+ @GuardedBy("mLock")
boolean isFlexibilitySatisfiedLocked(JobStatus js) {
- synchronized (mLock) {
- return mService.getUidBias(js.getUid()) == JobInfo.BIAS_TOP_APP
- || mService.isCurrentlyRunningLocked(js)
- || getNumSatisfiedRequiredConstraintsLocked(js)
- >= js.getNumRequiredFlexibleConstraints();
- }
+ return !mFlexibilityEnabled
+ || mService.getUidBias(js.getUid()) == JobInfo.BIAS_TOP_APP
+ || mService.isCurrentlyRunningLocked(js)
+ || getNumSatisfiedRequiredConstraintsLocked(js)
+ >= js.getNumRequiredFlexibleConstraints();
}
@VisibleForTesting
@GuardedBy("mLock")
int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) {
- return Integer.bitCount(js.getFlexibleConstraints() & mSatisfiedFlexibleConstraints);
+ return Integer.bitCount(mSatisfiedFlexibleConstraints)
+ + (js.getHasAccessToUnmetered() ? 1 : 0);
}
/**
* Sets the controller's constraint to a given state.
* Changes flexibility constraint satisfaction for affected jobs.
*/
- @VisibleForTesting
void setConstraintSatisfied(int constraint, boolean state) {
synchronized (mLock) {
final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
@@ -149,14 +181,27 @@
// The rest did not have a change in state and are still satisfied or unsatisfied.
final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied);
- final ArraySet<JobStatus> jobs = mFlexibilityTracker.getJobsByNumRequiredConstraints(
- numConstraintsToUpdate);
final long nowElapsed = sElapsedRealtimeClock.millis();
- for (int i = 0; i < jobs.size(); i++) {
- JobStatus js = jobs.valueAt(i);
- js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
+ // 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.
+ for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
+ final ArraySet<JobStatus> jobs = mFlexibilityTracker
+ .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);
+
+ if (jobs == null) {
+ // If there are no more jobs to iterate through we can just return.
+ return;
+ }
+
+ for (int i = 0; i < jobs.size(); i++) {
+ JobStatus js = jobs.valueAt(i);
+ js.setFlexibilityConstraintSatisfied(
+ nowElapsed, isFlexibilitySatisfiedLocked(js));
+ }
}
+
}
}
@@ -173,9 +218,9 @@
final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
? js.enqueueTime : js.getEarliestRunTime();
final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
- ? earliest + mDefaultFlexibleDeadline
+ ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
: js.getLatestRunTimeElapsed();
- final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
+ final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
final long percentInTime = ((latest - earliest) * percent) / 100;
return earliest + percentInTime;
}
@@ -196,20 +241,58 @@
}
}
+ @Override
+ @GuardedBy("mLock")
+ public void onConstantsUpdatedLocked() {
+ if (mFcConstants.mShouldReevaluateConstraints) {
+ // Update job bookkeeping out of band.
+ JobSchedulerBackgroundThread.getHandler().post(() -> {
+ final ArraySet<JobStatus> changedJobs = new ArraySet<>();
+ synchronized (mLock) {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ for (int j = 1; j <= mFlexibilityTracker.size(); j++) {
+ final ArraySet<JobStatus> jobs = mFlexibilityTracker
+ .getJobsByNumRequiredConstraints(j);
+ for (int i = 0; i < jobs.size(); i++) {
+ JobStatus js = jobs.valueAt(i);
+ if (js.setFlexibilityConstraintSatisfied(
+ nowElapsed, isFlexibilitySatisfiedLocked(js))) {
+ changedJobs.add(js);
+ }
+ }
+ }
+ }
+ if (changedJobs.size() > 0) {
+ mStateChangedListener.onControllerStateChanged(changedJobs);
+ }
+ });
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void prepareForUpdatedConstantsLocked() {
+ mFcConstants.mShouldReevaluateConstraints = false;
+ }
+
@VisibleForTesting
class FlexibilityTracker {
final ArrayList<ArraySet<JobStatus>> mTrackedJobs;
- FlexibilityTracker(int flexibleConstraints) {
+ FlexibilityTracker(int numFlexibleConstraints) {
mTrackedJobs = new ArrayList<>();
- int numFlexibleConstraints = Integer.bitCount(flexibleConstraints);
for (int i = 0; i <= numFlexibleConstraints; i++) {
mTrackedJobs.add(new ArraySet<JobStatus>());
}
}
/** Gets every tracked job with a given number of required constraints. */
+ @Nullable
public ArraySet<JobStatus> getJobsByNumRequiredConstraints(int numRequired) {
+ if (numRequired > mTrackedJobs.size()) {
+ Slog.wtfStack(TAG, "Asked for a larger number of constraints than exists.");
+ return null;
+ }
return mTrackedJobs.get(numRequired - 1);
}
@@ -252,6 +335,10 @@
return true;
}
+ public int size() {
+ return mTrackedJobs.size();
+ }
+
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
for (int i = 0; i < mTrackedJobs.size(); i++) {
ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
@@ -273,7 +360,8 @@
private class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
private FlexibilityAlarmQueue(Context context, Looper looper) {
super(context, looper, "*job.flexibility_check*",
- "Flexible Constraint Check", false, mMinTimeBetweenAlarmsMs);
+ "Flexible Constraint Check", false,
+ MIN_TIME_BETWEEN_ALARMS_MS);
}
@Override
@@ -288,12 +376,10 @@
for (int i = 0; i < expired.size(); i++) {
js = expired.valueAt(i);
long time = getNextConstraintDropTimeElapsed(js);
- if (js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS) {
- mFlexibilityTracker.adjustJobsRequiredConstraints(js,
- -js.getNumRequiredFlexibleConstraints());
- continue;
- }
- if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1)) {
+ int toDecrease =
+ js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
+ ? -js.getNumRequiredFlexibleConstraints() : -1;
+ if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) {
mFlexibilityAlarmQueue.addAlarm(js, time);
}
}
@@ -301,6 +387,52 @@
}
}
+ @VisibleForTesting
+ class FcConstants {
+ private boolean mShouldReevaluateConstraints = false;
+
+ private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+
+ public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
+
+ /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
+ private static final String FC_CONSTANT_PREFIX = "fc_";
+
+ static final String KEY_FLEXIBILITY_ENABLED = FC_CONSTANT_PREFIX + "enable_flexibility";
+
+ // TODO(b/239925946): properly handle DeviceConfig and changing variables
+ @GuardedBy("mLock")
+ public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
+ @NonNull String key) {
+ switch (key) {
+ case KEY_FLEXIBILITY_ENABLED:
+ FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED);
+ if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
+ mFlexibilityEnabled = FLEXIBILITY_ENABLED;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ }
+ }
+
+ private void dump(IndentingPrintWriter pw) {
+ pw.println();
+ pw.print(FlexibilityController.class.getSimpleName());
+ pw.println(":");
+ pw.increaseIndent();
+
+ pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println();
+
+ pw.decreaseIndent();
+ }
+ }
+
+ @VisibleForTesting
+ @NonNull
+ FcConstants getFcConstants() {
+ return mFcConstants;
+ }
+
@Override
@GuardedBy("mLock")
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -308,5 +440,6 @@
pw.println();
mFlexibilityTracker.dump(pw, predicate);
+ mFcConstants.dump(pw);
}
}
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 a6fae2c..d5750f8 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
@@ -48,10 +48,13 @@
// screen off or dreaming or wireless charging dock idle for at least this long
final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
IdlenessTracker mIdleTracker;
+ private final FlexibilityController mFlexibilityController;
- public IdleController(JobSchedulerService service) {
+ public IdleController(JobSchedulerService service,
+ FlexibilityController flexibilityController) {
super(service);
initIdleStateTracking(mContext);
+ mFlexibilityController = flexibilityController;
}
/**
@@ -92,6 +95,7 @@
*/
@Override
public void reportNewIdleState(boolean isIdle) {
+ mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_IDLE, isIdle);
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
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 41cf4212..52882fe 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
@@ -25,6 +25,8 @@
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
import android.annotation.ElapsedRealtimeLong;
import android.app.AppGlobals;
@@ -122,12 +124,6 @@
| CONSTRAINT_IDLE;
/**
- * The set of constraints that are required to satisfy flexible constraints.
- * Constraints explicitly requested by the job will not be added to the set.
- */
- private int mFlexibleConstraints;
-
- /**
* Keeps track of how many flexible constraints must be satisfied for the job to execute.
*/
private int mNumRequiredFlexibleConstraints;
@@ -137,6 +133,9 @@
*/
private int mNumDroppedFlexibleConstraints;
+ /** If the job is going to be passed an unmetered network. */
+ private boolean mHasAccessToUnmetered;
+
/**
* The additional set of dynamic constraints that must be met if this is an expedited job that
* had a long enough run while the device was Dozing or in battery saver.
@@ -466,6 +465,12 @@
/** The job's dynamic requirements have been satisfied. */
private boolean mReadyDynamicSatisfied;
+ /**
+ * The job prefers an unmetered network if it has the connectivity constraint but is
+ * okay with any meteredness.
+ */
+ private final boolean mPreferUnmetered;
+
/** The reason a job most recently went from ready to not ready. */
private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED;
@@ -541,6 +546,9 @@
if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_DEADLINE;
}
+ if (job.isPrefetch()) {
+ requiredConstraints |= CONSTRAINT_PREFETCH;
+ }
boolean exemptedMediaUrisOnly = false;
if (job.getTriggerContentUris() != null) {
requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
@@ -554,30 +562,23 @@
}
mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly;
- if (isRequestedExpeditedJob()
- || ((latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
- < MIN_WINDOW_FOR_FLEXIBILITY_MS)
- || job.isPrefetch()) {
- mFlexibleConstraints = 0;
- } else {
- if ((requiredConstraints & CONSTRAINT_CHARGING) == 0) {
- mFlexibleConstraints |= CONSTRAINT_CHARGING;
- }
- if ((requiredConstraints & CONSTRAINT_BATTERY_NOT_LOW) == 0) {
- mFlexibleConstraints |= CONSTRAINT_BATTERY_NOT_LOW;
- }
- if ((requiredConstraints & CONSTRAINT_IDLE) == 0) {
- mFlexibleConstraints |= CONSTRAINT_IDLE;
- }
- if (job.getRequiredNetwork() != null
- && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED)) {
- mFlexibleConstraints |= CONSTRAINT_CONNECTIVITY;
- }
- }
- if (mFlexibleConstraints != 0) {
- // TODO(b/239047584): Uncomment once Flexibility Controller is plugged in.
- // requiredConstraints |= CONSTRAINT_FLEXIBLE;
- mNumRequiredFlexibleConstraints = Integer.bitCount(mFlexibleConstraints);
+ mPreferUnmetered = job.getRequiredNetwork() != null
+ && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED);
+
+ final boolean lacksSomeFlexibleConstraints =
+ ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0
+ || mPreferUnmetered;
+ final boolean satisfiesMinWindowException =
+ (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
+ >= MIN_WINDOW_FOR_FLEXIBILITY_MS;
+
+ if (!isRequestedExpeditedJob()
+ && satisfiesMinWindowException
+ && !job.isPrefetch()
+ && lacksSomeFlexibleConstraints) {
+ mNumRequiredFlexibleConstraints =
+ NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
+ requiredConstraints |= CONSTRAINT_FLEXIBLE;
}
this.requiredConstraints = requiredConstraints;
@@ -1137,11 +1138,6 @@
return hasConstraint(CONSTRAINT_IDLE);
}
- /** Returns true if the job has a prefetch constraint */
- public boolean hasPrefetchConstraint() {
- return hasConstraint(CONSTRAINT_PREFETCH);
- }
-
public boolean hasContentTriggerConstraint() {
// No need to check mDynamicConstraints since content trigger will only be in that list if
// it's already in the requiredConstraints list.
@@ -1206,14 +1202,23 @@
return mOriginalLatestRunTimeElapsedMillis;
}
- public int getFlexibleConstraints() {
- return mFlexibleConstraints;
- }
-
public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
}
+ /** Sets the jobs access to an unmetered network. */
+ void setHasAccessToUnmetered(boolean access) {
+ mHasAccessToUnmetered = access;
+ }
+
+ boolean getHasAccessToUnmetered() {
+ return mHasAccessToUnmetered;
+ }
+
+ boolean getPreferUnmetered() {
+ return mPreferUnmetered;
+ }
+
@JobParameters.StopReason
public int getStopReason() {
return mReasonReadyToUnready;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index d0f719b..a46430f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -91,6 +91,7 @@
import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT;
import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_MAX;
import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.arcToCake;
import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING;
@@ -103,7 +104,6 @@
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -150,13 +150,15 @@
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
+ private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
private final SparseArray<Reward> mRewards = new SparseArray<>();
- AlarmManagerEconomicPolicy(InternalResourceService irs) {
+ AlarmManagerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
mInternalResourceService = irs;
+ mInjector = injector;
loadConstants("", null);
}
@@ -164,7 +166,7 @@
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
- loadConstants(Settings.Global.getString(resolver, TARE_ALARM_MANAGER_CONSTANTS),
+ loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_ALARM_MANAGER_CONSTANTS),
properties);
}
@@ -226,20 +228,20 @@
Slog.e(TAG, "Global setting key incorrect: ", e);
}
- mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
- KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
- DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES);
mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
- KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP,
- DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
+ KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
+ mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
+ KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+ mMinSatiatedBalanceOther);
mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
- KEY_AM_MAX_SATIATED_BALANCE,
- DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES);
+ KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
+ Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
- KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES);
- mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
- getConstantAsCake(mParser, properties,
- KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES));
+ KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
+ arcToCake(1));
+ mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+ KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
+ mInitialSatiatedConsumptionLimit);
final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties,
KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index c3eb5bf..5d9cce8 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -23,11 +23,13 @@
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
-import libcore.util.EmptyArray;
+import com.android.internal.annotations.VisibleForTesting;
+import libcore.util.EmptyArray;
/** Combines all enabled policies into one. */
public class CompleteEconomicPolicy extends EconomicPolicy {
+
private final ArraySet<EconomicPolicy> mEnabledEconomicPolicies = new ArraySet<>();
/** Lazily populated set of actions covered by this policy. */
private final SparseArray<Action> mActions = new SparseArray<>();
@@ -35,12 +37,23 @@
private final SparseArray<Reward> mRewards = new SparseArray<>();
private final int[] mCostModifiers;
private long mMaxSatiatedBalance;
- private long mConsumptionLimit;
+ private long mInitialConsumptionLimit;
+ private long mHardConsumptionLimit;
CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
+ this(irs, new CompleteInjector());
+ }
+
+ @VisibleForTesting
+ CompleteEconomicPolicy(@NonNull InternalResourceService irs,
+ @NonNull CompleteInjector injector) {
super(irs);
- mEnabledEconomicPolicies.add(new AlarmManagerEconomicPolicy(irs));
- mEnabledEconomicPolicies.add(new JobSchedulerEconomicPolicy(irs));
+ if (injector.isPolicyEnabled(POLICY_AM)) {
+ mEnabledEconomicPolicies.add(new AlarmManagerEconomicPolicy(irs, injector));
+ }
+ if (injector.isPolicyEnabled(POLICY_JS)) {
+ mEnabledEconomicPolicies.add(new JobSchedulerEconomicPolicy(irs, injector));
+ }
ArraySet<Integer> costModifiers = new ArraySet<>();
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
@@ -54,7 +67,7 @@
mCostModifiers[i] = costModifiers.valueAt(i);
}
- updateMaxBalances();
+ updateLimits();
}
@Override
@@ -63,21 +76,22 @@
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
mEnabledEconomicPolicies.valueAt(i).setup(properties);
}
- updateMaxBalances();
+ updateLimits();
}
- private void updateMaxBalances() {
- long max = 0;
+ private void updateLimits() {
+ long maxSatiatedBalance = 0;
+ long initialConsumptionLimit = 0;
+ long hardConsumptionLimit = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
- max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedBalance();
+ final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i);
+ maxSatiatedBalance += economicPolicy.getMaxSatiatedBalance();
+ initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit();
+ hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit();
}
- mMaxSatiatedBalance = max;
-
- max = 0;
- for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
- max += mEnabledEconomicPolicies.valueAt(i).getInitialSatiatedConsumptionLimit();
- }
- mConsumptionLimit = max;
+ mMaxSatiatedBalance = maxSatiatedBalance;
+ mInitialConsumptionLimit = initialConsumptionLimit;
+ mHardConsumptionLimit = hardConsumptionLimit;
}
@Override
@@ -96,12 +110,12 @@
@Override
long getInitialSatiatedConsumptionLimit() {
- return mConsumptionLimit;
+ return mInitialConsumptionLimit;
}
@Override
long getHardSatiatedConsumptionLimit() {
- return mConsumptionLimit;
+ return mHardConsumptionLimit;
}
@NonNull
@@ -156,6 +170,14 @@
return reward;
}
+ @VisibleForTesting
+ static class CompleteInjector extends Injector {
+
+ boolean isPolicyEnabled(int policy) {
+ return true;
+ }
+ }
+
@Override
void dump(IndentingPrintWriter pw) {
dumpActiveModifiers(pw);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index aeb6abc..0937e7b 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -29,10 +29,14 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ContentResolver;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.KeyValueListParser;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -419,18 +423,34 @@
protected long getConstantAsCake(@NonNull KeyValueListParser parser,
@Nullable DeviceConfig.Properties properties, String key, long defaultValCake) {
+ return getConstantAsCake(parser, properties, key, defaultValCake, 0);
+ }
+
+ protected long getConstantAsCake(@NonNull KeyValueListParser parser,
+ @Nullable DeviceConfig.Properties properties, String key, long defaultValCake,
+ long minValCake) {
// Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig
// config can cause issues since the scales may be different, so use one or the other.
if (parser.size() > 0) {
// User settings take precedence. Just stick with the Settings constants, even if there
// are invalid values. It's not worth the time to evaluate all the key/value pairs to
// make sure there are valid ones before deciding.
- return parseCreditValue(parser.getString(key, null), defaultValCake);
+ return Math.max(minValCake,
+ parseCreditValue(parser.getString(key, null), defaultValCake));
}
if (properties != null) {
- return parseCreditValue(properties.getString(key, null), defaultValCake);
+ return Math.max(minValCake,
+ parseCreditValue(properties.getString(key, null), defaultValCake));
}
- return defaultValCake;
+ return Math.max(minValCake, defaultValCake);
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ @Nullable
+ String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) {
+ return Settings.Global.getString(resolver, name);
+ }
}
protected static void dumpActiveModifiers(IndentingPrintWriter pw) {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 948f0a7..e7db1ad 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -100,6 +100,7 @@
import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT;
import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_MAX;
import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING;
+import static android.app.tare.EconomyManager.arcToCake;
import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING;
@@ -112,7 +113,6 @@
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -152,13 +152,15 @@
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
+ private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
private final SparseArray<Reward> mRewards = new SparseArray<>();
- JobSchedulerEconomicPolicy(InternalResourceService irs) {
+ JobSchedulerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
mInternalResourceService = irs;
+ mInjector = injector;
loadConstants("", null);
}
@@ -166,7 +168,7 @@
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
- loadConstants(Settings.Global.getString(resolver, TARE_JOB_SCHEDULER_CONSTANTS),
+ loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_JOB_SCHEDULER_CONSTANTS),
properties);
}
@@ -223,22 +225,20 @@
Slog.e(TAG, "Global setting key incorrect: ", e);
}
- mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
- KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
- DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES);
mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties,
- KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP,
- DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
+ KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES);
+ mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
+ KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+ mMinSatiatedBalanceOther);
mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
- KEY_JS_MAX_SATIATED_BALANCE,
- DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES);
+ KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+ Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
- KEY_JS_INITIAL_CONSUMPTION_LIMIT,
- DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES);
- mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit,
- getConstantAsCake(mParser, properties,
- KEY_JS_HARD_CONSUMPTION_LIMIT,
- DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES));
+ KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
+ arcToCake(1));
+ mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+ KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
+ mInitialSatiatedConsumptionLimit);
mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
getConstantAsCake(mParser, properties,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index 3b5e444..e91ed12 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -17,9 +17,9 @@
package com.android.server.tare;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.util.TimeUtils.dumpTime;
import static com.android.server.tare.TareUtils.cakeToString;
-import static com.android.server.tare.TareUtils.dumpTime;
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import android.annotation.CurrentTimeMillisLong;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
index 6b6984f..aa4c75a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TareUtils.java
@@ -19,26 +19,15 @@
import static android.app.tare.EconomyManager.CAKE_IN_ARC;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
-import java.text.SimpleDateFormat;
import java.time.Clock;
class TareUtils {
- @SuppressLint("SimpleDateFormat")
- private static final SimpleDateFormat sDumpDateFormat =
- new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-
@VisibleForTesting
static Clock sSystemClock = Clock.systemUTC();
- static void dumpTime(IndentingPrintWriter pw, long time) {
- pw.print(sDumpDateFormat.format(time));
- }
-
static long getCurrentTimeMillis() {
return sSystemClock.millis();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 5d9f335..f1c4eb4 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1543,8 +1543,10 @@
@Override
@StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
long elapsedRealtime, boolean shouldObfuscateInstantApps) {
- if (!mAppIdleEnabled || (shouldObfuscateInstantApps
- && mInjector.isPackageEphemeral(userId, packageName))) {
+ if (!mAppIdleEnabled) {
+ return STANDBY_BUCKET_EXEMPTED;
+ }
+ if (shouldObfuscateInstantApps && mInjector.isPackageEphemeral(userId, packageName)) {
return STANDBY_BUCKET_ACTIVE;
}
diff --git a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
index dbc3b51..f5184e7 100644
--- a/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
+++ b/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt
@@ -39,7 +39,6 @@
Lcom/android/ims/internal/uce/presence/IPresenceService$Stub;-><init>()V
Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub;-><init>()V
Lcom/android/ims/internal/uce/uceservice/IUceService$Stub;-><init>()V
-Lcom/android/internal/app/IVoiceInteractionManagerService$Stub$Proxy;->showSessionFromSession(Landroid/os/IBinder;Landroid/os/Bundle;I)Z
Lcom/android/internal/appwidget/IAppWidgetService$Stub;->TRANSACTION_bindAppWidgetId:I
Lcom/android/internal/telephony/ITelephony$Stub;->DESCRIPTOR:Ljava/lang/String;
Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_dial:I
diff --git a/core/api/current.txt b/core/api/current.txt
index 7fbf3ec..1cd8253 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4596,6 +4596,7 @@
method public boolean canScheduleExactAlarms();
method public void cancel(android.app.PendingIntent);
method public void cancel(android.app.AlarmManager.OnAlarmListener);
+ method public void cancelAll();
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
method public void set(int, long, android.app.PendingIntent);
method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
@@ -18903,6 +18904,7 @@
method public void onUnbindInput();
method @Deprecated public void onUpdateCursor(android.graphics.Rect);
method public void onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo);
+ method public void onUpdateEditorToolType(int);
method public void onUpdateExtractedText(int, android.view.inputmethod.ExtractedText);
method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
@@ -45090,6 +45092,7 @@
ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float);
method public void draw(android.graphics.Canvas);
method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int);
+ method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int);
method public final android.text.Layout.Alignment getAlignment();
method public abstract int getBottomPadding();
method public void getCursorPath(int, android.graphics.Path, CharSequence);
@@ -45244,6 +45247,8 @@
method public static boolean extendRight(android.text.Spannable, android.text.Layout);
method public static final void extendSelection(android.text.Spannable, int);
method public static boolean extendToLeftEdge(android.text.Spannable, android.text.Layout);
+ method public static boolean extendToParagraphEnd(@NonNull android.text.Spannable);
+ method public static boolean extendToParagraphStart(@NonNull android.text.Spannable);
method public static boolean extendToRightEdge(android.text.Spannable, android.text.Layout);
method public static boolean extendUp(android.text.Spannable, android.text.Layout);
method public static final int getSelectionEnd(CharSequence);
@@ -45252,6 +45257,8 @@
method public static boolean moveLeft(android.text.Spannable, android.text.Layout);
method public static boolean moveRight(android.text.Spannable, android.text.Layout);
method public static boolean moveToLeftEdge(android.text.Spannable, android.text.Layout);
+ method public static boolean moveToParagraphEnd(@NonNull android.text.Spannable, @NonNull android.text.Layout);
+ method public static boolean moveToParagraphStart(@NonNull android.text.Spannable, @NonNull android.text.Layout);
method public static boolean moveToRightEdge(android.text.Spannable, android.text.Layout);
method public static boolean moveUp(android.text.Spannable, android.text.Layout);
method public static final void removeSelection(android.text.Spannable);
@@ -45700,6 +45707,7 @@
method protected boolean left(android.widget.TextView, android.text.Spannable);
method protected boolean lineEnd(android.widget.TextView, android.text.Spannable);
method protected boolean lineStart(android.widget.TextView, android.text.Spannable);
+ method public boolean nextParagraph(@NonNull android.widget.TextView, @NonNull android.text.Spannable);
method public boolean onGenericMotionEvent(android.widget.TextView, android.text.Spannable, android.view.MotionEvent);
method public boolean onKeyDown(android.widget.TextView, android.text.Spannable, int, android.view.KeyEvent);
method public boolean onKeyOther(android.widget.TextView, android.text.Spannable, android.view.KeyEvent);
@@ -45709,6 +45717,7 @@
method public boolean onTrackballEvent(android.widget.TextView, android.text.Spannable, android.view.MotionEvent);
method protected boolean pageDown(android.widget.TextView, android.text.Spannable);
method protected boolean pageUp(android.widget.TextView, android.text.Spannable);
+ method public boolean previousParagraph(@NonNull android.widget.TextView, @NonNull android.text.Spannable);
method protected boolean right(android.widget.TextView, android.text.Spannable);
method protected boolean top(android.widget.TextView, android.text.Spannable);
method protected boolean up(android.widget.TextView, android.text.Spannable);
@@ -52966,9 +52975,11 @@
method @Nullable public android.view.inputmethod.SurroundingText getInitialSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
method @Nullable public CharSequence getInitialTextAfterCursor(@IntRange(from=0) int, int);
method @Nullable public CharSequence getInitialTextBeforeCursor(@IntRange(from=0) int, int);
+ method public int getInitialToolType();
method public final void makeCompatible(int);
method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
method public void setInitialSurroundingText(@NonNull CharSequence);
+ method public void setInitialToolType(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR;
field public static final int IME_ACTION_DONE = 6; // 0x6
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fccb3c0..09d4ba6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -670,6 +670,7 @@
public final class UsageStatsManager {
method public void forceUsageSourceSettingRead();
+ method public boolean isAppStandbyEnabled();
}
}
@@ -1796,6 +1797,7 @@
}
public final class PowerManager {
+ method public boolean areAutoPowerSaveModesEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean);
field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 2fffaab7..af0fa39 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -187,8 +187,6 @@
"com/android/internal/util/IndentingPrintWriter.java",
"com/android/internal/util/MessageUtils.java",
"com/android/internal/util/WakeupMessage.java",
- // TODO: delete as soon as NetworkStatsFactory stops using
- "com/android/internal/util/ProcFileReader.java",
],
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d8a48c9..25ef6e8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -308,7 +308,7 @@
* <li>The <b>foreground lifetime</b> of an activity happens between a call to
* {@link android.app.Activity#onResume} until a corresponding call to
* {@link android.app.Activity#onPause}. During this time the activity is
- * in visible, active and interacting with the user. An activity
+ * visible, active and interacting with the user. An activity
* can frequently go between the resumed and paused states -- for example when
* the device goes to sleep, when an activity result is delivered, when a new
* intent is delivered -- so the code in these methods should be fairly
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 1a06c6f..f362204 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1453,6 +1453,11 @@
}
/** @hide */
+ public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
+ mRemoteTransition = remoteTransition;
+ }
+
+ /** @hide */
public static ActivityOptions fromBundle(Bundle bOptions) {
return bOptions != null ? new ActivityOptions(bOptions) : null;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2a83cbd..b383d7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -374,6 +374,7 @@
int mCurDefaultDisplayDpi;
@UnsupportedAppUsage
boolean mDensityCompatMode;
+ private CompatibilityInfo mCompatibilityInfo;
@UnsupportedAppUsage(trackingBug = 176961850, maxTargetSdk = Build.VERSION_CODES.R,
publicAlternatives = "Use {@code Context#getResources()#getConfiguration()} instead.")
Configuration mConfiguration;
@@ -612,7 +613,7 @@
}
public ActivityClientRecord(IBinder token, Intent intent, int ident,
- ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ ActivityInfo info, Configuration overrideConfig,
String referrer, IVoiceInteractor voiceInteractor, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -627,7 +628,6 @@
this.referrer = referrer;
this.voiceInteractor = voiceInteractor;
this.activityInfo = info;
- this.compatInfo = compatInfo;
this.state = state;
this.persistentState = persistentState;
this.pendingResults = pendingResults;
@@ -635,8 +635,7 @@
this.isForward = isForward;
this.profilerInfo = profilerInfo;
this.overrideConfig = overrideConfig;
- this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
- compatInfo);
+ this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
mActivityOptions = activityOptions;
mLaunchedFromBubble = launchedFromBubble;
mInitialTaskFragmentToken = initialTaskFragmentToken;
@@ -804,7 +803,6 @@
static final class CreateBackupAgentData {
ApplicationInfo appInfo;
- CompatibilityInfo compatInfo;
int backupMode;
int userId;
int operationType;
@@ -1038,15 +1036,13 @@
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
sync, false, mAppThread.asBinder(), sendingUser);
r.info = info;
- r.compatInfo = compatInfo;
sendMessage(H.RECEIVER, r);
}
public final void scheduleCreateBackupAgent(ApplicationInfo app,
- CompatibilityInfo compatInfo, int backupMode, int userId, int operationType) {
+ int backupMode, int userId, int operationType) {
CreateBackupAgentData d = new CreateBackupAgentData();
d.appInfo = app;
- d.compatInfo = compatInfo;
d.backupMode = backupMode;
d.userId = userId;
d.operationType = operationType;
@@ -1054,11 +1050,9 @@
sendMessage(H.CREATE_BACKUP_AGENT, d);
}
- public final void scheduleDestroyBackupAgent(ApplicationInfo app,
- CompatibilityInfo compatInfo, int userId) {
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app, int userId) {
CreateBackupAgentData d = new CreateBackupAgentData();
d.appInfo = app;
- d.compatInfo = compatInfo;
d.userId = userId;
sendMessage(H.DESTROY_BACKUP_AGENT, d);
@@ -1070,7 +1064,6 @@
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
- s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}
@@ -2577,16 +2570,16 @@
registerPackage);
}
- @Override
@UnsupportedAppUsage
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
- private LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo,
- boolean isSdkSandbox) {
- return getPackageInfo(ai, compatInfo, null, false, true, false, isSdkSandbox);
+ @Override
+ public LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
+ return getPackageInfo(ai, mCompatibilityInfo, null /* baseLoader */,
+ false /* securityViolation */, true /* includeCode */, false /* registerPackage */);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -3538,7 +3531,7 @@
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
- r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
+ r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo,
Context.CONTEXT_INCLUDE_CODE);
}
@@ -4279,8 +4272,7 @@
String component = data.intent.getComponent().getClassName();
- LoadedApk packageInfo = getPackageInfoNoCheck(
- data.info.applicationInfo, data.compatInfo);
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);
IActivityManager mgr = ActivityManager.getService();
@@ -4366,7 +4358,7 @@
unscheduleGcIdler();
// instantiate the BackupAgent class named in the manifest
- LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
String packageName = packageInfo.mPackageName;
if (packageName == null) {
Slog.d(TAG, "Asked to create backup agent for nonexistent package");
@@ -4439,7 +4431,7 @@
private void handleDestroyBackupAgent(CreateBackupAgentData data) {
if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
- LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
String packageName = packageInfo.mPackageName;
ArrayMap<String, BackupAgent> backupAgents = getBackupAgentsForUser(data.userId);
BackupAgent agent = backupAgents.get(packageName);
@@ -4471,8 +4463,7 @@
// we are back active so skip it.
unscheduleGcIdler();
- LoadedApk packageInfo = getPackageInfoNoCheck(
- data.info.applicationInfo, data.compatInfo);
+ final LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);
Service service = null;
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
@@ -5306,6 +5297,7 @@
}
private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
+ mCompatibilityInfo = data.info;
LoadedApk apk = peekPackageInfo(data.pkg, false);
if (apk != null) {
apk.setCompatibilityInfo(data.info);
@@ -6486,6 +6478,7 @@
mConfigurationController.setConfiguration(data.config);
mConfigurationController.setCompatConfiguration(data.config);
mConfiguration = mConfigurationController.getConfiguration();
+ mCompatibilityInfo = data.compatInfo;
mProfiler = new Profiler();
String agent = null;
@@ -6569,7 +6562,9 @@
}
final boolean isSdkSandbox = data.sdkSandboxClientAppPackage != null;
- data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo, isSdkSandbox);
+ data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
+ false /* securityViolation */, true /* includeCode */,
+ false /* registerPackage */, isSdkSandbox);
if (isSdkSandbox) {
data.info.setSdkSandboxStorage(data.sdkSandboxClientAppVolumeUuid,
data.sdkSandboxClientAppPackage);
@@ -6844,7 +6839,7 @@
private void handleInstrumentWithoutRestart(AppBindData data) {
try {
data.compatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ data.info = getPackageInfoNoCheck(data.appInfo);
mInstrumentingWithoutRestart = true;
final InstrumentationInfo ii = prepareInstrumentation(data);
final ContextImpl appContext =
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 65e6ab7..389da2d 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -23,7 +23,6 @@
import android.app.servertransaction.TransactionExecutor;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
@@ -180,8 +179,7 @@
PendingTransactionActions pendingActions, ActivityOptions activityOptions);
/** Get package info. */
- public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
- CompatibilityInfo compatInfo);
+ public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai);
/** Deliver app configuration change notification. */
public abstract void handleConfigurationChanged(Configuration config);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index dc6825c..3459d0e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -69,6 +69,7 @@
import android.service.voice.IVoiceInteractionSession;
import android.view.IRecentsAnimationRunner;
import android.view.IRemoteAnimationRunner;
+import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.window.IWindowOrganizerController;
@@ -230,7 +231,7 @@
in IBinder activityToken, int flags);
boolean isAssistDataAllowedOnCurrentActivity();
boolean requestAssistDataForTask(in IAssistDataReceiver receiver, int taskId,
- in String callingPackageName);
+ in String callingPackageName, String callingAttributionTag);
/**
* Notify the system that the keyguard is going away.
@@ -352,6 +353,8 @@
* Prepare the back navigation in the server. This setups the leashed for sysui to animate
* the back gesture and returns the data needed for the animation.
* @param requestAnimation true if the caller wishes to animate the back navigation
+ * @param focusObserver a remote callback to nofify shell when the focused window lost focus.
*/
- android.window.BackNavigationInfo startBackNavigation(in boolean requestAnimation);
+ android.window.BackNavigationInfo startBackNavigation(in boolean requestAnimation,
+ in IWindowFocusObserver focusObserver);
}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 75ad363..843b684 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -102,10 +102,9 @@
void scheduleLowMemory();
void profilerControl(boolean start, in ProfilerInfo profilerInfo, int profileType);
void setSchedulingGroup(int group);
- void scheduleCreateBackupAgent(in ApplicationInfo app, in CompatibilityInfo compatInfo,
+ void scheduleCreateBackupAgent(in ApplicationInfo app,
int backupMode, int userId, int operationType);
- void scheduleDestroyBackupAgent(in ApplicationInfo app,
- in CompatibilityInfo compatInfo, int userId);
+ void scheduleDestroyBackupAgent(in ApplicationInfo app, int userId);
void scheduleOnNewActivityOptions(IBinder token, in Bundle options);
void scheduleSuicide();
void dispatchPackageBroadcast(int cmd, in String[] packages);
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index d0ea8d4..397c8e0 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -460,6 +460,15 @@
setDisplayWindowingMode(WINDOWING_MODE_UNDEFINED);
}
+ /** @hide */
+ public void scale(float scale) {
+ mBounds.scale(scale);
+ mMaxBounds.scale(scale);
+ if (mAppBounds != null) {
+ mAppBounds.scale(scale);
+ }
+ }
+
/**
* Copies the fields from delta into this Configuration object, keeping
* track of which ones have changed. Any undefined fields in {@code delta}
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
index acd404b..dea4e9c8 100644
--- a/core/java/android/app/compat/ChangeIdStateCache.java
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -32,7 +32,7 @@
public final class ChangeIdStateCache
extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
- private static final int MAX_ENTRIES = 20;
+ private static final int MAX_ENTRIES = 64;
private static boolean sDisabled = false;
private volatile IPlatformCompat mPlatformCompat;
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index db3a192..2581daa 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -74,7 +74,6 @@
private static final String TAG = AppPredictor.class.getSimpleName();
-
private final IPredictionManager mPredictionManager;
private final CloseGuard mCloseGuard = CloseGuard.get();
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
@@ -82,8 +81,6 @@
private final AppPredictionSessionId mSessionId;
private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
- private final IBinder mToken = new Binder();
-
/**
* Creates a new Prediction client.
* <p>
@@ -99,7 +96,7 @@
mSessionId = new AppPredictionSessionId(
context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
try {
- mPredictionManager.createPredictionSession(predictionContext, mSessionId, mToken);
+ mPredictionManager.createPredictionSession(predictionContext, mSessionId, getToken());
} catch (RemoteException e) {
Log.e(TAG, "Failed to create predictor", e);
e.rethrowAsRuntimeException();
@@ -324,4 +321,12 @@
}
}
}
+
+ private static class Token {
+ static final IBinder sBinder = new Binder(TAG);
+ }
+
+ private static IBinder getToken() {
+ return Token.sBinder;
+ }
}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 076dbef..03d6e27 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -30,7 +30,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.BaseBundle;
import android.os.Bundle;
@@ -58,7 +57,6 @@
private ActivityInfo mInfo;
private Configuration mCurConfig;
private Configuration mOverrideConfig;
- private CompatibilityInfo mCompatInfo;
private String mReferrer;
private IVoiceInteractor mVoiceInteractor;
private int mProcState;
@@ -94,7 +92,7 @@
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
- mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
+ mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
mTaskFragmentToken);
@@ -115,7 +113,7 @@
/** Obtain an instance initialized with provided params. */
public static LaunchActivityItem obtain(Intent intent, int ident, ActivityInfo info,
- Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ Configuration curConfig, Configuration overrideConfig,
String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -126,7 +124,7 @@
if (instance == null) {
instance = new LaunchActivityItem();
}
- setValues(instance, intent, ident, info, curConfig, overrideConfig, compatInfo, referrer,
+ setValues(instance, intent, ident, info, curConfig, overrideConfig, referrer,
voiceInteractor, procState, state, persistentState, pendingResults,
pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
activityClientController, shareableActivityToken,
@@ -137,7 +135,7 @@
@Override
public void recycle() {
- setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
+ setValues(this, null, 0, null, null, null, null, null, 0, null, null, null, null,
null, false, null, null, null, null, false, null);
ObjectPool.recycle(this);
}
@@ -153,7 +151,6 @@
dest.writeTypedObject(mInfo, flags);
dest.writeTypedObject(mCurConfig, flags);
dest.writeTypedObject(mOverrideConfig, flags);
- dest.writeTypedObject(mCompatInfo, flags);
dest.writeString(mReferrer);
dest.writeStrongInterface(mVoiceInteractor);
dest.writeInt(mProcState);
@@ -175,8 +172,7 @@
private LaunchActivityItem(Parcel in) {
setValues(this, in.readTypedObject(Intent.CREATOR), in.readInt(),
in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
- in.readTypedObject(Configuration.CREATOR),
- in.readTypedObject(CompatibilityInfo.CREATOR), in.readString(),
+ in.readTypedObject(Configuration.CREATOR), in.readString(),
IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
in.readBundle(getClass().getClassLoader()),
in.readPersistableBundle(getClass().getClassLoader()),
@@ -216,7 +212,6 @@
return intentsEqual && mIdent == other.mIdent
&& activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
&& Objects.equals(mOverrideConfig, other.mOverrideConfig)
- && Objects.equals(mCompatInfo, other.mCompatInfo)
&& Objects.equals(mReferrer, other.mReferrer)
&& mProcState == other.mProcState && areBundlesEqualRoughly(mState, other.mState)
&& areBundlesEqualRoughly(mPersistentState, other.mPersistentState)
@@ -237,7 +232,6 @@
result = 31 * result + mIdent;
result = 31 * result + Objects.hashCode(mCurConfig);
result = 31 * result + Objects.hashCode(mOverrideConfig);
- result = 31 * result + Objects.hashCode(mCompatInfo);
result = 31 * result + Objects.hashCode(mReferrer);
result = 31 * result + Objects.hashCode(mProcState);
result = 31 * result + getRoughBundleHashCode(mState);
@@ -292,7 +286,7 @@
// Using the same method to set and clear values to make sure we don't forget anything
private static void setValues(LaunchActivityItem instance, Intent intent, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
- CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+ String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo,
@@ -303,7 +297,6 @@
instance.mInfo = info;
instance.mCurConfig = curConfig;
instance.mOverrideConfig = overrideConfig;
- instance.mCompatInfo = compatInfo;
instance.mReferrer = referrer;
instance.mVoiceInteractor = voiceInteractor;
instance.mProcState = procState;
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
index b523be2..3658ffc 100644
--- a/core/java/android/app/smartspace/SmartspaceSession.java
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -83,7 +83,6 @@
private final SmartspaceSessionId mSessionId;
private final ArrayMap<OnTargetsAvailableListener, CallbackWrapper> mRegisteredCallbacks =
new ArrayMap<>();
- private final IBinder mToken = new Binder();
/**
* Creates a new Smartspace ui client.
@@ -101,7 +100,7 @@
mSessionId = new SmartspaceSessionId(
context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUser());
try {
- mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, mToken);
+ mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, getToken());
} catch (RemoteException e) {
Log.e(TAG, "Failed to create Smartspace session", e);
e.rethrowFromSystemServer();
@@ -283,4 +282,12 @@
}
}
}
+
+ private static class Token {
+ static final IBinder sBinder = new Binder(TAG);
+ }
+
+ private static IBinder getToken() {
+ return Token.sBinder;
+ }
}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index cc78f1a..4295517 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -44,6 +44,7 @@
UsageEvents queryEventsForPackageForUser(long beginTime, long endTime, int userId, String pkg, String callingPackage);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
void setAppInactive(String packageName, boolean inactive, int userId);
+ boolean isAppStandbyEnabled();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isAppInactive(String packageName, int userId, String callingPackage);
void onCarrierPrivilegedAppsChanged();
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 1dfc7d4..4b0ca28 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -642,6 +642,19 @@
}
/**
+ * Returns whether the app standby bucket feature is enabled.
+ * @hide
+ */
+ @TestApi
+ public boolean isAppStandbyEnabled() {
+ try {
+ return mService.isAppStandbyEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns whether the specified app is currently considered inactive. This will be true if the
* app hasn't been used directly or indirectly for a period of time defined by the system. This
* could be of the order of several hours or days. Apps are not considered inactive when the
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index fd94969..269ca89 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -586,6 +586,9 @@
* The extras can be used to embed additional information about this widget to be accessed
* by the associated widget's AppWidgetProvider.
*
+ * <p>
+ * The new options are merged into existing options using {@link Bundle#putAll} semantics.
+ *
* @see #getAppWidgetOptions(int)
*
* @param appWidgetId The AppWidget instances for which to set the RemoteViews.
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index 3344ebc..e7e8ef9 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -127,7 +127,8 @@
/**
* Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED}
- * broadcast when this widget has been layed out at a new size.
+ * broadcast when this widget has been layed out at a new size or its options changed via
+ * {@link AppWidgetManager#updateAppWidgetOptions}.
*
* {@more}
*
@@ -136,7 +137,7 @@
* @param appWidgetManager A {@link AppWidgetManager} object you can call {@link
* AppWidgetManager#updateAppWidget} on.
* @param appWidgetId The appWidgetId of the widget whose size changed.
- * @param newOptions The appWidgetId of the widget whose size changed.
+ * @param newOptions The new options of the changed widget.
*
* @see AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED
*/
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index da486ee..0c171ad 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -19,7 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.Process.myUserHandle;
-import static android.os.Trace.TRACE_TAG_DATABASE;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -278,7 +278,7 @@
// Return an empty cursor for all columns.
return new MatrixCursor(cursor.getColumnNames(), 0);
}
- traceBegin(TRACE_TAG_DATABASE, "query: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "query: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -289,7 +289,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -298,13 +298,13 @@
// getCallingPackage() isn't available in getType(), as the javadoc states.
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getType: ", uri.getAuthority());
try {
return mInterface.getType(uri);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -336,7 +336,7 @@
setCallingAttributionSource(original);
}
}
- traceBegin(TRACE_TAG_DATABASE, "insert: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "insert: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -345,7 +345,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -358,7 +358,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- traceBegin(TRACE_TAG_DATABASE, "bulkInsert: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "bulkInsert: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -367,7 +367,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -404,7 +404,7 @@
}
}
}
- traceBegin(TRACE_TAG_DATABASE, "applyBatch: ", authority);
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "applyBatch: ", authority);
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -423,7 +423,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -436,7 +436,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- traceBegin(TRACE_TAG_DATABASE, "delete: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "delete: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -445,7 +445,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -458,7 +458,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return 0;
}
- traceBegin(TRACE_TAG_DATABASE, "update: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "update: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -467,7 +467,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -478,7 +478,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, mode);
- traceBegin(TRACE_TAG_DATABASE, "openFile: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -488,7 +488,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -499,7 +499,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, mode);
- traceBegin(TRACE_TAG_DATABASE, "openAssetFile: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openAssetFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -509,7 +509,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -518,7 +518,7 @@
String method, @Nullable String arg, @Nullable Bundle extras) {
validateIncomingAuthority(authority);
Bundle.setDefusable(extras, true);
- traceBegin(TRACE_TAG_DATABASE, "call: ", authority);
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "call: ", authority);
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -527,7 +527,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -536,13 +536,13 @@
// getCallingPackage() isn't available in getType(), as the javadoc states.
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- traceBegin(TRACE_TAG_DATABASE, "getStreamTypes: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "getStreamTypes: ", uri.getAuthority());
try {
return mInterface.getStreamTypes(uri, mimeTypeFilter);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -554,7 +554,7 @@
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
enforceFilePermission(attributionSource, uri, "r");
- traceBegin(TRACE_TAG_DATABASE, "openTypedAssetFile: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "openTypedAssetFile: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -564,7 +564,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -582,7 +582,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return null;
}
- traceBegin(TRACE_TAG_DATABASE, "canonicalize: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "canonicalize: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -591,7 +591,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -618,7 +618,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return null;
}
- traceBegin(TRACE_TAG_DATABASE, "uncanonicalize: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "uncanonicalize: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -627,7 +627,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -654,7 +654,7 @@
!= PermissionChecker.PERMISSION_GRANTED) {
return false;
}
- traceBegin(TRACE_TAG_DATABASE, "refresh: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "refresh: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -662,7 +662,7 @@
CancellationSignal.fromTransport(cancellationSignal));
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
@@ -671,7 +671,7 @@
int uid, int modeFlags) {
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
- traceBegin(TRACE_TAG_DATABASE, "checkUriPermission: ", uri.getAuthority());
+ traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "checkUriPermission: ", uri.getAuthority());
final AttributionSource original = setCallingAttributionSource(
attributionSource);
try {
@@ -680,7 +680,7 @@
throw e.rethrowAsRuntimeException();
} finally {
setCallingAttributionSource(original);
- Trace.traceEnd(TRACE_TAG_DATABASE);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ccc2441..dd517c9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10422,7 +10422,7 @@
private static final PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>
sApplicationInfoCache =
new PropertyInvalidatedCache<ApplicationInfoQuery, ApplicationInfo>(
- 16, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getApplicationInfo") {
@Override
public ApplicationInfo recompute(ApplicationInfoQuery query) {
@@ -10523,7 +10523,7 @@
private static final PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>
sPackageInfoCache =
new PropertyInvalidatedCache<PackageInfoQuery, PackageInfo>(
- 32, PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ 64, PermissionManager.CACHE_KEY_PACKAGE_INFO,
"getPackageInfo") {
@Override
public PackageInfo recompute(PackageInfoQuery query) {
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 608e34b..e146bb9 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -551,12 +551,7 @@
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
- inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
- inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
- final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
- if (appBounds != null) {
- appBounds.scale(invertedRatio);
- }
+ inoutConfig.windowConfiguration.scale(invertedRatio);
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 3dedc41..f47c1e0 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1974,10 +1974,10 @@
return true;
}
int diff = other.seq - seq;
- if (diff > 0x10000) {
+ if (Math.abs(diff) > 0x10000000) {
// If there has been a sufficiently large jump, assume the
// sequence has wrapped around.
- return false;
+ return diff < 0;
}
return diff > 0;
}
diff --git a/core/java/android/database/TEST_MAPPING b/core/java/android/database/TEST_MAPPING
new file mode 100644
index 0000000..4a7fa66
--- /dev/null
+++ b/core/java/android/database/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsDatabaseTestCases"
+ }
+ ]
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 336ef7a..41822e7 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1693,8 +1693,10 @@
((i != idx) || notifyCurrentIndex)) {
TotalCaptureResult result = previewMap.valueAt(i).second;
Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
- mCaptureResultHandler.onCaptureCompleted(timestamp,
- initializeFilteredResults(result));
+ if (mCaptureResultHandler != null) {
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+ }
Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
final long ident = Binder.clearCallingIdentity();
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index e5b9cdb..26415d3 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -920,15 +920,15 @@
// UH res YUV / RAW / JPEG + YUV preview size stream
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.FULL_RES),
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)},
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW)},
"No-viewfinder Ultra high resolution YUV image capture with image analysis"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.FULL_RES),
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)},
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW)},
"No-viewfinder Ultra high resolution RAW_SENSOR image capture with image analysis"),
new StreamCombinationTemplate(new StreamTemplate [] {
new StreamTemplate(ImageFormat.JPEG, SizeThreshold.FULL_RES),
- new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW)},
+ new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW)},
"No-viewfinder Ultra high resolution JPEG image capture with image analysis"),
// UH res YUV / RAW / JPEG + PRIV preview + PRIV RECORD stream
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index e960df1..3d59387 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -80,6 +80,7 @@
private static final int DO_START_STYLUS_HANDWRITING = 110;
private static final int DO_INIT_INK_WINDOW = 120;
private static final int DO_FINISH_STYLUS_HANDWRITING = 130;
+ private static final int DO_UPDATE_TOOL_TYPE = 140;
final WeakReference<InputMethodServiceInternal> mTarget;
final Context mContext;
@@ -234,6 +235,10 @@
inputMethod.canStartStylusHandwriting(msg.arg1);
return;
}
+ case DO_UPDATE_TOOL_TYPE: {
+ inputMethod.updateEditorToolType(msg.arg1);
+ return;
+ }
case DO_START_STYLUS_HANDWRITING: {
final SomeArgs args = (SomeArgs) msg.obj;
inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
@@ -402,6 +407,14 @@
@BinderThread
@Override
+ public void updateEditorToolType(int toolType)
+ throws RemoteException {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageI(DO_UPDATE_TOOL_TYPE, toolType));
+ }
+
+ @BinderThread
+ @Override
public void startStylusHandwriting(int requestId, @NonNull InputChannel channel,
@Nullable List<MotionEvent> stylusEvents)
throws RemoteException {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index b108490..c2027b1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -953,6 +953,15 @@
* @hide
*/
@Override
+ public void updateEditorToolType(int toolType) {
+ onUpdateEditorToolType(toolType);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
public void canStartStylusHandwriting(int requestId) {
if (DEBUG) Log.v(TAG, "canStartStylusHandwriting()");
if (mHandwritingRequestId.isPresent()) {
@@ -3012,11 +3021,14 @@
* not call to inform the IME of this interaction.
* @param focusChanged true if the user changed the focused view by this click.
* @see InputMethodManager#viewClicked(View)
+ * @see #onUpdateEditorToolType(int)
* @deprecated The method may not be called for composite {@link View} that works as a giant
* "Canvas", which can host its own UI hierarchy and sub focus state.
* {@link android.webkit.WebView} is a good example. Application / IME developers
* should not rely on this method. If your goal is just being notified when an
* on-going input is interrupted, simply monitor {@link #onFinishInput()}.
+ * If your goal is to know what {@link MotionEvent#getToolType(int)} clicked on
+ * editor, use {@link #onUpdateEditorToolType(int)} instead.
*/
@Deprecated
public void onViewClicked(boolean focusChanged) {
@@ -3024,6 +3036,19 @@
}
/**
+ * Called when the user tapped or clicked an {@link android.widget.Editor}.
+ * This can be useful when IME makes a decision of showing Virtual keyboard based on what
+ * {@link MotionEvent#getToolType(int)} was used to click the editor.
+ * e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a
+ * companion widget instead of normal virtual keyboard.
+ * <p> Default implementation does nothing. </p>
+ * @param toolType what {@link MotionEvent#getToolType(int)} was used to click on editor.
+ */
+ public void onUpdateEditorToolType(int toolType) {
+ // Intentionally empty
+ }
+
+ /**
* Called when the application has reported a new location of its text
* cursor. This is only called if explicitly requested by the input method.
* The default implementation does nothing.
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 4fe6524..f1e3ab0 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -53,6 +53,7 @@
float getBrightnessConstraint(int constraint);
@UnsupportedAppUsage
boolean isInteractive();
+ boolean areAutoPowerSaveModesEnabled();
boolean isPowerSaveMode();
PowerSaveState getPowerSaveState(int serviceType);
boolean setPowerSaveModeEnabled(boolean mode);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index aa61558..d553132 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -767,7 +767,7 @@
}
/**
- * Set the bytes in data to be the raw bytes of this Parcel.
+ * Fills the raw bytes of this Parcel with the supplied data.
*/
public final void unmarshall(@NonNull byte[] data, int offset, int length) {
nativeUnmarshall(mNativePtr, data, offset, length);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3063d3a..01b75d1 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1810,6 +1810,21 @@
}
/**
+ * Returns true if the platform has auto power save modes (eg. Doze & app standby) enabled.
+ * This doesn't necessarily mean that the individual features are enabled. For example, if this
+ * returns true, Doze might be enabled while app standby buckets remain disabled.
+ * @hide
+ */
+ @TestApi
+ public boolean areAutoPowerSaveModesEnabled() {
+ try {
+ return mService.areAutoPowerSaveModesEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns true if the device is currently in power save mode. When in this mode,
* applications should reduce their functionality in order to conserve battery as
* much as possible. You can monitor for changes to this state with
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 063762c7..9cd2325 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -761,6 +761,13 @@
*/
public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
+ /**
+ * Namespace for wear OS platform features.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_WEAR = "wear";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6fc2811..9000899 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14257,7 +14257,7 @@
*
* @hide
*/
- public static final int DEFAULT_ENABLE_TARE = 0;
+ public static final int DEFAULT_ENABLE_TARE = 1;
/**
* Whether to enable the TARE AlarmManager economic policy or not.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 02176b0..fb52ed2 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -261,6 +261,10 @@
/**
* Notification was canceled when entering lockdown mode, which turns off
* Smart Lock, fingerprint unlocking, and notifications on the lock screen.
+ * All the listeners shall ensure the canceled notifications are indeed removed
+ * on their end to prevent data leaking.
+ * When the user exits the lockdown mode, the removed notifications (due to lockdown)
+ * will be restored via NotificationListeners#notifyPostedLocked()
*/
public static final int REASON_LOCKDOWN = 23;
diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
index b20dccc..c47fdd3 100644
--- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java
+++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
@@ -16,7 +16,9 @@
package android.service.voice;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.IBinder;
@@ -32,9 +34,12 @@
* Start a new voice interaction session when requested from within an activity
* by Activity.startLocalVoiceInteraction()
* @param callingActivity The binder token representing the calling activity.
- * @param options
+ * @param attributionTag The attribution tag of the calling context or {@code null} for default
+ * attribution
+ * @param options A Bundle of private arguments to the current voice interaction service
*/
- public abstract void startLocalVoiceInteraction(IBinder callingActivity, Bundle options);
+ public abstract void startLocalVoiceInteraction(@NonNull IBinder callingActivity,
+ @Nullable String attributionTag, @NonNull Bundle options);
/**
* Returns whether the currently selected voice interaction service supports local voice
@@ -65,6 +70,13 @@
public abstract HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity();
/**
+ * Called by {@code UMS.convertPreCreatedUserIfPossible()} when a new user is not created from
+ * scratched, but converted from the pool of existing pre-created users.
+ */
+ // TODO(b/226201975): remove method once RoleService supports pre-created users
+ public abstract void onPreCreatedUserConversion(@UserIdInt int userId);
+
+ /**
* Provides the uids of the currently active
* {@link android.service.voice.HotwordDetectionService} and its owning package. The
* HotwordDetectionService is an isolated service, so it has a separate uid.
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1170237..1b46107 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -242,7 +242,7 @@
throw new IllegalStateException("Not available until onReady() is called");
}
try {
- mSystemService.showSession(args, flags);
+ mSystemService.showSession(args, flags, getAttributionTag());
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 42500b4..d08a67e 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -84,12 +84,12 @@
/**
* An active voice interaction session, providing a facility for the implementation
- * to interact with the user in the voice interaction layer. The user interface is
- * initially shown by default, and can be created be overriding {@link #onCreateContentView()}
+ * to interact with the user in the voice interaction layer. The user interface is
+ * initially shown by default, and can be created by overriding {@link #onCreateContentView()}
* in which the UI can be built.
*
* <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
- * when done. It can also initiate voice interactions with applications by calling
+ * when done. It can also initiate voice interactions with applications by calling
* {@link #startVoiceActivity}</p>.
*/
public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCallbacks2 {
@@ -1384,7 +1384,8 @@
throw new IllegalStateException("Can't call before onCreate()");
}
try {
- mSystemService.showSessionFromSession(mToken, args, flags);
+ mSystemService.showSessionFromSession(mToken, args, flags,
+ mContext.getAttributionTag());
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 46ca8f7..4fbf4b5 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1133,7 +1133,7 @@
if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel,
- mInsetsState, mTempControls, new Rect()) < 0) {
+ mInsetsState, mTempControls, new Rect(), new float[1]) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 95adb77..4efc838 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -1312,6 +1313,101 @@
}
/**
+ * Return the characters' bounds in the given range. The {@code bounds} array will be filled
+ * starting from {@code boundsStart} (inclusive). The coordinates are in local text layout.
+ *
+ * @param start the start index to compute the character bounds, inclusive.
+ * @param end the end index to compute the character bounds, exclusive.
+ * @param bounds the array to fill in the character bounds. The array is divided into segments
+ * of four where each index in that segment represents left, top, right and
+ * bottom of the character.
+ * @param boundsStart the inclusive start index in the array to start filling in the values
+ * from.
+ *
+ * @throws IndexOutOfBoundsException if the range defined by {@code start} and {@code end}
+ * exceeds the range of the text, or {@code bounds} doesn't have enough space to store the
+ * result.
+ * @throws IllegalArgumentException if {@code bounds} is null.
+ */
+ public void fillCharacterBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
+ @NonNull float[] bounds, @IntRange(from = 0) int boundsStart) {
+ if (start < 0 || end < start || end > mText.length()) {
+ throw new IndexOutOfBoundsException("given range: " + start + ", " + end + " is "
+ + "out of the text range: 0, " + mText.length());
+ }
+
+ if (bounds == null) {
+ throw new IllegalArgumentException("bounds can't be null.");
+ }
+
+ final int neededLength = 4 * (end - start);
+ if (neededLength > bounds.length - boundsStart) {
+ throw new IndexOutOfBoundsException("bounds doesn't have enough space to store the "
+ + "result, needed: " + neededLength + " had: "
+ + (bounds.length - boundsStart));
+ }
+
+ if (start == end) {
+ return;
+ }
+
+ final int startLine = getLineForOffset(start);
+ final int endLine = getLineForOffset(end - 1);
+ float[] horizontalBounds = null;
+ for (int line = startLine; line <= endLine; ++line) {
+ final int lineStart = getLineStart(line);
+ final int lineEnd = getLineEnd(line);
+ final int lineLength = lineEnd - lineStart;
+
+ final int dir = getParagraphDirection(line);
+ final boolean hasTab = getLineContainsTab(line);
+ final Directions directions = getLineDirections(line);
+
+ TabStops tabStops = null;
+ if (hasTab && mText instanceof Spanned) {
+ // Just checking this line should be good enough, tabs should be
+ // consistent across all lines in a paragraph.
+ TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, lineStart, lineEnd,
+ TabStopSpan.class);
+ if (tabs.length > 0) {
+ tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+ }
+ }
+
+ final TextLine tl = TextLine.obtain();
+ tl.set(mPaint, mText, lineStart, lineEnd, dir, directions, hasTab, tabStops,
+ getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
+ isFallbackLineSpacingEnabled());
+ if (horizontalBounds == null || horizontalBounds.length < 2 * lineLength) {
+ horizontalBounds = new float[2 * lineLength];
+ }
+
+ tl.measureAllBounds(horizontalBounds, null);
+ TextLine.recycle(tl);
+ final int lineLeft = getParagraphLeft(line);
+ final int lineRight = getParagraphRight(line);
+ final int lineStartPos = getLineStartPos(line, lineLeft, lineRight);
+
+ final int lineTop = getLineTop(line);
+ final int lineBottom = getLineBottom(line);
+
+ final int startIndex = Math.max(start, lineStart);
+ final int endIndex = Math.min(end, lineEnd);
+ for (int index = startIndex; index < endIndex; ++index) {
+ final int offset = index - lineStart;
+ final float left = horizontalBounds[offset * 2] + lineStartPos;
+ final float right = horizontalBounds[offset * 2 + 1] + lineStartPos;
+
+ final int boundsIndex = boundsStart + 4 * (index - start);
+ bounds[boundsIndex] = left;
+ bounds[boundsIndex + 1] = lineTop;
+ bounds[boundsIndex + 2] = right;
+ bounds[boundsIndex + 3] = lineBottom;
+ }
+ }
+ }
+
+ /**
* Get the leftmost position that should be exposed for horizontal
* scrolling on the specified line.
*/
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 57c1d23..6a54d23 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -16,6 +16,7 @@
package android.text;
+import android.annotation.NonNull;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -346,6 +347,100 @@
return false;
}
+ private static final char PARAGRAPH_SEPARATOR = '\n';
+
+ /**
+ * Move the cusrot to the closest paragraph start offset.
+ *
+ * @param text the spannable text
+ * @param layout layout to be used for drawing.
+ * @return true if the cursor is moved, otherwise false.
+ */
+ public static boolean moveToParagraphStart(@NonNull Spannable text, @NonNull Layout layout) {
+ int start = getSelectionStart(text);
+ int end = getSelectionEnd(text);
+
+ if (start != end) {
+ setSelection(text, chooseHorizontal(layout, -1, start, end));
+ return true;
+ } else {
+ int to = TextUtils.lastIndexOf(text, PARAGRAPH_SEPARATOR, start - 1);
+ if (to == -1) {
+ to = 0; // If not found, use the document start offset as a paragraph start.
+ }
+ if (to != end) {
+ setSelection(text, to);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Move the cursor to the closest paragraph end offset.
+ *
+ * @param text the spannable text
+ * @param layout layout to be used for drawing.
+ * @return true if the cursor is moved, otherwise false.
+ */
+ public static boolean moveToParagraphEnd(@NonNull Spannable text, @NonNull Layout layout) {
+ int start = getSelectionStart(text);
+ int end = getSelectionEnd(text);
+
+ if (start != end) {
+ setSelection(text, chooseHorizontal(layout, 1, start, end));
+ return true;
+ } else {
+ int to = TextUtils.indexOf(text, PARAGRAPH_SEPARATOR, end + 1);
+ if (to == -1) {
+ to = text.length();
+ }
+ if (to != end) {
+ setSelection(text, to);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Extend the selection to the closest paragraph start offset.
+ *
+ * @param text the spannable text
+ * @return true if the selection is extended, otherwise false
+ */
+ public static boolean extendToParagraphStart(@NonNull Spannable text) {
+ int end = getSelectionEnd(text);
+ int to = TextUtils.lastIndexOf(text, PARAGRAPH_SEPARATOR, end - 1);
+ if (to == -1) {
+ to = 0; // If not found, use the document start offset as a paragraph start.
+ }
+ if (to != end) {
+ extendSelection(text, to);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Extend the selection to the closest paragraph end offset.
+ *
+ * @param text the spannable text
+ * @return true if the selection is extended, otherwise false
+ */
+ public static boolean extendToParagraphEnd(@NonNull Spannable text) {
+ int end = getSelectionEnd(text);
+ int to = TextUtils.indexOf(text, PARAGRAPH_SEPARATOR, end + 1);
+ if (to == -1) {
+ to = text.length();
+ }
+ if (to != end) {
+ extendSelection(text, to);
+ return true;
+ }
+ return false;
+ }
+
/**
* Move the selection end to the buffer offset physically above
* the current selection end.
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 57fe131..d3367d0 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -16,6 +16,7 @@
package android.text.method;
+import android.annotation.NonNull;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
@@ -222,6 +223,26 @@
}
@Override
+ public boolean previousParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendToParagraphStart(buffer);
+ } else {
+ return Selection.moveToParagraphStart(buffer, layout);
+ }
+ }
+
+ @Override
+ public boolean nextParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
+ final Layout layout = widget.getLayout();
+ if (isSelecting(buffer)) {
+ return Selection.extendToParagraphEnd(buffer);
+ } else {
+ return Selection.moveToParagraphEnd(buffer, layout);
+ }
+ }
+
+ @Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1;
int initialScrollY = -1;
diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java
index 155a2c4..7a4b3a0 100644
--- a/core/java/android/text/method/BaseMovementMethod.java
+++ b/core/java/android/text/method/BaseMovementMethod.java
@@ -16,6 +16,7 @@
package android.text.method;
+import android.annotation.NonNull;
import android.text.Layout;
import android.text.Spannable;
import android.view.InputDevice;
@@ -190,6 +191,9 @@
} else if (KeyEvent.metaStateHasModifiers(movementMetaState,
KeyEvent.META_ALT_ON)) {
return top(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return previousParagraph(widget, buffer);
}
break;
@@ -199,6 +203,9 @@
} else if (KeyEvent.metaStateHasModifiers(movementMetaState,
KeyEvent.META_ALT_ON)) {
return bottom(widget, buffer);
+ } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
+ KeyEvent.META_CTRL_ON)) {
+ return nextParagraph(widget, buffer);
}
break;
@@ -399,6 +406,28 @@
return false;
}
+ /**
+ * Performs a previous paragraph movement action.
+ *
+ * @param widget the text view
+ * @param buffer the text buffer
+ * @return true if the event was handled, otherwise false.
+ */
+ public boolean previousParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
+ return false;
+ }
+
+ /**
+ * Performs a next paragraph movement action.
+ *
+ * @param widget the text view
+ * @param buffer the text buffer
+ * @return true if the event was handled, otherwise false.
+ */
+ public boolean nextParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
+ return false;
+ }
+
private int getTopLine(TextView widget) {
return widget.getLayout().getLineForVertical(widget.getScrollY());
}
diff --git a/core/java/android/view/IWindowFocusObserver.aidl b/core/java/android/view/IWindowFocusObserver.aidl
index d14bb48..3b23c77 100644
--- a/core/java/android/view/IWindowFocusObserver.aidl
+++ b/core/java/android/view/IWindowFocusObserver.aidl
@@ -16,7 +16,7 @@
package android.view;
/** {@hide} */
-interface IWindowFocusObserver
+oneway interface IWindowFocusObserver
{
void focusGained(IBinder inputToken);
void focusLost(IBinder inputToken);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 58a041a..8aa113d 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -958,4 +958,18 @@
* means the recents app can control the SystemUI flags, and vice-versa.
*/
void setRecentsAppBehindSystemBars(boolean behindSystemBars);
+
+ /**
+ * Gets the background color of the letterbox. Considered invalid if the background has
+ * multiple colors {@link #isLetterboxBackgroundMultiColored}. Should be called by SystemUI when
+ * computing the letterbox appearance for status bar treatment.
+ */
+ int getLetterboxBackgroundColorInArgb();
+
+ /**
+ * Whether the outer area of the letterbox has multiple colors (e.g. blurred background).
+ * Should be called by SystemUI when computing the letterbox appearance for status bar
+ * treatment.
+ */
+ boolean isLetterboxBackgroundMultiColored();
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index ef57b1d..3016473 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -50,15 +50,16 @@
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
out InputChannel outInputChannel, out InsetsState insetsState,
- out InsetsSourceControl[] activeControls, out Rect attachedFrame);
+ out InsetsSourceControl[] activeControls, out Rect attachedFrame,
+ out float[] sizeCompatScale);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId,
in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
out InsetsState insetsState, out InsetsSourceControl[] activeControls,
- out Rect attachedFrame);
+ out Rect attachedFrame, out float[] sizeCompatScale);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out InsetsState insetsState,
- out Rect attachedFrame);
+ out Rect attachedFrame, out float[] sizeCompatScale);
@UnsupportedAppUsage
void remove(IWindow window);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index b17e199..d63c25a 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -25,7 +25,6 @@
import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
-import static android.view.InsetsSourceControl.INVALID_HINTS;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.getDefaultVisibility;
import static android.view.InsetsState.toPublicType;
@@ -34,7 +33,6 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.graphics.Insets;
import android.graphics.Rect;
import android.util.ArraySet;
import android.util.Log;
@@ -94,13 +92,6 @@
private Rect mPendingVisibleFrame;
/**
- * Indicates if we have the pending animation. When we have the control, we need to play the
- * animation if the requested visibility is different from the current state. But if we haven't
- * had a leash yet, we will set this flag, and play the animation once we get the leash.
- */
- private boolean mIsAnimationPending;
-
- /**
* @param type The {@link InternalInsetsType} of the consumed insets.
* @param state The current {@link InsetsState} of the consumed insets.
* @param transactionSupplier The source of new {@link Transaction} instances. The supplier
@@ -138,7 +129,6 @@
}
return false;
}
- SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
final InsetsSourceControl lastControl = mSourceControl;
mSourceControl = control;
@@ -163,27 +153,21 @@
// For updateCompatSysUiVisibility
applyLocalVisibilityOverride();
} else {
- // We are gaining control, and need to run an animation since previous state
- // didn't match
final boolean requestedVisible = isRequestedVisibleAwaitingControl();
- final boolean fakeControl = INVALID_HINTS.equals(control.getInsetsHint());
- final boolean needsAnimation = requestedVisible != mState.getSource(mType).isVisible()
- && !fakeControl;
- if (control.getLeash() != null && (needsAnimation || mIsAnimationPending)) {
- if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b",
+ final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
+ final SurfaceControl newLeash = control.getLeash();
+ if (newLeash != null && (oldLeash == null || !newLeash.isSameSurface(oldLeash))
+ && requestedVisible != control.isInitiallyVisible()) {
+ // We are gaining leash, and need to run an animation since previous state
+ // didn't match.
+ if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b",
mController.getHost().getRootViewTitle(), requestedVisible));
if (requestedVisible) {
showTypes[0] |= toPublicType(getType());
} else {
hideTypes[0] |= toPublicType(getType());
}
- mIsAnimationPending = false;
} else {
- if (needsAnimation) {
- // We need animation but we haven't had a leash yet. Set this flag that when we
- // get the leash we can play the deferred animation.
- mIsAnimationPending = true;
- }
// We are gaining control, but don't need to run an animation.
// However make sure that the leash visibility is still up to date.
if (applyLocalVisibilityOverride()) {
@@ -195,7 +179,7 @@
applyRequestedVisibilityToControl();
// Remove the surface that owned by last control when it lost.
- if (!requestedVisible && !mIsAnimationPending && lastControl == null) {
+ if (!requestedVisible && lastControl == null) {
removeSurface();
}
}
@@ -406,16 +390,6 @@
protected void setRequestedVisible(boolean requestedVisible) {
if (mRequestedVisible != requestedVisible) {
mRequestedVisible = requestedVisible;
-
- // We need an animation later if the leash of a real control (which has an insets hint)
- // is not ready. The !mIsAnimationPending check is in case that the requested visibility
- // is changed twice before playing the animation -- we don't need an animation in this
- // case.
- mIsAnimationPending = !mIsAnimationPending
- && mSourceControl != null
- && mSourceControl.getLeash() == null
- && !Insets.NONE.equals(mSourceControl.getInsetsHint());
-
mController.onRequestedVisibilityChanged(this);
if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 2cf827d..5f1cbba 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -31,6 +31,7 @@
import android.view.InsetsState.InternalInsetsType;
import java.io.PrintWriter;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -39,10 +40,9 @@
*/
public class InsetsSourceControl implements Parcelable {
- public static final Insets INVALID_HINTS = Insets.of(-1, -1, -1, -1);
-
private final @InternalInsetsType int mType;
private final @Nullable SurfaceControl mLeash;
+ private final boolean mInitiallyVisible;
private final Point mSurfacePosition;
// This is used while playing an insets animation regardless of the relative frame. This would
@@ -53,9 +53,10 @@
private int mParcelableFlags;
public InsetsSourceControl(@InternalInsetsType int type, @Nullable SurfaceControl leash,
- Point surfacePosition, Insets insetsHint) {
+ boolean initiallyVisible, Point surfacePosition, Insets insetsHint) {
mType = type;
mLeash = leash;
+ mInitiallyVisible = initiallyVisible;
mSurfacePosition = surfacePosition;
mInsetsHint = insetsHint;
}
@@ -67,6 +68,7 @@
} else {
mLeash = null;
}
+ mInitiallyVisible = other.mInitiallyVisible;
mSurfacePosition = new Point(other.mSurfacePosition);
mInsetsHint = other.mInsetsHint;
mSkipAnimationOnce = other.getAndClearSkipAnimationOnce();
@@ -75,6 +77,7 @@
public InsetsSourceControl(Parcel in) {
mType = in.readInt();
mLeash = in.readTypedObject(SurfaceControl.CREATOR);
+ mInitiallyVisible = in.readBoolean();
mSurfacePosition = in.readTypedObject(Point.CREATOR);
mInsetsHint = in.readTypedObject(Insets.CREATOR);
mSkipAnimationOnce = in.readBoolean();
@@ -94,6 +97,10 @@
return mLeash;
}
+ public boolean isInitiallyVisible() {
+ return mInitiallyVisible;
+ }
+
public boolean setSurfacePosition(int left, int top) {
if (mSurfacePosition.equals(left, top)) {
return false;
@@ -148,6 +155,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeTypedObject(mLeash, mParcelableFlags);
+ dest.writeBoolean(mInitiallyVisible);
dest.writeTypedObject(mSurfacePosition, mParcelableFlags);
dest.writeTypedObject(mInsetsHint, mParcelableFlags);
dest.writeBoolean(mSkipAnimationOnce);
@@ -172,6 +180,7 @@
return mType == that.mType
&& ((mLeash == thatLeash)
|| (mLeash != null && thatLeash != null && mLeash.isSameSurface(thatLeash)))
+ && mInitiallyVisible == that.mInitiallyVisible
&& mSurfacePosition.equals(that.mSurfacePosition)
&& mInsetsHint.equals(that.mInsetsHint)
&& mSkipAnimationOnce == that.mSkipAnimationOnce;
@@ -179,12 +188,8 @@
@Override
public int hashCode() {
- int result = mType;
- result = 31 * result + (mLeash != null ? mLeash.hashCode() : 0);
- result = 31 * result + mSurfacePosition.hashCode();
- result = 31 * result + mInsetsHint.hashCode();
- result = 31 * result + (mSkipAnimationOnce ? 1 : 0);
- return result;
+ return Objects.hash(mType, mLeash, mInitiallyVisible, mSurfacePosition, mInsetsHint,
+ mSkipAnimationOnce);
}
@Override
@@ -200,6 +205,7 @@
pw.print(prefix);
pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType));
pw.print(" mLeash="); pw.print(mLeash);
+ pw.print(" mInitiallyVisible="); pw.print(mInitiallyVisible);
pw.print(" mSurfacePosition="); pw.print(mSurfacePosition);
pw.print(" mInsetsHint="); pw.print(mInsetsHint);
pw.print(" mSkipAnimationOnce="); pw.print(mSkipAnimationOnce);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c198098..9f426a1 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -81,7 +81,9 @@
ITYPE_BOTTOM_MANDATORY_GESTURES,
ITYPE_LEFT_MANDATORY_GESTURES,
ITYPE_RIGHT_MANDATORY_GESTURES,
+ ITYPE_LEFT_TAPPABLE_ELEMENT,
ITYPE_TOP_TAPPABLE_ELEMENT,
+ ITYPE_RIGHT_TAPPABLE_ELEMENT,
ITYPE_BOTTOM_TAPPABLE_ELEMENT,
ITYPE_LEFT_DISPLAY_CUTOUT,
ITYPE_TOP_DISPLAY_CUTOUT,
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index a468951..0db28d4 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -45,6 +45,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
+import android.window.TaskSnapshot;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -234,6 +235,12 @@
*/
public @ColorInt int backgroundColor;
+ /**
+ * Whether the activity is going to show IME on the target window after the app transition.
+ * @see TaskSnapshot#hasImeSurface() that used the task snapshot during animating task.
+ */
+ public boolean willShowImeOnTarget;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
@@ -294,12 +301,21 @@
hasAnimatingParent = in.readBoolean();
backgroundColor = in.readInt();
showBackdrop = in.readBoolean();
+ willShowImeOnTarget = in.readBoolean();
}
public void setShowBackdrop(boolean shouldShowBackdrop) {
showBackdrop = shouldShowBackdrop;
}
+ public void setWillShowImeOnTarget(boolean showImeOnTarget) {
+ willShowImeOnTarget = showImeOnTarget;
+ }
+
+ public boolean willShowImeOnTarget() {
+ return willShowImeOnTarget;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -328,6 +344,7 @@
dest.writeBoolean(hasAnimatingParent);
dest.writeInt(backgroundColor);
dest.writeBoolean(showBackdrop);
+ dest.writeBoolean(willShowImeOnTarget);
}
public void dump(PrintWriter pw, String prefix) {
@@ -346,10 +363,11 @@
pw.print(prefix); pw.print("leash="); pw.println(leash);
pw.print(prefix); pw.print("taskInfo="); pw.println(taskInfo);
pw.print(prefix); pw.print("allowEnterPip="); pw.println(allowEnterPip);
- pw.print(prefix); pw.print("windowType="); pw.print(windowType);
- pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent);
- pw.print(prefix); pw.print("backgroundColor="); pw.print(backgroundColor);
- pw.print(prefix); pw.print("showBackdrop="); pw.print(showBackdrop);
+ pw.print(prefix); pw.print("windowType="); pw.println(windowType);
+ pw.print(prefix); pw.print("hasAnimatingParent="); pw.println(hasAnimatingParent);
+ pw.print(prefix); pw.print("backgroundColor="); pw.println(backgroundColor);
+ pw.print(prefix); pw.print("showBackdrop="); pw.println(showBackdrop);
+ pw.print(prefix); pw.print("willShowImeOnTarget="); pw.println(willShowImeOnTarget);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c1a5a3a2..e1966a0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -696,6 +696,8 @@
boolean mPendingAlwaysConsumeSystemBars;
private final InsetsState mTempInsets = new InsetsState();
private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
+ private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
+ private float mInvSizeCompatScale = 1f;
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
= new ViewTreeObserver.InternalInsetsInfo();
@@ -732,6 +734,8 @@
final PointF mDragPoint = new PointF();
final PointF mLastTouchPoint = new PointF();
int mLastTouchSource;
+ /** Tracks last {@link MotionEvent#getToolType(int)} with {@link MotionEvent#ACTION_UP}. **/
+ private int mLastClickToolType;
private boolean mProfileRendering;
private Choreographer.FrameCallback mRenderProfiler;
@@ -846,6 +850,8 @@
private int mLastTransformHint = Integer.MIN_VALUE;
+ private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
+
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1096,6 +1102,16 @@
return mContext.getResources().getConfiguration();
}
+ private WindowConfiguration getCompatWindowConfiguration() {
+ final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
+ if (mInvSizeCompatScale == 1f) {
+ return winConfig;
+ }
+ mTempWinConfig.setTo(winConfig);
+ mTempWinConfig.scale(mInvSizeCompatScale);
+ return mTempWinConfig;
+ }
+
/**
* We have one child
*/
@@ -1225,10 +1241,11 @@
controlInsetsForCompatibility(mWindowAttributes);
Rect attachedFrame = new Rect();
+ final float[] sizeCompatScale = { 1f };
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
- mTempControls, attachedFrame);
+ mTempControls, attachedFrame, sizeCompatScale);
if (!attachedFrame.isValid()) {
attachedFrame = null;
}
@@ -1238,6 +1255,8 @@
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
mTmpFrames.attachedFrame = attachedFrame;
+ mTmpFrames.sizeCompatScale = sizeCompatScale[0];
+ mInvSizeCompatScale = 1f / sizeCompatScale[0];
} catch (RemoteException e) {
mAdded = false;
mView = null;
@@ -1260,7 +1279,7 @@
final InsetsState state = mInsetsController.getState();
final Rect displayCutoutSafe = mTempRect;
state.getDisplayCutoutSafe(displayCutoutSafe);
- final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
+ final WindowConfiguration winConfig = getCompatWindowConfiguration();
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
@@ -1397,8 +1416,12 @@
if (registered) {
final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes(
mWindowAttributes);
- mAccessibilityManager.setAccessibilityWindowAttributes(getDisplayId(),
- mAttachInfo.mAccessibilityWindowId, attributes);
+ if (!attributes.equals(mAccessibilityWindowAttributes)) {
+ mAccessibilityWindowAttributes = attributes;
+ mAccessibilityManager.setAccessibilityWindowAttributes(getDisplayId(),
+ mAttachInfo.mAccessibilityWindowId, attributes);
+ }
+
}
}
@@ -1766,19 +1789,24 @@
mTranslator.translateRectInScreenToAppWindow(displayFrame);
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
+ final float sizeCompatScale = frames.sizeCompatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
final boolean attachedFrameChanged = LOCAL_LAYOUT
&& !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean resizeModeChanged = mResizeMode != resizeMode;
+ final boolean sizeCompatScaleChanged = mTmpFrames.sizeCompatScale != sizeCompatScale;
if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
- && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout) {
+ && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
+ && !sizeCompatScaleChanged) {
return;
}
mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
mResizeMode = resizeMode;
+ mTmpFrames.sizeCompatScale = sizeCompatScale;
+ mInvSizeCompatScale = 1f / sizeCompatScale;
if (configChanged) {
// If configuration changed - notify about that and, maybe, about move to display.
@@ -3783,7 +3811,6 @@
}
}
mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
- mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus);
// NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
// is lost, so we don't need to to force a flush - there might be other events such as
@@ -6392,11 +6419,16 @@
event.offsetLocation(0, mCurScrollY);
}
- // Remember the touch position for possible drag-initiation.
if (event.isTouchEvent()) {
+ // Remember the touch position for possible drag-initiation.
mLastTouchPoint.x = event.getRawX();
mLastTouchPoint.y = event.getRawY();
mLastTouchSource = event.getSource();
+
+ // Register last ACTION_UP. This will be propagated to IME.
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ mLastClickToolType = event.getToolType(event.getActionIndex());
+ }
}
return FORWARD;
}
@@ -7992,6 +8024,14 @@
return mLastTouchSource;
}
+ /**
+ * Used by InputMethodManager.
+ * @hide
+ */
+ public int getLastClickToolType() {
+ return mLastClickToolType;
+ }
+
public void setDragFocus(View newDragTarget, DragEvent event) {
if (mCurrentDragView != newDragTarget && !View.sCascadedDragDrop) {
// Send EXITED and ENTERED notifications to the old and new drag focus views.
@@ -8110,11 +8150,12 @@
if (maybeSyncSeqId > 0) {
mSyncSeqId = maybeSyncSeqId;
}
+ mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
- final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
+ final WindowConfiguration winConfig = getCompatWindowConfiguration();
WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
@@ -8217,7 +8258,7 @@
private void setFrame(Rect frame) {
mWinFrame.set(frame);
- final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
+ final WindowConfiguration winConfig = getCompatWindowConfiguration();
mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
? winConfig.getMaxBounds()
: frame);
@@ -10345,6 +10386,7 @@
!= AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
if (registered) {
mAttachInfo.mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ mAccessibilityWindowAttributes = null;
mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
}
}
@@ -10907,8 +10949,8 @@
private void registerCompatOnBackInvokedCallback() {
mCompatOnBackInvokedCallback = () -> {
- sendBackKeyEvent(KeyEvent.ACTION_DOWN);
- sendBackKeyEvent(KeyEvent.ACTION_UP);
+ sendBackKeyEvent(KeyEvent.ACTION_DOWN);
+ sendBackKeyEvent(KeyEvent.ACTION_UP);
};
mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatOnBackInvokedCallback);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 70a690c..6f69361 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -824,16 +824,20 @@
* provide consent for their app to allow OEMs to manually provide ActivityEmbedding split
* rule configuration on behalf of the app.
*
- * <p>If {@code true}, the system CAN override the windowing behaviors for the app, such as
+ * <p>If {@code true}, the system can override the windowing behaviors for the app, such as
* showing some activities side-by-side. In this case, it will report that ActivityEmbedding
* APIs are disabled for the app to avoid conflict.
*
- * <p>If {@code false}, the system MUST NOT override the window behavior for the app. It should
+ * <p>If {@code false}, the system can't override the window behavior for the app. It should
* be used if the app wants to provide their own ActivityEmbedding split rules, or if the app
* wants to opt-out of system overrides for any other reason.
*
* <p>Default is {@code false}.
*
+ * <p>The system enforcement is added in Android 14, but some devices may start following the
+ * requirement before that. The best practice for apps is to always explicitly set this
+ * property in AndroidManifest instead of relying on the default value.
+ *
* <p>Example usage:
* <pre>
* <application>
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 94da274..d55c838 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -149,7 +149,8 @@
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ float[] outSizeCompatScale) {
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(attrs.format)
.setBLASTLayer()
@@ -182,6 +183,7 @@
mStateForWindow.put(window.asBinder(), state);
}
outAttachedFrame.set(0, 0, -1, -1);
+ outSizeCompatScale[0] = 1f;
final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
WindowManagerGlobal.ADD_FLAG_USE_BLAST;
@@ -197,15 +199,18 @@
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ float[] outSizeCompatScale) {
return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
- outInputChannel, outInsetsState, outActiveControls, outAttachedFrame);
+ outInputChannel, outInsetsState, outActiveControls, outAttachedFrame,
+ outSizeCompatScale);
}
@Override
public int addToDisplayWithoutInputChannel(android.view.IWindow window,
android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
- android.view.InsetsState insetsState, Rect outAttachedFrame) {
+ android.view.InsetsState insetsState, Rect outAttachedFrame,
+ float[] outSizeCompatScale) {
return 0;
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index fdff7a3..55a2d22 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -30,6 +30,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.LocaleList;
@@ -40,6 +41,7 @@
import android.text.TextUtils;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;
+import android.view.MotionEvent;
import android.view.View;
import android.view.autofill.AutofillId;
@@ -564,6 +566,12 @@
@Nullable
private SurroundingText mInitialSurroundingText = null;
+ /**
+ * Initial {@link MotionEvent#ACTION_UP} tool type {@link MotionEvent#getToolType(int)} that
+ * was used to focus this editor.
+ */
+ private int mInitialToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
/**
* Editors may use this method to provide initial input text to IMEs. As the surrounding text
@@ -937,6 +945,31 @@
}
/**
+ * Returns the initial {@link MotionEvent#ACTION_UP} tool type
+ * {@link MotionEvent#getToolType(int)} responsible for focus on the current editor.
+ *
+ * @see #setInitialToolType(int)
+ * @see MotionEvent#getToolType(int)
+ * @see InputMethodService#onUpdateEditorToolType(int)
+ * @return toolType {@link MotionEvent#getToolType(int)}.
+ */
+ public int getInitialToolType() {
+ return mInitialToolType;
+ }
+
+ /**
+ * Set the initial {@link MotionEvent#ACTION_UP} tool type {@link MotionEvent#getToolType(int)}.
+ * that brought focus to the view.
+ *
+ * @see #getInitialToolType()
+ * @see MotionEvent#getToolType(int)
+ * @see InputMethodService#onUpdateEditorToolType(int)
+ */
+ public void setInitialToolType(int toolType) {
+ mInitialToolType = toolType;
+ }
+
+ /**
* Export the state of {@link EditorInfo} into a protocol buffer output stream.
*
* @param proto Stream to write the state to
@@ -972,6 +1005,7 @@
+ " actionId=" + actionId);
pw.println(prefix + "initialSelStart=" + initialSelStart
+ " initialSelEnd=" + initialSelEnd
+ + " initialToolType=" + mInitialToolType
+ " initialCapsMode=0x"
+ Integer.toHexString(initialCapsMode));
pw.println(prefix + "hintText=" + hintText
@@ -1006,6 +1040,7 @@
newEditorInfo.initialSelStart = initialSelStart;
newEditorInfo.initialSelEnd = initialSelEnd;
newEditorInfo.initialCapsMode = initialCapsMode;
+ newEditorInfo.mInitialToolType = mInitialToolType;
newEditorInfo.hintText = TextUtils.stringOrSpannedString(hintText);
newEditorInfo.label = TextUtils.stringOrSpannedString(label);
newEditorInfo.packageName = packageName;
@@ -1036,6 +1071,7 @@
dest.writeInt(initialSelStart);
dest.writeInt(initialSelEnd);
dest.writeInt(initialCapsMode);
+ dest.writeInt(mInitialToolType);
TextUtils.writeToParcel(hintText, dest, flags);
TextUtils.writeToParcel(label, dest, flags);
dest.writeString(packageName);
@@ -1072,6 +1108,7 @@
res.initialSelStart = source.readInt();
res.initialSelEnd = source.readInt();
res.initialCapsMode = source.readInt();
+ res.mInitialToolType = source.readInt();
res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
res.packageName = source.readString();
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
index 60edf35..429b0b8 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
@@ -111,10 +111,11 @@
@AnyThread
boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
- int flags, @Nullable ResultReceiver resultReceiver,
+ int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
try {
- return mTarget.showSoftInput(client, windowToken, flags, resultReceiver, reason);
+ return mTarget.showSoftInput(
+ client, windowToken, flags, lastClickToolType, resultReceiver, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -182,9 +183,9 @@
@AnyThread
@Nullable
- InputMethodSubtype getCurrentInputMethodSubtype() {
+ InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
try {
- return mTarget.getCurrentInputMethodSubtype();
+ return mTarget.getCurrentInputMethodSubtype(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -192,9 +193,9 @@
@AnyThread
void setAdditionalInputMethodSubtypes(@NonNull String imeId,
- @NonNull InputMethodSubtype[] subtypes) {
+ @NonNull InputMethodSubtype[] subtypes, @UserIdInt int userId) {
try {
- mTarget.setAdditionalInputMethodSubtypes(imeId, subtypes);
+ mTarget.setAdditionalInputMethodSubtypes(imeId, subtypes, userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 95add29..bfe6ae6 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -377,6 +377,15 @@
}
/**
+ * This method is called when the user tapped or clicked an {@link android.widget.Editor}.
+ * @param toolType {@link android.view.MotionEvent#getToolType(int)} used for clicking editor.
+ * @hide
+ */
+ default void updateEditorToolType(int toolType) {
+ // intentionally empty
+ }
+
+ /**
* Start stylus handwriting session.
* @hide
*/
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8ce134e7..0b4a298 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1929,6 +1929,7 @@
mClient,
view.getWindowToken(),
flags,
+ mCurRootView.getLastClickToolType(),
resultReceiver,
reason);
}
@@ -1960,6 +1961,7 @@
mClient,
mCurRootView.getView().getWindowToken(),
flags,
+ mCurRootView.getLastClickToolType(),
resultReceiver,
SoftInputShowHideReason.SHOW_SOFT_INPUT);
}
@@ -2339,6 +2341,9 @@
editorInfo.packageName = view.getContext().getOpPackageName();
editorInfo.autofillId = view.getAutofillId();
editorInfo.fieldId = view.getId();
+ synchronized (mH) {
+ editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
+ }
InputConnection ic = view.onCreateInputConnection(editorInfo);
if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
@@ -3374,7 +3379,7 @@
*/
@Nullable
public InputMethodSubtype getCurrentInputMethodSubtype() {
- return mServiceInvoker.getCurrentInputMethodSubtype();
+ return mServiceInvoker.getCurrentInputMethodSubtype(UserHandle.myUserId());
}
/**
@@ -3610,7 +3615,7 @@
@Deprecated
public void setAdditionalInputMethodSubtypes(@NonNull String imiId,
@NonNull InputMethodSubtype[] subtypes) {
- mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes);
+ mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes, UserHandle.myUserId());
}
@Nullable
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 9c63d56..121839b 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -666,14 +666,12 @@
/**
* Sort the list of InputMethodSubtype
- * @param context Context will be used for getting localized strings from IME
- * @param flags Flags for the sort order
* @param imi InputMethodInfo of which subtypes are subject to be sorted
* @param subtypeList List of InputMethodSubtype which will be sorted
* @return Sorted list of subtypes
* @hide
*/
- public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi,
+ public static List<InputMethodSubtype> sort(InputMethodInfo imi,
List<InputMethodSubtype> subtypeList) {
if (imi == null) return subtypeList;
final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
index cbc690e..f32d3fa 100644
--- a/core/java/android/view/inspector/PropertyMapper.java
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -18,6 +18,7 @@
import android.annotation.AttrRes;
import android.annotation.NonNull;
+import android.content.res.Resources;
import java.util.Set;
import java.util.function.IntFunction;
@@ -30,6 +31,10 @@
* Mapping properties to IDs enables quick comparisons against shadow copies of inspectable
* objects without performing a large number of string comparisons.
*
+ * Properties that derive their value from an XML attribute should provide the attribute resource
+ * ID (e.g.: {@code R.attr.color}). For runtime or generated properties properties without
+ * attribute IDs, supply {@link Resources#ID_NULL} for {@code attributeId}.
+ *
* @see InspectionCompanion#mapProperties(PropertyMapper)
*/
public interface PropertyMapper {
@@ -37,7 +42,7 @@
* Map a string name to an integer ID for a primitive boolean property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -47,7 +52,7 @@
* Map a string name to an integer ID for a primitive byte property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -57,7 +62,7 @@
* Map a string name to an integer ID for a primitive char property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -67,7 +72,7 @@
* Map a string name to an integer ID for a primitive double property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -77,7 +82,7 @@
* Map a string name to an integer ID for a primitive float property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -87,7 +92,7 @@
* Map a string name to an integer ID for a primitive int property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -97,7 +102,7 @@
* Map a string name to an integer ID for a primitive long property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -107,7 +112,7 @@
* Map a string name to an integer ID for a primitive short property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -117,7 +122,7 @@
* Map a string name to an integer ID for an object property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -127,7 +132,7 @@
* Map a string name to an integer ID for a color property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
* @see android.graphics.Color
@@ -138,7 +143,7 @@
* Map a string name to an integer ID for a gravity property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
* @see android.view.Gravity
@@ -149,7 +154,7 @@
* Map a string name to an integer ID for an enumeration packed into an int property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @param mapping A mapping from int to String
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
@@ -163,7 +168,7 @@
* Map a string name to an integer ID for an attribute that contains resource IDs.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
@@ -173,7 +178,7 @@
* Map a string name to an integer ID for a flag set packed into an int property.
*
* @param name The name of the property
- * @param attributeId If the property is from an XML attribute, the resource ID of the property
+ * @param attributeId The attribute resource ID of this property, or {@link Resources#ID_NULL}
* @param mapping A mapping from int to a set of strings
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
@@ -182,6 +187,7 @@
@NonNull String name,
@AttrRes int attributeId,
@NonNull IntFunction<Set<String>> mapping);
+
/**
* Thrown from a map method if a property name is already mapped as different type.
*/
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index fb298c7..b233e54 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -709,7 +709,7 @@
}
getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
- resumeBlink();
+ makeBlink();
}
void onDetachedFromWindow() {
@@ -1685,17 +1685,12 @@
void onWindowFocusChanged(boolean hasWindowFocus) {
if (hasWindowFocus) {
- if (mBlink != null) {
- mBlink.uncancel();
- makeBlink();
- }
+ resumeBlink();
if (mTextView.hasSelection() && !extractedTextModeWillBeStarted()) {
refreshTextActionMode();
}
} else {
- if (mBlink != null) {
- mBlink.cancel();
- }
+ suspendBlink();
if (mInputContentType != null) {
mInputContentType.enterDown = false;
}
@@ -2851,7 +2846,8 @@
* @return True when the TextView isFocused and has a valid zero-length selection (cursor).
*/
private boolean shouldBlink() {
- if (!isCursorVisible() || !mTextView.isFocused()) return false;
+ if (!isCursorVisible() || !mTextView.isFocused()
+ || mTextView.getWindowVisibility() != mTextView.VISIBLE) return false;
final int start = mTextView.getSelectionStart();
if (start < 0) return false;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b7bb091..fe0cbcb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12530,64 +12530,38 @@
public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
int startIndex, int endIndex, float viewportToContentHorizontalOffset,
float viewportToContentVerticalOffset) {
- final int minLine = mLayout.getLineForOffset(startIndex);
- final int maxLine = mLayout.getLineForOffset(endIndex - 1);
final Rect rect = new Rect();
getLocalVisibleRect(rect);
final RectF visibleRect = new RectF(rect);
- for (int line = minLine; line <= maxLine; ++line) {
- final int lineStart = mLayout.getLineStart(line);
- final int lineEnd = mLayout.getLineEnd(line);
- final int offsetStart = Math.max(lineStart, startIndex);
- final int offsetEnd = Math.min(lineEnd, endIndex);
- final boolean ltrLine =
- mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
- final float[] widths = new float[offsetEnd - offsetStart];
- mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
- final float top = mLayout.getLineTop(line);
- final float bottom = mLayout.getLineBottom(line);
- for (int offset = offsetStart; offset < offsetEnd; ++offset) {
- final float charWidth = widths[offset - offsetStart];
- final boolean isRtl = mLayout.isRtlCharAt(offset);
- // TODO: This doesn't work perfectly for text with custom styles and
- // TAB chars.
- final float left;
- if (ltrLine) {
- if (isRtl) {
- left = mLayout.getSecondaryHorizontal(offset) - charWidth;
- } else {
- left = mLayout.getPrimaryHorizontal(offset);
- }
- } else {
- if (!isRtl) {
- left = mLayout.getSecondaryHorizontal(offset);
- } else {
- left = mLayout.getPrimaryHorizontal(offset) - charWidth;
- }
- }
- final float right = left + charWidth;
- // TODO: Check top-right and bottom-left as well.
- final float localLeft = left + viewportToContentHorizontalOffset;
- final float localRight = right + viewportToContentHorizontalOffset;
- final float localTop = top + viewportToContentVerticalOffset;
- final float localBottom = bottom + viewportToContentVerticalOffset;
- final boolean isTopLeftVisible = visibleRect.contains(localLeft, localTop);
- final boolean isBottomRightVisible =
- visibleRect.contains(localRight, localBottom);
- int characterBoundsFlags = 0;
- if (isTopLeftVisible || isBottomRightVisible) {
- characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
- }
- if (!isTopLeftVisible || !isBottomRightVisible) {
- characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
- }
- if (isRtl) {
- characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
- }
- // Here offset is the index in Java chars.
- builder.addCharacterBounds(offset, localLeft, localTop, localRight,
- localBottom, characterBoundsFlags);
+
+ final float[] characterBounds = new float[4 * (endIndex - startIndex)];
+ mLayout.fillCharacterBounds(startIndex, endIndex, characterBounds, 0);
+ final int limit = endIndex - startIndex;
+ for (int offset = 0; offset < limit; ++offset) {
+ final float left =
+ characterBounds[offset * 4] + viewportToContentHorizontalOffset;
+ final float top =
+ characterBounds[offset * 4 + 1] + viewportToContentVerticalOffset;
+ final float right =
+ characterBounds[offset * 4 + 2] + viewportToContentHorizontalOffset;
+ final float bottom =
+ characterBounds[offset * 4 + 3] + viewportToContentVerticalOffset;
+
+ final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom);
+ final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom);
+ int characterBoundsFlags = 0;
+ if (hasVisibleRegion) {
+ characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
}
+ if (hasInVisibleRegion) {
+ characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+ }
+
+ if (mLayout.isRtlCharAt(offset)) {
+ characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
+ }
+ builder.addCharacterBounds(offset + startIndex, left, top, right, bottom,
+ characterBoundsFlags);
}
}
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 0ab6db5..dd49014 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -118,7 +118,7 @@
* back preview.
* @param onBackInvokedCallback The back callback registered by the current top level window.
*/
- public BackNavigationInfo(@BackTargetType int type,
+ private BackNavigationInfo(@BackTargetType int type,
@Nullable RemoteAnimationTarget departingAnimationTarget,
@Nullable SurfaceControl screenshotSurface,
@Nullable HardwareBuffer screenshotBuffer,
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 929e81ed..f274d1a 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -49,6 +49,8 @@
public boolean isParentFrameClippedByDisplayCutout;
+ public float sizeCompatScale = 1f;
+
public ClientWindowFrames() {
}
@@ -60,6 +62,7 @@
attachedFrame = new Rect(other.attachedFrame);
}
isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout;
+ sizeCompatScale = other.sizeCompatScale;
}
private ClientWindowFrames(Parcel in) {
@@ -73,6 +76,7 @@
parentFrame.readFromParcel(in);
attachedFrame = in.readTypedObject(Rect.CREATOR);
isParentFrameClippedByDisplayCutout = in.readBoolean();
+ sizeCompatScale = in.readFloat();
}
@Override
@@ -82,6 +86,7 @@
parentFrame.writeToParcel(dest, flags);
dest.writeTypedObject(attachedFrame, flags);
dest.writeBoolean(isParentFrameClippedByDisplayCutout);
+ dest.writeFloat(sizeCompatScale);
}
@Override
@@ -91,7 +96,8 @@
+ " display=" + displayFrame.toShortString(sb)
+ " parentFrame=" + parentFrame.toShortString(sb)
+ (attachedFrame != null ? " attachedFrame=" + attachedFrame.toShortString() : "")
- + " parentClippedByDisplayCutout=" + isParentFrameClippedByDisplayCutout + "}";
+ + (isParentFrameClippedByDisplayCutout ? " parentClippedByDisplayCutout" : "")
+ + (sizeCompatScale != 1f ? " sizeCompatScale=" + sizeCompatScale : "") + "}";
}
@Override
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
index 3335c9c..79ddadb 100644
--- a/core/java/android/window/ITaskFragmentOrganizer.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -16,54 +16,9 @@
package android.window;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentTransaction;
/** @hide */
oneway interface ITaskFragmentOrganizer {
- void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo);
-
- /**
- * 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.
- */
- void onTaskFragmentParentInfoChanged(in IBinder fragmentToken, in Configuration parentConfig);
-
- /**
- * Called when the {@link WindowContainerTransaction} created with
- * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
- *
- * @param errorCallbackToken Token set through {@link
- * WindowContainerTransaction#setErrorCallbackToken(IBinder)}
- * @param errorBundle Bundle containing the exception, operation type and TaskFragmentInfo
- * if any. Should be created with
- * {@link TaskFragmentOrganizer#putErrorInfoInBundle}.
- */
- void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle errorBundle);
-
- /**
- * 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
- * Activity matches any split rule.
- *
- * @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
- * 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
- * {@link WindowContainerTransaction} if needed.
- */
- void onActivityReparentToTask(int taskId, in Intent activityIntent, in IBinder activityToken);
+ void onTransactionReady(in TaskFragmentTransaction transaction);
}
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 2fc22c2..f74d294 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -83,8 +83,7 @@
final Bundle bundle = new Bundle();
// Always invoke back for ime without checking the window focus.
final IOnBackInvokedCallback iCallback =
- new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback,
- () -> true);
+ new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
bundle.putInt(RESULT_KEY_PRIORITY, priority);
bundle.putInt(RESULT_KEY_ID, callback.hashCode());
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 8ad1093..49acde9 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -122,7 +122,7 @@
}
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
int priority = callbackPair.second;
- if (priority >= 0) {
+ if (priority >= PRIORITY_DEFAULT) {
mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
} else {
mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 765cd81..cc90aaf 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -16,6 +16,13 @@
package android.window;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_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;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
+
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,6 +34,7 @@
import android.os.RemoteException;
import android.view.RemoteAnimationDefinition;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -186,6 +194,67 @@
public void onActivityReparentToTask(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.
+ * @hide
+ */
+ public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
+ final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+ for (TaskFragmentTransaction.Change change : changes) {
+ // TODO(b/240519866): apply all changes in one WCT.
+ switch (change.getType()) {
+ case TYPE_TASK_FRAGMENT_APPEARED:
+ onTaskFragmentAppeared(change.getTaskFragmentInfo());
+ if (change.getTaskConfiguration() != null) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the
+ // same Task
+ onTaskFragmentParentInfoChanged(
+ change.getTaskFragmentToken(),
+ change.getTaskConfiguration());
+ }
+ break;
+ case TYPE_TASK_FRAGMENT_INFO_CHANGED:
+ if (change.getTaskConfiguration() != null) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the
+ // same Task
+ onTaskFragmentParentInfoChanged(
+ change.getTaskFragmentToken(),
+ change.getTaskConfiguration());
+ }
+ onTaskFragmentInfoChanged(change.getTaskFragmentInfo());
+ break;
+ case TYPE_TASK_FRAGMENT_VANISHED:
+ onTaskFragmentVanished(change.getTaskFragmentInfo());
+ break;
+ case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
+ onTaskFragmentParentInfoChanged(
+ change.getTaskFragmentToken(),
+ change.getTaskConfiguration());
+ break;
+ case TYPE_TASK_FRAGMENT_ERROR:
+ final Bundle errorBundle = change.getErrorBundle();
+ onTaskFragmentError(
+ change.getErrorCallbackToken(),
+ errorBundle.getParcelable(
+ KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
+ errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
+ errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
+ java.lang.Throwable.class));
+ break;
+ case TYPE_ACTIVITY_REPARENT_TO_TASK:
+ onActivityReparentToTask(
+ change.getTaskId(),
+ change.getActivityIntent(),
+ change.getActivityToken());
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown TaskFragmentEvent=" + change.getType());
+ }
+ }
+ }
+
@Override
public void applyTransaction(@NonNull WindowContainerTransaction t) {
t.setTaskFragmentOrganizer(mInterface);
@@ -203,51 +272,8 @@
private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo));
- }
-
- @Override
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentInfoChanged(taskFragmentInfo));
- }
-
- @Override
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentVanished(taskFragmentInfo));
- }
-
- @Override
- public void onTaskFragmentParentInfoChanged(
- @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onTaskFragmentParentInfoChanged(
- fragmentToken, parentConfig));
- }
-
- @Override
- public void onTaskFragmentError(
- @NonNull IBinder errorCallbackToken, @NonNull Bundle errorBundle) {
- mExecutor.execute(() -> {
- final TaskFragmentInfo info = errorBundle.getParcelable(
- KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
- TaskFragmentOrganizer.this.onTaskFragmentError(
- errorCallbackToken, info,
- errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
- (Throwable) errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
- java.lang.Throwable.class));
- });
- }
-
- @Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {
- mExecutor.execute(
- () -> TaskFragmentOrganizer.this.onActivityReparentToTask(
- taskId, activityIntent, activityToken));
+ public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
+ mExecutor.execute(() -> TaskFragmentOrganizer.this.onTransactionReady(transaction));
}
};
diff --git a/core/java/android/window/TaskFragmentTransaction.aidl b/core/java/android/window/TaskFragmentTransaction.aidl
new file mode 100644
index 0000000..aaa2db4
--- /dev/null
+++ b/core/java/android/window/TaskFragmentTransaction.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.window;
+
+parcelable TaskFragmentTransaction;
+parcelable TaskFragmentTransaction.Change;
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
new file mode 100644
index 0000000..755864f
--- /dev/null
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -0,0 +1,335 @@
+/*
+ * 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.window;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to communicate information about what are changing on embedded TaskFragments belonging to
+ * the same TaskFragmentOrganizer. A transaction can contain multiple changes.
+ * @see TaskFragmentTransaction.Change
+ * @hide
+ */
+public final class TaskFragmentTransaction implements Parcelable {
+
+ private final ArrayList<Change> mChanges = new ArrayList<>();
+
+ public TaskFragmentTransaction() {}
+
+ private TaskFragmentTransaction(Parcel in) {
+ in.readTypedList(mChanges, Change.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mChanges);
+ }
+
+ /** Adds a {@link Change} to this transaction. */
+ public void addChange(@Nullable Change change) {
+ if (change != null) {
+ mChanges.add(change);
+ }
+ }
+
+ /** Whether this transaction contains any {@link Change}. */
+ public boolean isEmpty() {
+ return mChanges.isEmpty();
+ }
+
+ @NonNull
+ public List<Change> getChanges() {
+ return mChanges;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("TaskFragmentTransaction{changes=[");
+ for (int i = 0; i < mChanges.size(); ++i) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(mChanges.get(i));
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<TaskFragmentTransaction> CREATOR = new Creator<>() {
+ @Override
+ public TaskFragmentTransaction createFromParcel(Parcel in) {
+ return new TaskFragmentTransaction(in);
+ }
+
+ @Override
+ public TaskFragmentTransaction[] newArray(int size) {
+ return new TaskFragmentTransaction[size];
+ }
+ };
+
+ /** Change type: the TaskFragment is attached to the hierarchy. */
+ public static final int TYPE_TASK_FRAGMENT_APPEARED = 1;
+
+ /** Change type: the status of the TaskFragment is changed. */
+ public static final int TYPE_TASK_FRAGMENT_INFO_CHANGED = 2;
+
+ /** Change type: the TaskFragment is removed form the hierarchy. */
+ public static final int TYPE_TASK_FRAGMENT_VANISHED = 3;
+
+ /** Change type: the status of the parent leaf Task is changed. */
+ public static final int TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED = 4;
+
+ /** Change type: the TaskFragment related operation failed on the server side. */
+ public static final int TYPE_TASK_FRAGMENT_ERROR = 5;
+
+ /**
+ * Change type: an Activity is reparented to the Task. For example, when an Activity enters and
+ * 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;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_TASK_FRAGMENT_APPEARED,
+ TYPE_TASK_FRAGMENT_INFO_CHANGED,
+ TYPE_TASK_FRAGMENT_VANISHED,
+ TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED,
+ TYPE_TASK_FRAGMENT_ERROR,
+ TYPE_ACTIVITY_REPARENT_TO_TASK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ChangeType {}
+
+ /** Represents the change an embedded TaskFragment undergoes. */
+ public static final class Change implements Parcelable {
+
+ /** @see ChangeType */
+ @ChangeType
+ private final int mType;
+
+ /** @see #setTaskFragmentToken(IBinder) */
+ @Nullable
+ private IBinder mTaskFragmentToken;
+
+ /** @see #setTaskFragmentInfo(TaskFragmentInfo) */
+ @Nullable
+ private TaskFragmentInfo mTaskFragmentInfo;
+
+ /** @see #setTaskId(int) */
+ private int mTaskId;
+
+ /** @see #setTaskConfiguration(Configuration) */
+ @Nullable
+ private Configuration mTaskConfiguration;
+
+ /** @see #setErrorCallbackToken(IBinder) */
+ @Nullable
+ private IBinder mErrorCallbackToken;
+
+ /** @see #setErrorBundle(Bundle) */
+ @Nullable
+ private Bundle mErrorBundle;
+
+ /** @see #setActivityIntent(Intent) */
+ @Nullable
+ private Intent mActivityIntent;
+
+ /** @see #setActivityToken(IBinder) */
+ @Nullable
+ private IBinder mActivityToken;
+
+ public Change(@ChangeType int type) {
+ mType = type;
+ }
+
+ private Change(Parcel in) {
+ mType = in.readInt();
+ mTaskFragmentToken = in.readStrongBinder();
+ mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
+ mTaskId = in.readInt();
+ mTaskConfiguration = in.readTypedObject(Configuration.CREATOR);
+ mErrorCallbackToken = in.readStrongBinder();
+ mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader());
+ mActivityIntent = in.readTypedObject(Intent.CREATOR);
+ mActivityToken = in.readStrongBinder();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeStrongBinder(mTaskFragmentToken);
+ dest.writeTypedObject(mTaskFragmentInfo, flags);
+ dest.writeInt(mTaskId);
+ dest.writeTypedObject(mTaskConfiguration, flags);
+ dest.writeStrongBinder(mErrorCallbackToken);
+ dest.writeBundle(mErrorBundle);
+ dest.writeTypedObject(mActivityIntent, flags);
+ dest.writeStrongBinder(mActivityToken);
+ }
+
+ /** The change is related to the TaskFragment created with this unique token. */
+ public Change setTaskFragmentToken(@NonNull IBinder taskFragmentToken) {
+ mTaskFragmentToken = requireNonNull(taskFragmentToken);
+ return this;
+ }
+
+ /** Info of the embedded TaskFragment. */
+ public Change setTaskFragmentInfo(@NonNull TaskFragmentInfo info) {
+ mTaskFragmentInfo = requireNonNull(info);
+ return this;
+ }
+
+ /** Task id the parent Task. */
+ public Change setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
+ /** Configuration of the parent Task. */
+ public Change setTaskConfiguration(@NonNull Configuration configuration) {
+ mTaskConfiguration = requireNonNull(configuration);
+ return this;
+ }
+
+ /**
+ * If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction}
+ * from the {@link TaskFragmentOrganizer}, it may come with an error callback token to
+ * report back.
+ */
+ public Change setErrorCallbackToken(@Nullable IBinder errorCallbackToken) {
+ mErrorCallbackToken = errorCallbackToken;
+ return this;
+ }
+
+ /**
+ * Bundle with necessary info about the failure operation of
+ * {@link #TYPE_TASK_FRAGMENT_ERROR}.
+ */
+ public Change setErrorBundle(@NonNull Bundle errorBundle) {
+ mErrorBundle = requireNonNull(errorBundle);
+ return this;
+ }
+
+ /**
+ * Intent of the activity that is reparented to the Task for
+ * {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ */
+ public Change setActivityIntent(@NonNull Intent intent) {
+ mActivityIntent = requireNonNull(intent);
+ return this;
+ }
+
+ /**
+ * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENT_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
+ * {@link WindowContainerTransaction} if needed.
+ */
+ public Change setActivityToken(@NonNull IBinder activityToken) {
+ mActivityToken = requireNonNull(activityToken);
+ return this;
+ }
+
+ @ChangeType
+ public int getType() {
+ return mType;
+ }
+
+ @Nullable
+ public IBinder getTaskFragmentToken() {
+ return mTaskFragmentToken;
+ }
+
+ @Nullable
+ public TaskFragmentInfo getTaskFragmentInfo() {
+ return mTaskFragmentInfo;
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ @Nullable
+ public Configuration getTaskConfiguration() {
+ return mTaskConfiguration;
+ }
+
+ @Nullable
+ public IBinder getErrorCallbackToken() {
+ return mErrorCallbackToken;
+ }
+
+ @Nullable
+ public Bundle getErrorBundle() {
+ return mErrorBundle;
+ }
+
+ @Nullable
+ public Intent getActivityIntent() {
+ return mActivityIntent;
+ }
+
+ @Nullable
+ public IBinder getActivityToken() {
+ return mActivityToken;
+ }
+
+ @Override
+ public String toString() {
+ return "Change{ type=" + mType + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<Change> CREATOR = new Creator<>() {
+ @Override
+ public Change createFromParcel(Parcel in) {
+ return new Change(in);
+ }
+
+ @Override
+ public Change[] newArray(int size) {
+ return new Change[size];
+ }
+ };
+ }
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 1f3841a..b263b08 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -116,6 +116,9 @@
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
public static final int FLAG_FIRST_CUSTOM = 1 << 10;
+ /** The container is going to show IME on its task after the transition. */
+ public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
@@ -129,7 +132,8 @@
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_IS_INPUT_METHOD,
FLAG_IS_EMBEDDED,
- FLAG_FIRST_CUSTOM
+ FLAG_FIRST_CUSTOM,
+ FLAG_WILL_IME_SHOWN
})
public @interface ChangeFlags {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 5a0d11d..2126166 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -670,6 +670,39 @@
}
/**
+ * Sets/removes the always on top flag for this {@code windowContainer}. See
+ * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
+ * Please note that this method is only intended to be used for a
+ * {@link com.android.server.wm.DisplayArea}.
+ *
+ * <p>
+ * Setting always on top to {@code True} will also make the {@code windowContainer} to move
+ * to the top.
+ * </p>
+ * <p>
+ * Setting always on top to {@code False} will make this {@code windowContainer} to move
+ * below the other always on top sibling containers.
+ * </p>
+ *
+ * @param windowContainer the container which the flag need to be updated for.
+ * @param alwaysOnTop denotes whether or not always on top flag should be set.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setAlwaysOnTop(
+ @NonNull WindowContainerToken windowContainer,
+ boolean alwaysOnTop) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP)
+ .setContainer(windowContainer.asBinder())
+ .setAlwaysOnTop(alwaysOnTop)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* When this {@link WindowContainerTransaction} failed to finish on the server side, it will
* trigger callback with this {@param errorCallbackToken}.
* @param errorCallbackToken client provided token that will be passed back as parameter in
@@ -1078,6 +1111,7 @@
public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16;
public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18;
+ public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1128,6 +1162,8 @@
@Nullable
private ShortcutInfo mShortcutInfo;
+ private boolean mAlwaysOnTop;
+
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1225,6 +1261,7 @@
mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
mPendingIntent = copy.mPendingIntent;
mShortcutInfo = copy.mShortcutInfo;
+ mAlwaysOnTop = copy.mAlwaysOnTop;
}
protected HierarchyOp(Parcel in) {
@@ -1246,6 +1283,7 @@
mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
+ mAlwaysOnTop = in.readBoolean();
}
public int getType() {
@@ -1311,6 +1349,10 @@
return mActivityIntent;
}
+ public boolean isAlwaysOnTop() {
+ return mAlwaysOnTop;
+ }
+
@Nullable
public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
return mTaskFragmentCreationOptions;
@@ -1379,6 +1421,9 @@
+ " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
return "{requestFocusOnTaskFragment: container=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+ return "{setAlwaysOnTop: container=" + mContainer
+ + " alwaysOnTop=" + mAlwaysOnTop + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1408,6 +1453,7 @@
dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
dest.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mShortcutInfo, flags);
+ dest.writeBoolean(mAlwaysOnTop);
}
@Override
@@ -1466,6 +1512,8 @@
@Nullable
private ShortcutInfo mShortcutInfo;
+ private boolean mAlwaysOnTop;
+
Builder(int type) {
mType = type;
}
@@ -1525,6 +1573,11 @@
return this;
}
+ Builder setAlwaysOnTop(boolean alwaysOnTop) {
+ mAlwaysOnTop = alwaysOnTop;
+ return this;
+ }
+
Builder setTaskFragmentCreationOptions(
@Nullable TaskFragmentCreationParams taskFragmentCreationOptions) {
mTaskFragmentCreationOptions = taskFragmentCreationOptions;
@@ -1553,6 +1606,7 @@
hierarchyOp.mLaunchOptions = mLaunchOptions;
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
+ hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
hierarchyOp.mShortcutInfo = mShortcutInfo;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 02c5ebc..1d396be 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -32,7 +32,6 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.TreeMap;
-import java.util.function.Supplier;
/**
* Provides window based implementation of {@link OnBackInvokedDispatcher}.
@@ -65,7 +64,6 @@
private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
private final Checker mChecker;
- private boolean mHasFocus;
public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
mChecker = new Checker(applicationCallBackEnabled);
@@ -191,7 +189,7 @@
.ImeOnBackInvokedCallback
? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
callback).getIOnBackInvokedCallback()
- : new OnBackInvokedCallbackWrapper(callback, this::hasFocus);
+ : new OnBackInvokedCallbackWrapper(callback);
callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
}
mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
@@ -200,17 +198,6 @@
}
}
- /**
- * Called when window focus changed.
- */
- public void onWindowFocusChanged(boolean hasFocus) {
- mHasFocus = hasFocus;
- }
-
- private boolean hasFocus() {
- return mHasFocus;
- }
-
public OnBackInvokedCallback getTopCallback() {
if (mAllCallbacks.isEmpty()) {
return null;
@@ -234,11 +221,8 @@
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
- private final Supplier<Boolean> mHasFocus;
- OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback,
- @NonNull Supplier<Boolean> hasFocus) {
+ OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
mCallback = new WeakReference<>(callback);
- mHasFocus = hasFocus;
}
@Override
@@ -278,10 +262,6 @@
if (callback == null) {
return;
}
- if (!mHasFocus.get()) {
- Log.w(TAG, "Skip back invoke due to current focus has lost.");
- return;
- }
callback.onBackInvoked();
});
}
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index c056e26..f843318 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.app;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -67,12 +68,53 @@
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
}
- public boolean showSessionForActiveService(Bundle args, int sourceFlags,
- IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
+ /**
+ * Shows the session for the currently active service. Used to start a new session from system
+ * affordances.
+ *
+ * @param args the bundle to pass as arguments to the voice interaction session
+ * @param sourceFlags flags indicating the source of this show
+ * @param showCallback optional callback to be notified when the session was shown
+ * @param activityToken optional token of activity that needs to be on top
+ *
+ * @deprecated Use {@link #showSessionForActiveService(Bundle, int, String,
+ * IVoiceInteractionSessionShowCallback, IBinder)} instead
+ */
+ @Deprecated
+ public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+ @Nullable IVoiceInteractionSessionShowCallback showCallback,
+ @Nullable IBinder activityToken) {
+ return showSessionForActiveServiceInternal(args, sourceFlags, /* attributionTag */ null,
+ showCallback, activityToken);
+ }
+
+ /**
+ * Shows the session for the currently active service. Used to start a new session from system
+ * affordances.
+ *
+ * @param args the bundle to pass as arguments to the voice interaction session
+ * @param sourceFlags flags indicating the source of this show
+ * @param attributionTag the attribution tag of the calling context or {@code null} for default
+ * attribution
+ * @param showCallback optional callback to be notified when the session was shown
+ * @param activityToken optional token of activity that needs to be on top
+ */
+ public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+ @Nullable String attributionTag,
+ @Nullable IVoiceInteractionSessionShowCallback showCallback,
+ @Nullable IBinder activityToken) {
+ return showSessionForActiveServiceInternal(args, sourceFlags, attributionTag, showCallback,
+ activityToken);
+ }
+
+ private boolean showSessionForActiveServiceInternal(@NonNull Bundle args, int sourceFlags,
+ @Nullable String attributionTag,
+ @Nullable IVoiceInteractionSessionShowCallback showCallback,
+ @Nullable IBinder activityToken) {
try {
if (mVoiceInteractionManagerService != null) {
return mVoiceInteractionManagerService.showSessionForActiveService(args,
- sourceFlags, showCallback, activityToken);
+ sourceFlags, attributionTag, showCallback, activityToken);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to call showSessionForActiveService", e);
diff --git a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
index d35f665..ec2d2ef 100644
--- a/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
+++ b/core/java/com/android/internal/app/BlockedAppStreamingActivity.java
@@ -36,6 +36,7 @@
private static final String EXTRA_BLOCKED_ACTIVITY_INFO =
PACKAGE_NAME + ".extra.BLOCKED_ACTIVITY_INFO";
private static final String EXTRA_STREAMED_DEVICE = PACKAGE_NAME + ".extra.STREAMED_DEVICE";
+ private static final String BLOCKED_COMPONENT_PLAYSTORE = "com.android.vending";
private static final String BLOCKED_COMPONENT_SETTINGS = "com.android.settings";
@Override
@@ -62,21 +63,25 @@
mAlertParams.mTitle =
getString(R.string.app_streaming_blocked_title_for_permission_dialog);
mAlertParams.mMessage =
- getString(R.string.app_streaming_blocked_message_for_permission_dialog,
- streamedDeviceName);
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ } else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_PLAYSTORE)) {
+ mAlertParams.mTitle =
+ getString(R.string.app_streaming_blocked_title_for_playstore_dialog);
+ mAlertParams.mMessage =
+ getString(R.string.app_streaming_blocked_message, streamedDeviceName);
} else if (TextUtils.equals(activityInfo.packageName, BLOCKED_COMPONENT_SETTINGS)) {
mAlertParams.mTitle =
getString(R.string.app_streaming_blocked_title_for_settings_dialog);
mAlertParams.mMessage =
- getString(R.string.app_streaming_blocked_message, streamedDeviceName);
+ getString(R.string.app_streaming_blocked_message_for_settings_dialog,
+ streamedDeviceName);
} else {
- mAlertParams.mTitle =
- getString(R.string.app_streaming_blocked_title, appLabel);
+ // No title required
mAlertParams.mMessage =
getString(R.string.app_streaming_blocked_message, streamedDeviceName);
}
} else {
- mAlertParams.mTitle = getString(R.string.app_blocked_title);
+ // No title required
mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
}
mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index ff1afdd..f6c64732 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -254,7 +254,7 @@
SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
- private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125;
+ private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 0;
private static final int URI_PERMISSION_INTENT_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 4b01357..83bf801 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -39,15 +39,16 @@
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
interface IVoiceInteractionManagerService {
- void showSession(in Bundle sessionArgs, int flags);
+ void showSession(in Bundle sessionArgs, int flags, String attributionTag);
boolean deliverNewSession(IBinder token, IVoiceInteractionSession session,
IVoiceInteractor interactor);
- boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags);
+ boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags,
+ String attributionTag);
boolean hideSessionFromSession(IBinder token);
int startVoiceActivity(IBinder token, in Intent intent, String resolvedType,
- String callingFeatureId);
+ String attributionTag);
int startAssistantActivity(IBinder token, in Intent intent, String resolvedType,
- String callingFeatureId);
+ String attributionTag);
void setKeepAwake(IBinder token, boolean keepAwake);
void closeSystemDialogs(IBinder token);
void finish(IBinder token);
@@ -125,12 +126,14 @@
*
* @param args the bundle to pass as arguments to the voice interaction session
* @param sourceFlags flags indicating the source of this show
+ * @param attributionTag the attribution tag of the calling context or {@code null} for default
+ * attribution
* @param showCallback optional callback to be notified when the session was shown
* @param activityToken optional token of activity that needs to be on top
* @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
@EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
- boolean showSessionForActiveService(in Bundle args, int sourceFlags,
+ boolean showSessionForActiveService(in Bundle args, int sourceFlags, String attributionTag,
IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken);
/**
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index b32afb4..66fff5c 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -951,7 +951,7 @@
protected void onPostExecute(Drawable d) {
if (getOtherProfile() == mDisplayResolveInfo) {
mResolverListCommunicator.updateProfileViewButton();
- } else {
+ } else if (!mDisplayResolveInfo.hasDisplayIcon()) {
mDisplayResolveInfo.setDisplayIcon(d);
mHolder.bindIcon(mDisplayResolveInfo);
// Notify in case view is already bound to resolve the race conditions on
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 8bab5c3..5db2e84 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -74,6 +74,8 @@
void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
+ void updateEditorToolType(int toolType);
+
void changeInputMethodSubtype(in InputMethodSubtype subtype);
void canStartStylusHandwriting(int requestId);
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 8f10a5e..e625b31 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -55,7 +55,6 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
@@ -155,7 +154,6 @@
// Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
- public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1;
public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
@@ -226,7 +224,7 @@
public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
// This should be mapping to CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE.
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE,
- UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK,
+ NO_STATSD_LOGGING, // This is deprecated.
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE,
@@ -310,7 +308,6 @@
/** @hide */
@IntDef({
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
- CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK,
CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
@@ -738,8 +735,6 @@
switch (cujType) {
case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE:
return "SHADE_EXPAND_COLLAPSE";
- case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK:
- return "SHADE_EXPAND_COLLAPSE_LOCK";
case CUJ_NOTIFICATION_SHADE_SCROLL_FLING:
return "SHADE_SCROLL_FLING";
case CUJ_NOTIFICATION_SHADE_ROW_EXPAND:
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 6eae34b..b64771b 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -39,8 +39,7 @@
import java.util.function.IntFunction;
/**
- * ArrayUtils contains some methods that you can call to find out
- * the most efficient increments by which to grow arrays.
+ * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}.
*/
public class ArrayUtils {
private static final int CACHE_SIZE = 73;
@@ -351,15 +350,16 @@
}
/**
- * Combine multiple arrays into a single array.
+ * Returns the concatenation of the given arrays. Only works for object arrays, not for
+ * primitive arrays. See {@link #concat(byte[]...)} for a variant that works on byte arrays.
*
* @param kind The class of the array elements
- * @param arrays The arrays to combine
+ * @param arrays The arrays to concatenate. Null arrays are treated as empty.
* @param <T> The class of the array elements (inferred from kind).
* @return A single array containing all the elements of the parameter arrays.
*/
@SuppressWarnings("unchecked")
- public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[]... arrays) {
+ public static @NonNull <T> T[] concat(Class<T> kind, @Nullable T[]... arrays) {
if (arrays == null || arrays.length == 0) {
return createEmptyArray(kind);
}
@@ -400,6 +400,29 @@
return (T[]) Array.newInstance(kind, 0);
}
+ /**
+ * Returns the concatenation of the given byte arrays. Null arrays are treated as empty.
+ */
+ public static @NonNull byte[] concat(@Nullable byte[]... arrays) {
+ if (arrays == null) {
+ return new byte[0];
+ }
+ int totalLength = 0;
+ for (byte[] a : arrays) {
+ if (a != null) {
+ totalLength += a.length;
+ }
+ }
+ final byte[] result = new byte[totalLength];
+ int pos = 0;
+ for (byte[] a : arrays) {
+ if (a != null) {
+ System.arraycopy(a, 0, result, pos, a.length);
+ pos += a.length;
+ }
+ }
+ return result;
+ }
/**
* Adds value to given array if not already present, providing set-like
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index ca40a40..14a6d5e 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -142,6 +142,11 @@
*/
public static final int ACTION_LOAD_SHARE_SHEET = 16;
+ /**
+ * Time it takes to show AOD display after folding the device.
+ */
+ public static final int ACTION_FOLD_TO_AOD = 17;
+
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -160,6 +165,7 @@
ACTION_UDFPS_ILLUMINATE,
ACTION_SHOW_BACK_ARROW,
ACTION_LOAD_SHARE_SHEET,
+ ACTION_FOLD_TO_AOD,
};
/** @hide */
@@ -181,6 +187,7 @@
ACTION_UDFPS_ILLUMINATE,
ACTION_SHOW_BACK_ARROW,
ACTION_LOAD_SHARE_SHEET,
+ ACTION_FOLD_TO_AOD
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -204,6 +211,7 @@
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_BACK_ARROW,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET,
+ FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD
};
private static LatencyTracker sLatencyTracker;
@@ -297,6 +305,8 @@
return "ACTION_SHOW_BACK_ARROW";
case 17:
return "ACTION_LOAD_SHARE_SHEET";
+ case 19:
+ return "ACTION_FOLD_TO_AOD";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 354dd9a..1808bd5 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -5,3 +5,4 @@
per-file Protocol* = etancohen@google.com, lorenzo@google.com
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
+per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 518fa53..b9243ec 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,6 @@
package com.android.internal.util;
import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,6 +28,10 @@
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager;
+import android.view.WindowManager.ScreenshotSource;
+import android.view.WindowManager.ScreenshotType;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
import java.util.function.Consumer;
@@ -42,24 +45,28 @@
* Describes a screenshot request (to make it easier to pass data through to the handler).
*/
public static class ScreenshotRequest implements Parcelable {
- private int mSource;
- private boolean mHasStatusBar;
- private boolean mHasNavBar;
- private Bundle mBitmapBundle;
- private Rect mBoundsInScreen;
- private Insets mInsets;
- private int mTaskId;
- private int mUserId;
- private ComponentName mTopComponent;
+ private final int mSource;
+ private final Bundle mBitmapBundle;
+ private final Rect mBoundsInScreen;
+ private final Insets mInsets;
+ private final int mTaskId;
+ private final int mUserId;
+ private final ComponentName mTopComponent;
- ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
+ @VisibleForTesting
+ public ScreenshotRequest(int source) {
mSource = source;
- mHasStatusBar = hasStatus;
- mHasNavBar = hasNav;
+ mBitmapBundle = null;
+ mBoundsInScreen = null;
+ mInsets = null;
+ mTaskId = -1;
+ mUserId = -1;
+ mTopComponent = null;
}
- ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets,
- int taskId, int userId, ComponentName topComponent) {
+ @VisibleForTesting
+ public ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen,
+ Insets insets, int taskId, int userId, ComponentName topComponent) {
mSource = source;
mBitmapBundle = bitmapBundle;
mBoundsInScreen = boundsInScreen;
@@ -71,16 +78,21 @@
ScreenshotRequest(Parcel in) {
mSource = in.readInt();
- mHasStatusBar = in.readBoolean();
- mHasNavBar = in.readBoolean();
-
if (in.readInt() == 1) {
mBitmapBundle = in.readBundle(getClass().getClassLoader());
- mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class);
- mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class);
+ mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
+ mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
mTaskId = in.readInt();
mUserId = in.readInt();
- mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
+ mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
+ ComponentName.class);
+ } else {
+ mBitmapBundle = null;
+ mBoundsInScreen = null;
+ mInsets = null;
+ mTaskId = -1;
+ mUserId = -1;
+ mTopComponent = null;
}
}
@@ -88,14 +100,6 @@
return mSource;
}
- public boolean getHasStatusBar() {
- return mHasStatusBar;
- }
-
- public boolean getHasNavBar() {
- return mHasNavBar;
- }
-
public Bundle getBitmapBundle() {
return mBitmapBundle;
}
@@ -112,7 +116,6 @@
return mTaskId;
}
-
public int getUserId() {
return mUserId;
}
@@ -129,8 +132,6 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mSource);
- dest.writeBoolean(mHasStatusBar);
- dest.writeBoolean(mHasNavBar);
if (mBitmapBundle == null) {
dest.writeInt(0);
} else {
@@ -144,7 +145,8 @@
}
}
- public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
new Parcelable.Creator<ScreenshotRequest>() {
@Override
@@ -217,8 +219,9 @@
throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
}
- HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, android.hardware.HardwareBuffer.class);
- ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE, android.graphics.ParcelableColorSpace.class);
+ HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
+ ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
+ ParcelableColorSpace.class);
return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
colorSpace.getColorSpace());
@@ -254,113 +257,71 @@
/**
* Request a screenshot be taken.
- *
+ * <p>
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false} if not.
- * @param source The source of the screenshot request. One of
- * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD,
- * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER}
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, int source, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
- takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
+ public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+ takeScreenshot(screenshotType, handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
completionConsumer);
}
/**
- * Request a screenshot be taken, with provided reason.
- *
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if
- * not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false}
- * if not.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
- */
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
- completionConsumer);
- }
-
- /**
- * Request a screenshot be taken with a specific timeout.
- *
+ * Request a screenshot be taken.
+ * <p>
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
*
- * @param screenshotType The type of screenshot, for example either
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
- * or
- * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
- * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
- * if
- * not.
- * @param hasNav {@code true} if the navigation bar is currently showing. {@code
- * false}
- * if not.
- * @param timeoutMs If the screenshot hasn't been completed within this time period,
- * the screenshot attempt will be cancelled and `completionConsumer`
- * will be run.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotType The type of screenshot, defined by {@link ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
+ * @param timeoutMs time limit for processing, intended only for testing
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
- public void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, long timeoutMs, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
- hasNav);
- takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
+ @VisibleForTesting
+ public void takeScreenshot(@ScreenshotType int screenshotType, @ScreenshotSource int source,
+ @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source);
+ takeScreenshot(screenshotType, handler, screenshotRequest, timeoutMs, completionConsumer);
}
/**
* Request that provided image be handled as if it was a screenshot.
*
- * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
- * @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from.
- * @param insets The insets that the image was shown with, inside the screenbounds.
- * @param taskId The taskId of the task that the screen shot was taken of.
- * @param userId The userId of user running the task provided in taskId.
- * @param topComponent The component name of the top component running in the task.
- * @param handler A handler used in case the screenshot times out
- * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
- * screenshot was taken.
+ * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
+ * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
+ * @param insets The insets that the image was shown with, inside the screen bounds.
+ * @param taskId The taskId of the task that the screen shot was taken of.
+ * @param userId The userId of user running the task provided in taskId.
+ * @param topComponent The component name of the top component running in the task.
+ * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
+ * @param handler A handler used in case the screenshot times out
+ * @param completionConsumer receives the URI of the captured screenshot, once saved or
+ * null if no screenshot was saved
*/
public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
- @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source,
- @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest =
- new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId,
- userId, topComponent);
- takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
- handler, screenshotRequest, completionConsumer);
+ @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
+ @ScreenshotSource int source, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, screenshotBundle,
+ boundsInScreen, insets, taskId, userId, topComponent);
+ takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, handler, screenshotRequest,
+ SCREENSHOT_TIMEOUT_MS,
+ completionConsumer);
}
- private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
- ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
+ private void takeScreenshot(@ScreenshotType int screenshotType, @NonNull Handler handler,
+ ScreenshotRequest screenshotRequest, long timeoutMs,
+ @Nullable Consumer<Uri> completionConsumer) {
synchronized (mScreenshotLock) {
final Runnable mScreenshotTimeout = () -> {
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index eb8b860..9471fae 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -55,7 +55,7 @@
InputMethodSubtype getLastInputMethodSubtype(int userId);
boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
- in @nullable ResultReceiver resultReceiver, int reason);
+ int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason);
boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags,
in @nullable ResultReceiver resultReceiver, int reason);
@@ -88,8 +88,15 @@
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
boolean isInputMethodPickerShownForTest();
- @nullable InputMethodSubtype getCurrentInputMethodSubtype();
- void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+ @nullable InputMethodSubtype getCurrentInputMethodSubtype(int userId);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+ void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes,
+ int userId);
+
// This is kept due to @UnsupportedAppUsage.
// TODO(Bug 113914148): Consider removing this.
int getInputMethodWindowVisibleHeight(in IInputMethodClient client);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index cc076ab..3e0a6cb 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -166,10 +166,6 @@
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
- public static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
- public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
- public static final String SYNTHETIC_PASSWORD_KEY_PREFIX = "synthetic_password_";
-
public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
public static final String PASSWORD_HISTORY_DELIMITER = ",";
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 40164a4..9f3f335 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -29,6 +29,7 @@
import android.os.storage.StorageManager;
import android.text.TextUtils;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import libcore.util.HexEncoding;
@@ -278,58 +279,36 @@
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(hashFactor);
- byte[] saltedPassword = Arrays.copyOf(passwordToHash, passwordToHash.length
- + salt.length);
- System.arraycopy(salt, 0, saltedPassword, passwordToHash.length, salt.length);
- sha256.update(saltedPassword);
- Arrays.fill(saltedPassword, (byte) 0);
- return new String(HexEncoding.encode(sha256.digest()));
+ sha256.update(passwordToHash);
+ sha256.update(salt);
+ return HexEncoding.encodeToString(sha256.digest());
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
}
}
/**
- * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
- * Not the most secure, but it is at least a second level of protection. First level is that
- * the file is in a location only readable by the system process.
+ * Hash the given password for the password history, using the legacy algorithm.
*
- * @return the hash of the pattern in a byte array.
+ * @deprecated This algorithm is insecure because the password can be easily bruteforced, given
+ * the hash and salt. Use {@link #passwordToHistoryHash(byte[], byte[], byte[])}
+ * instead, which incorporates an SP-derived secret into the hash.
+ *
+ * @return the legacy password hash
*/
- public String legacyPasswordToHash(byte[] salt) {
- return legacyPasswordToHash(mCredential, salt);
- }
-
- /**
- * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
- * Not the most secure, but it is at least a second level of protection. First level is that
- * the file is in a location only readable by the system process.
- *
- * @param password the gesture pattern.
- *
- * @return the hash of the pattern in a byte array.
- */
+ @Deprecated
public static String legacyPasswordToHash(byte[] password, byte[] salt) {
if (password == null || password.length == 0 || salt == null) {
return null;
}
try {
- // Previously the password was passed as a String with the following code:
- // byte[] saltedPassword = (password + salt).getBytes();
- // The code below creates the identical digest preimage using byte arrays:
- byte[] saltedPassword = Arrays.copyOf(password, password.length + salt.length);
- System.arraycopy(salt, 0, saltedPassword, password.length, salt.length);
+ byte[] saltedPassword = ArrayUtils.concat(password, salt);
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
- byte[] combined = new byte[sha1.length + md5.length];
- System.arraycopy(sha1, 0, combined, 0, sha1.length);
- System.arraycopy(md5, 0, combined, sha1.length, md5.length);
-
- final char[] hexEncoded = HexEncoding.encode(combined);
Arrays.fill(saltedPassword, (byte) 0);
- return new String(hexEncoded);
+ return HexEncoding.encodeToString(ArrayUtils.concat(sha1, md5));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
}
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 09ff4e0..9ee9b82 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -828,6 +828,8 @@
mSystemGestureExclusionListener, mContext.getDisplayId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to unregister window manager callbacks", e);
}
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 5cb0de3..cbc3462 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2127,6 +2127,8 @@
// Reset the fd to the unsolicited zygote socket
gSystemServerSocketFd = -1;
+ } else if (pid == -1) {
+ ALOGE("Failed to fork child process: %s (%d)", strerror(errno), errno);
} else {
ALOGD("Forked child process %d", pid);
}
diff --git a/core/res/res/drawable/ic_sd_card_48dp.xml b/core/res/res/drawable/ic_sd_card_48dp.xml
index 90bab47..10fd120 100644
--- a/core/res/res/drawable/ic_sd_card_48dp.xml
+++ b/core/res/res/drawable/ic_sd_card_48dp.xml
@@ -19,6 +19,6 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#FF000000"
+ android:fillColor="?android:attr/colorAccent"
android:pathData="M36 4H20L8.04 16 8 40c0 2.2 1.8 4 4 4h24c2.2 0 4,-1.8 4,-4V8c0,-2.2,-1.8,-4,-4,-4zM24 16h-4V8h4v8zm6 0h-4V8h4v8zm6 0h-4V8h4v8z"/>
</vector>
diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml
index d686dd2..34b6a54 100644
--- a/core/res/res/values-sw600dp/config.xml
+++ b/core/res/res/values-sw600dp/config.xml
@@ -51,7 +51,5 @@
<!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
<bool name="config_showUserSwitcherByDefault">true</bool>
-
- <integer name="config_chooser_max_targets_per_row">6</integer>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3dc301..d3f2607 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3971,7 +3971,7 @@
<string name="ext_media_new_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
<!-- Notification body when external media is ready for use [CHAR LIMIT=NONE] -->
- <string name="ext_media_ready_notification_message">For transferring photos and media</string>
+ <string name="ext_media_ready_notification_message">For storing photos, videos, music and more</string>
<!-- TV specific notification body when external media is ready for use [CHAR LIMIT=75] -->
<string name="ext_media_ready_notification_message" product="tv">Browse media files</string>
@@ -3987,11 +3987,11 @@
<string name="ext_media_unmountable_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
<!-- Notification title when external media is unsupported [CHAR LIMIT=30] -->
- <string name="ext_media_unsupported_notification_title">Unsupported <xliff:g id="name" example="SD card">%s</xliff:g></string>
+ <string name="ext_media_unsupported_notification_title"><xliff:g id="name" example="SD card">%s</xliff:g> detected </string>
<!-- Automotive specific notification title when external media is unsupported [CHAR LIMIT=30] -->
<string name="ext_media_unsupported_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
<!-- Notification body when external media is unsupported [CHAR LIMIT=NONE] -->
- <string name="ext_media_unsupported_notification_message">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Tap to set up in a supported format.</string>
+ <string name="ext_media_unsupported_notification_message">Tap to set up .</string>
<!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=75] -->
<string name="ext_media_unsupported_notification_message" product="tv">Select to set up <xliff:g id="name" example="SD card">%s</xliff:g> in a supported format.</string>
<!-- Automotive specific notification body when external media is unsupported [CHAR LIMIT=NONE] -->
@@ -4022,7 +4022,7 @@
<!-- Notification action to transfer media [CHAR LIMIT=40] -->
<string name="ext_media_seamless_action">Switch output</string>
- <!-- Notification title when external media is missing [CHAR LIMIT=30] -->
+ <!-- Notification title when adoptable storage media is ejected [CHAR LIMIT=30] -->
<string name="ext_media_missing_title"><xliff:g id="name" example="SD card">%s</xliff:g> missing</string>
<!-- Notification body when external media is missing [CHAR LIMIT=30] -->
<string name="ext_media_missing_message">Insert device again</string>
@@ -5477,6 +5477,8 @@
<string name="app_streaming_blocked_title_for_fingerprint_dialog">Continue on phone</string>
<!-- Title of the dialog shown when the microphone permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_title_for_microphone_dialog">Microphone unavailable</string>
+ <!-- Title of the dialog shown when the play store is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_title_for_playstore_dialog">Play Store unavailable</string>
<!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_title_for_settings_dialog" product="tv">Android TV settings unavailable</string>
<!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
@@ -5484,23 +5486,23 @@
<!-- Title of the dialog shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_title_for_settings_dialog" product="default">Phone settings unavailable</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string>
+ <string name="app_streaming_blocked_message" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your tablet instead.</string>
+ <string name="app_streaming_blocked_message" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
<!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
- <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message_for_permission_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your Android TV device instead.</string>
- <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your tablet instead.</string>
- <!-- Message shown when an app is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
- <string name="app_streaming_blocked_message_for_permission_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
+ <string name="app_streaming_blocked_message" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g> at this time. Try on your phone instead.</string>
<!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv">This app is requesting additional security. Try on your Android TV device instead.</string>
<!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet">This app is requesting additional security. Try on your tablet instead.</string>
<!-- Message shown when the fingerprint permission is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default">This app is requesting additional security. Try on your phone instead.</string>
+ <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your Android TV device instead.</string>
+ <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tablet">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your tablet instead.</string>
+ <!-- Message shown when the settings is blocked from being streamed to a remote device. [CHAR LIMIT=NONE] -->
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
<string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bcf9759..cb0393f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3309,10 +3309,11 @@
<java-symbol type="string" name="app_streaming_blocked_title_for_camera_dialog" />
<java-symbol type="string" name="app_streaming_blocked_title_for_fingerprint_dialog" />
<java-symbol type="string" name="app_streaming_blocked_title_for_microphone_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_title_for_playstore_dialog" />
<java-symbol type="string" name="app_streaming_blocked_title_for_settings_dialog" />
<java-symbol type="string" name="app_streaming_blocked_message" />
- <java-symbol type="string" name="app_streaming_blocked_message_for_permission_dialog" />
<java-symbol type="string" name="app_streaming_blocked_message_for_fingerprint_dialog" />
+ <java-symbol type="string" name="app_streaming_blocked_message_for_settings_dialog" />
<!-- Used internally for assistant to launch activity transitions -->
<java-symbol type="id" name="cross_task_transition" />
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
index 8587a35..25bf93f 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java
@@ -58,7 +58,7 @@
public class StartProgramListUpdatesFanoutTest {
private static final String TAG = "BroadcastRadioTests.hal2.StartProgramListUpdatesFanout";
- private static final VerificationWithTimeout CB_TIMEOUT = timeout(100);
+ private static final VerificationWithTimeout CB_TIMEOUT = timeout(500);
// Mocks
@Mock IBroadcastRadio mBroadcastRadioMock;
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
index 36ed024d..dfb1f0b 100644
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
+++ b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
index feefed3..622b861 100644
--- a/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
+++ b/core/tests/coretests/assets/fonts/StaticLayoutLineBreakingTestFont.ttx
@@ -144,6 +144,7 @@
<map code="0x0056" name="5em" /> <!-- V -->
<map code="0x0058" name="10em" /> <!-- X -->
<map code="0x005f" name="0em" /> <!-- _ -->
+ <map code="0x000a" name="0em" /> <!-- NEW_LINE -->
<map code="0x05D0" name="1em" /> <!-- HEBREW LETTER ALEF -->
<map code="0x05D1" name="5em" /> <!-- HEBREW LETTER BET -->
<map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 50639be..0a5a4d5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -32,7 +32,6 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
@@ -140,7 +139,6 @@
activityInfo.name = "name";
Configuration overrideConfig = new Configuration();
overrideConfig.assetsSeq = 5;
- CompatibilityInfo compat = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
String referrer = "referrer";
int procState = 4;
Bundle bundle = new Bundle();
@@ -152,7 +150,7 @@
Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
.setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
- .setOverrideConfig(overrideConfig).setCompatInfo(compat).setReferrer(referrer)
+ .setOverrideConfig(overrideConfig).setReferrer(referrer)
.setProcState(procState).setState(bundle).setPersistentState(persistableBundle)
.setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
.setIsForward(true).setAssistToken(assistToken)
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 26d9628..2cd890c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -23,7 +23,6 @@
import android.app.ResultInfo;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
@@ -96,7 +95,6 @@
private ActivityInfo mInfo;
private Configuration mCurConfig;
private Configuration mOverrideConfig;
- private CompatibilityInfo mCompatInfo;
private String mReferrer;
private IVoiceInteractor mVoiceInteractor;
private int mProcState;
@@ -137,11 +135,6 @@
return this;
}
- LaunchActivityItemBuilder setCompatInfo(CompatibilityInfo compatInfo) {
- mCompatInfo = compatInfo;
- return this;
- }
-
LaunchActivityItemBuilder setReferrer(String referrer) {
mReferrer = referrer;
return this;
@@ -214,7 +207,7 @@
LaunchActivityItem build() {
return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
- mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor,
+ mCurConfig, mOverrideConfig, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
null /* activityClientController */, mShareableActivityToken,
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 0eca0a8..e9bbdbe 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -187,7 +187,6 @@
activityInfo.name = "name";
Configuration overrideConfig = new Configuration();
overrideConfig.assetsSeq = 5;
- CompatibilityInfo compat = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
String referrer = "referrer";
int procState = 4;
Bundle bundle = new Bundle();
@@ -198,7 +197,7 @@
LaunchActivityItem item = new LaunchActivityItemBuilder()
.setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
- .setOverrideConfig(overrideConfig).setCompatInfo(compat).setReferrer(referrer)
+ .setOverrideConfig(overrideConfig).setReferrer(referrer)
.setProcState(procState).setState(bundle).setPersistentState(persistableBundle)
.setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic())
.setPendingNewIntents(referrerIntentList()).setIsForward(true)
@@ -489,13 +488,13 @@
@Override
public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo,
- CompatibilityInfo compatibilityInfo, int i, int userId, int operatioType)
+ int i, int userId, int operatioType)
throws RemoteException {
}
@Override
public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo,
- CompatibilityInfo compatibilityInfo, int userId) throws RemoteException {
+ int userId) throws RemoteException {
}
@Override
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
index 669138c..0d5cd72 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
@@ -157,6 +157,22 @@
assertFalse(config.isNightModeActive());
}
+ @Test
+ public void testSequenceNumberWrapAround() {
+ Configuration config = new Configuration();
+ Configuration otherConfig = new Configuration();
+
+ // Verify the other configuration is not newer when this sequence number warp around.
+ config.seq = 1000;
+ otherConfig.seq = Integer.MAX_VALUE - 1000;
+ assertFalse(config.isOtherSeqNewer(otherConfig));
+
+ // Verify the other configuration is newer when the other sequence number warp around.
+ config.seq = Integer.MAX_VALUE - 1000;
+ otherConfig.seq = 1000;
+ assertTrue(config.isOtherSeqNewer(otherConfig));
+ }
+
private void dumpDebug(File f, Configuration config) throws Exception {
final AtomicFile af = new AtomicFile(f);
FileOutputStream fos = af.startWrite();
diff --git a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
index 730a3cb..9bad6d2 100644
--- a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
+++ b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
@@ -161,7 +161,7 @@
boolean dbStatFound = false;
SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
for (SQLiteDebug.DbStats dbStat : info.dbStats) {
- if (dbStat.dbName.endsWith(dbName)) {
+ if (dbStat.dbName.endsWith(dbName) && !dbStat.arePoolStats) {
dbStatFound = true;
Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
if (expectDisabled) {
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index c1e72fe..32c3a26 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -58,10 +58,10 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import libcore.io.Streams;
-
import com.google.android.collect.Sets;
+import libcore.io.Streams;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -477,6 +477,14 @@
new File(mTarget, "test (1).jpg").createNewFile();
assertNameEquals("test (2).jpg",
FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
+
+ assertNameEquals("test.mp3", FileUtils.buildUniqueFile(mTarget, "audio/mp3", "test.mp3"));
+ new File(mTarget, "test.mp3").createNewFile();
+ assertNameEquals("test (1).mp3",
+ FileUtils.buildUniqueFile(mTarget, "audio/mp3", "test.mp3"));
+ new File(mTarget, "test (1).mp3").createNewFile();
+ assertNameEquals("test (2).mp3",
+ FileUtils.buildUniqueFile(mTarget, "audio/mp3", "test.mp3"));
}
@Test
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 34a1fd8..44bb062 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -92,7 +92,7 @@
@Test
public void testImeVisibility() {
final InsetsSourceControl ime =
- new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE);
+ new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -121,7 +121,7 @@
// set control and verify visibility is applied.
InsetsSourceControl control =
- new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE);
+ new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
mController.onControlsChanged(new InsetsSourceControl[] { control });
// IME show animation should be triggered when control becomes available.
verify(mController).applyAnimation(
@@ -158,7 +158,7 @@
// set control and verify visibility is applied.
InsetsSourceControl control = Mockito.spy(
- new InsetsSourceControl(ITYPE_IME, mLeash, new Point(), Insets.NONE));
+ new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE));
// Simulate IME source control set this flag when the target has starting window.
control.setSkipAnimationOnce(true);
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 4f1da1b2..d0f7fe04 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -95,12 +95,13 @@
() -> mMockTransaction, mMockController);
topConsumer.setControl(
new InsetsSourceControl(
- ITYPE_STATUS_BAR, mTopLeash, new Point(0, 0), Insets.of(0, 100, 0, 0)),
+ ITYPE_STATUS_BAR, mTopLeash, true, new Point(0, 0),
+ Insets.of(0, 100, 0, 0)),
new int[1], new int[1]);
InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR,
mInsetsState, () -> mMockTransaction, mMockController);
- navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash,
+ navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash, true,
new Point(400, 0), Insets.of(0, 0, 100, 0)), new int[1], new int[1]);
navConsumer.hide();
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index dcb1835..ed6a649 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -223,7 +223,7 @@
InsetsSourceControl control =
new InsetsSourceControl(
- ITYPE_STATUS_BAR, mLeash, new Point(), Insets.of(0, 10, 0, 0));
+ ITYPE_STATUS_BAR, mLeash, true, new Point(), Insets.of(0, 10, 0, 0));
mController.onControlsChanged(new InsetsSourceControl[]{control});
mController.controlWindowInsetsAnimation(0, 0 /* durationMs */,
new LinearInterpolator(),
@@ -926,7 +926,8 @@
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
- return new InsetsSourceControl(type, copy, new Point(), Insets.NONE);
+ return new InsetsSourceControl(type, copy, InsetsState.getDefaultVisibility(type),
+ new Point(), Insets.NONE);
}
private InsetsSourceControl[] createSingletonControl(@InternalInsetsType int type) {
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index b3aa7e8..2054b4f 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -110,7 +110,8 @@
instrumentation.waitForIdleSync();
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
+ new Point(), Insets.NONE),
new int[1], new int[1]);
}
@@ -180,7 +181,8 @@
verifyZeroInteractions(mMockTransaction);
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
+ new Point(), Insets.NONE),
new int[1], hideTypes);
assertEquals(statusBars(), hideTypes[0]);
assertFalse(mRemoveSurfaceCalled);
@@ -191,14 +193,14 @@
public void testRestore_noAnimation() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mConsumer.hide();
- mController.onStateChanged(mState);
mConsumer.setControl(null, new int[1], new int[1]);
reset(mMockTransaction);
verifyZeroInteractions(mMockTransaction);
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
mConsumer.setControl(
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point(), Insets.NONE),
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, false /* initialVisible */,
+ new Point(), Insets.NONE),
new int[1], hideTypes);
assertTrue(mRemoveSurfaceCalled);
assertEquals(0, hideTypes[0]);
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index b867e44..9637de8 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -42,6 +42,7 @@
import android.text.style.MaskFilterSpan;
import android.text.style.UnderlineSpan;
import android.util.StringBuilderPrinter;
+import android.view.MotionEvent;
import android.view.autofill.AutofillId;
import androidx.test.filters.SmallTest;
@@ -490,7 +491,8 @@
assertThat(sb.toString()).isEqualTo(
"prefix: inputType=0x0 imeOptions=0x0 privateImeOptions=null\n"
+ "prefix: actionLabel=null actionId=0\n"
- + "prefix: initialSelStart=-1 initialSelEnd=-1 initialCapsMode=0x0\n"
+ + "prefix: initialSelStart=-1 initialSelEnd=-1 initialToolType=0"
+ + " initialCapsMode=0x0\n"
+ "prefix: hintText=null label=null\n"
+ "prefix: packageName=null autofillId=null fieldId=0 fieldName=null\n"
+ "prefix: extras=null\n"
@@ -509,6 +511,7 @@
info.initialCapsMode = TextUtils.CAP_MODE_CHARACTERS; // 0x1000
info.hintText = "testHintText";
info.label = "testLabel";
+ info.setInitialToolType(MotionEvent.TOOL_TYPE_STYLUS);
info.packageName = "android.view.inputmethod";
info.autofillId = new AutofillId(123);
info.fieldId = 456;
@@ -523,7 +526,8 @@
assertThat(sb.toString()).isEqualTo(
"prefix2: inputType=0x1 imeOptions=0x2 privateImeOptions=testOptions\n"
+ "prefix2: actionLabel=null actionId=0\n"
- + "prefix2: initialSelStart=0 initialSelEnd=1 initialCapsMode=0x1000\n"
+ + "prefix2: initialSelStart=0 initialSelEnd=1 initialToolType=2"
+ + " initialCapsMode=0x1000\n"
+ "prefix2: hintText=testHintText label=testLabel\n"
+ "prefix2: packageName=android.view.inputmethod autofillId=123"
+ " fieldId=456 fieldName=testField\n"
@@ -543,7 +547,8 @@
assertThat(sb.toString()).isEqualTo(
"prefix: inputType=0x0 imeOptions=0x0 privateImeOptions=null\n"
+ "prefix: actionLabel=null actionId=0\n"
- + "prefix: initialSelStart=-1 initialSelEnd=-1 initialCapsMode=0x0\n"
+ + "prefix: initialSelStart=-1 initialSelEnd=-1 initialToolType=0"
+ + " initialCapsMode=0x0\n"
+ "prefix: hintText=null label=null\n"
+ "prefix: packageName=null autofillId=null fieldId=0 fieldName=null\n"
+ "prefix: hintLocales=null\n"
diff --git a/core/tests/coretests/src/android/widget/TextViewPopulateCharacterBoundsTest.java b/core/tests/coretests/src/android/widget/TextViewPopulateCharacterBoundsTest.java
new file mode 100644
index 0000000..d31a6a9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewPopulateCharacterBoundsTest.java
@@ -0,0 +1,422 @@
+/*
+ * 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.widget;
+
+import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.inputmethod.CursorAnchorInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TextViewPopulateCharacterBoundsTest {
+ @Rule
+ public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
+ TextViewActivity.class);
+ private Activity mActivity;
+ private Instrumentation mInstrumentation;
+ private Typeface mTypeface;
+ @Before
+ public void setup() {
+ mActivity = mActivityRule.getActivity();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // The test font has following coverage and width.
+ // U+0020: 10em
+ // U+002E (.): 10em
+ // U+0043 (C): 100em
+ // U+0049 (I): 1em
+ // U+004C (L): 50em
+ // U+0056 (V): 5em
+ // U+0058 (X): 10em
+ // U+005F (_): 0em
+ // U+05D0 : 1em // HEBREW LETTER ALEF
+ // U+05D1 : 5em // HEBREW LETTER BET
+ // U+FFFD (invalid surrogate will be replaced to this): 7em
+ // U+10331 (\uD800\uDF31): 10em
+ // Undefined : 0.5em
+ mTypeface = Typeface.createFromAsset(mInstrumentation.getTargetContext().getAssets(),
+ "fonts/StaticLayoutLineBreakingTestFont.ttf");
+ }
+
+ private TextView createTextView(String text, float textSize, int width, int height) {
+ final TextView textView = new TextView(mActivity);
+ textView.setTypeface(mTypeface);
+
+ textView.setText(text);
+ // Make 1 em equal to 10 pixels.
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ textView.measure(
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
+ textView.layout(0, 0, width, height);
+ return textView;
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_LTR() {
+ final String text = "IIVX";
+ final TextView textView = createTextView(text, 10.0f, 200, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+ new RectF(10.0f, layout.getLineTop(0), 20.0f, layout.getLineBottom(0)),
+ new RectF(20.0f, layout.getLineTop(0), 70.0f, layout.getLineBottom(0)),
+ new RectF(70.0f, layout.getLineTop(0), 170.0f, layout.getLineBottom(0))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_LTR_multiline() {
+ final String text = "IVVI";
+ final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+ new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+ // The second line.
+ new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)),
+ new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_LTR_newline() {
+ final String text = "IV\nVI";
+ final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+ new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+ // Newline belongs to the first line, and it has 0 width in the font.
+ new RectF(60.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+ // The second line.
+ new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)),
+ new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_RTL() {
+ final String text = "\u05D0\u05D0\u05D1\u05D1";
+ final TextView textView = createTextView(text, 10.0f, 200, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(190.0f, layout.getLineTop(0), 200.0f, layout.getLineBottom(0)),
+ new RectF(180.0f, layout.getLineTop(0), 190.0f, layout.getLineBottom(0)),
+ new RectF(130.0f, layout.getLineTop(0), 180.0f, layout.getLineBottom(0)),
+ new RectF(80.0f, layout.getLineTop(0), 130.0f, layout.getLineBottom(0))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_RTL_multiline() {
+ final String text = "\u05D0\u05D1\u05D1\u05D0";
+ final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(90.0f, layout.getLineTop(0), 100.0f, layout.getLineBottom(0)),
+ new RectF(40.0f, layout.getLineTop(0), 90.0f, layout.getLineBottom(0)),
+ // The second line
+ new RectF(50.0f, layout.getLineTop(1), 100.0f, layout.getLineBottom(1)),
+ new RectF(40.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_RTL_newline() {
+ final String text = "\u05D0\u05D1\n\u05D1\u05D0";
+ final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(90.0f, layout.getLineTop(0), 100.0f, layout.getLineBottom(0)),
+ new RectF(40.0f, layout.getLineTop(0), 90.0f, layout.getLineBottom(0)),
+ // Newline belongs to the first line, and it has 0 width in the font.
+ new RectF(40.0f, layout.getLineTop(0), 40.0f, layout.getLineBottom(0)),
+ // The second line
+ new RectF(50.0f, layout.getLineTop(1), 100.0f, layout.getLineBottom(1)),
+ new RectF(40.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ // Newline is in an RTL run.
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_BiDi() {
+ final String text = "IV\u05D0\u05D1IV";
+ final TextView textView = createTextView(text, 10.0f, 200, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+ new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+ new RectF(110.0f, layout.getLineTop(0), 120.0f, layout.getLineBottom(0)),
+ new RectF(60.0f, layout.getLineTop(0), 110.0f, layout.getLineBottom(0)),
+ new RectF(120.0f, layout.getLineTop(0), 130.0f, layout.getLineBottom(0)),
+ new RectF(130.0f, layout.getLineTop(0), 180.0f, layout.getLineBottom(0))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_BiDi_multiline() {
+ final String text = "IV\u05D0\u05D1IV";
+ final TextView textView = createTextView(text, 10.0f, 100, 1000);
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final Layout layout = textView.getLayout();
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(0.0f, layout.getLineTop(0), 10.0f, layout.getLineBottom(0)),
+ new RectF(10.0f, layout.getLineTop(0), 60.0f, layout.getLineBottom(0)),
+ new RectF(60.0f, layout.getLineTop(0), 70.0f, layout.getLineBottom(0)),
+ // The second line.
+ new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)),
+ new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1)),
+ // The third line
+ new RectF(0.0f, layout.getLineTop(2), 50.0f, layout.getLineBottom(2))
+ };
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_charactersWithInvisibleRegion() {
+ final String text = "IVVI";
+ final TextView textView = createTextView(text, 10.0f, 100, 1000);
+ final Layout layout = textView.getLayout();
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ final int verticalOffset = -50;
+ // Make viewToContentVerticalOffset -50px to simulate the case where TextView is scrolled.
+ textView.populateCharacterBounds(builder, 0, text.length(), 0, verticalOffset);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ final float firstLineTop = layout.getLineTop(0) + verticalOffset;
+ final float firstLineBottom = layout.getLineBottom(0) + verticalOffset;
+
+ final float secondLineTop = layout.getLineTop(1) + verticalOffset;
+ final float secondLineBottom = layout.getLineBottom(1) + verticalOffset;
+ final RectF[] expectedCharacterBounds = new RectF[] {
+ new RectF(0.0f, firstLineTop, 10.0f, firstLineBottom),
+ new RectF(10.0f, firstLineTop, 60.0f, firstLineBottom),
+ new RectF(0.0f, secondLineTop, 50.0f, secondLineBottom),
+ new RectF(50.0f, secondLineTop, 60.0f, secondLineBottom)
+ };
+
+ assertCharacterBounds(expectedCharacterBounds, cursorAnchorInfo);
+
+ final int[] expectedCharacterBoundsFlags = new int[] {
+ FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION,
+ // The second line is visible.
+ FLAG_HAS_VISIBLE_REGION,
+ FLAG_HAS_VISIBLE_REGION
+ };
+ assertCharacterBoundsFlags(expectedCharacterBoundsFlags, cursorAnchorInfo);
+ }
+
+ @Test
+ public void testPopulateCharacterBounds_withinRange() {
+ final String text = "IVVI";
+ final TextView textView = createTextView(text, 10.0f, 100, 1000);
+ final Layout layout = textView.getLayout();
+
+ final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
+ builder.setMatrix(Matrix.IDENTITY_MATRIX);
+ // Only query for character bounds within the range [2, 4).
+ textView.populateCharacterBounds(builder, 2, 4, 0, 0);
+
+ final CursorAnchorInfo cursorAnchorInfo = builder.build();
+
+ assertThat(cursorAnchorInfo.getCharacterBounds(2)).isEqualTo(
+ new RectF(0.0f, layout.getLineTop(1), 50.0f, layout.getLineBottom(1)));
+ assertThat(cursorAnchorInfo.getCharacterBounds(3)).isEqualTo(
+ new RectF(50.0f, layout.getLineTop(1), 60.0f, layout.getLineBottom(1)));
+
+ assertThat(cursorAnchorInfo.getCharacterBoundsFlags(2)).isEqualTo(FLAG_HAS_VISIBLE_REGION);
+ assertThat(cursorAnchorInfo.getCharacterBoundsFlags(3)).isEqualTo(FLAG_HAS_VISIBLE_REGION);
+ }
+
+ private static void assertCharacterBounds(RectF[] expected,
+ CursorAnchorInfo cursorAnchorInfo) {
+ final RectF[] characterBounds = new RectF[expected.length];
+ for (int i = 0; i < expected.length; ++i) {
+ characterBounds[i] = cursorAnchorInfo.getCharacterBounds(i);
+ }
+ assertArrayEquals(expected, characterBounds);
+ }
+
+ private static void assertCharacterBoundsFlags(int[] expected,
+ CursorAnchorInfo cursorAnchorInfo) {
+ final int[] characterBoundsFlags = new int[expected.length];
+ for (int i = 0; i < expected.length; ++i) {
+ characterBoundsFlags[i] = cursorAnchorInfo.getCharacterBoundsFlags(i);
+ }
+ assertArrayEquals(expected, characterBoundsFlags);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java
index ce69f12..bbbc423 100644
--- a/core/tests/coretests/src/android/window/BackNavigationTest.java
+++ b/core/tests/coretests/src/android/window/BackNavigationTest.java
@@ -91,7 +91,8 @@
private void assertCallbackIsCalled(CountDownLatch latch) {
try {
mInstrumentation.getUiAutomation().waitForIdle(500, 1000);
- BackNavigationInfo info = ActivityTaskManager.getService().startBackNavigation(true);
+ BackNavigationInfo info = ActivityTaskManager.getService()
+ .startBackNavigation(true, null);
assertNotNull("BackNavigationInfo is null", info);
assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback());
info.getOnBackInvokedCallback().onBackInvoked();
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index b5194f6..f448cb3 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -66,7 +66,6 @@
MockitoAnnotations.initMocks(this);
mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
mDispatcher.attachToWindow(mWindowSession, mWindow);
- mDispatcher.onWindowFocusChanged(true);
}
private void waitForIdle() {
@@ -153,31 +152,4 @@
waitForIdle();
verify(mCallback2).onBackStarted();
}
-
- @Test
- public void skipBackInvokeWhenNoFocus() throws RemoteException {
- ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
- ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
-
- mDispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
-
- verify(mWindowSession, times(1)).setOnBackInvokedCallbackInfo(
- Mockito.eq(mWindow),
- captor.capture());
-
- verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
-
- // Should invoke back if it's still in focused.
- captor.getValue().getCallback().onBackInvoked();
- waitForIdle();
- verify(mCallback1).onBackInvoked();
-
- // In case the focus has lost during back gesture.
- mDispatcher.onWindowFocusChanged(false);
-
- captor.getValue().getCallback().onBackInvoked();
- waitForIdle();
- verifyZeroInteractions(mCallback1);
- }
}
diff --git a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
deleted file mode 100644
index e8793a9..0000000
--- a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.internal.util;
-
-import androidx.test.filters.SmallTest;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-
-public class ArrayUtilsTest extends TestCase {
-
- @SmallTest
- public void testUnstableRemoveIf() throws Exception {
- java.util.function.Predicate<Object> isNull = new java.util.function.Predicate<Object>() {
- @Override
- public boolean test(Object o) {
- return o == null;
- }
- };
-
- final Object a = new Object();
- final Object b = new Object();
- final Object c = new Object();
-
- ArrayList<Object> collection = null;
- assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
-
- collection = new ArrayList<>();
- assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
-
- collection = new ArrayList<>(Collections.singletonList(a));
- assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(1, collection.size());
- assertTrue(collection.contains(a));
-
- collection = new ArrayList<>(Collections.singletonList(null));
- assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(0, collection.size());
-
- collection = new ArrayList<>(Arrays.asList(a, b));
- assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(2, collection.size());
- assertTrue(collection.contains(a));
- assertTrue(collection.contains(b));
-
- collection = new ArrayList<>(Arrays.asList(a, null));
- assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(1, collection.size());
- assertTrue(collection.contains(a));
-
- collection = new ArrayList<>(Arrays.asList(null, a));
- assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(1, collection.size());
- assertTrue(collection.contains(a));
-
- collection = new ArrayList<>(Arrays.asList(null, null));
- assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(0, collection.size());
-
- collection = new ArrayList<>(Arrays.asList(a, b, c));
- assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(3, collection.size());
- assertTrue(collection.contains(a));
- assertTrue(collection.contains(b));
- assertTrue(collection.contains(c));
-
- collection = new ArrayList<>(Arrays.asList(a, b, null));
- assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(2, collection.size());
- assertTrue(collection.contains(a));
- assertTrue(collection.contains(b));
-
- collection = new ArrayList<>(Arrays.asList(a, null, b));
- assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(2, collection.size());
- assertTrue(collection.contains(a));
- assertTrue(collection.contains(b));
-
- collection = new ArrayList<>(Arrays.asList(null, a, b));
- assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(2, collection.size());
- assertTrue(collection.contains(a));
- assertTrue(collection.contains(b));
-
- collection = new ArrayList<>(Arrays.asList(a, null, null));
- assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(1, collection.size());
- assertTrue(collection.contains(a));
-
- collection = new ArrayList<>(Arrays.asList(null, null, a));
- assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(1, collection.size());
- assertTrue(collection.contains(a));
-
- collection = new ArrayList<>(Arrays.asList(null, a, null));
- assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(1, collection.size());
- assertTrue(collection.contains(a));
-
- collection = new ArrayList<>(Arrays.asList(null, null, null));
- assertEquals(3, ArrayUtils.unstableRemoveIf(collection, isNull));
- assertEquals(0, collection.size());
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_passesWhenRangeInsideArray() {
- ArrayUtils.throwsIfOutOfBounds(10, 2, 6);
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_passesWhenRangeIsWholeArray() {
- ArrayUtils.throwsIfOutOfBounds(10, 0, 10);
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_passesWhenEmptyRangeAtStart() {
- ArrayUtils.throwsIfOutOfBounds(10, 0, 0);
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_passesWhenEmptyRangeAtEnd() {
- ArrayUtils.throwsIfOutOfBounds(10, 10, 0);
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_passesWhenEmptyArray() {
- ArrayUtils.throwsIfOutOfBounds(0, 0, 0);
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_failsWhenRangeStartNegative() {
- try {
- ArrayUtils.throwsIfOutOfBounds(10, -1, 5);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- // expected
- }
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_failsWhenCountNegative() {
- try {
- ArrayUtils.throwsIfOutOfBounds(10, 5, -1);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- // expected
- }
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_failsWhenRangeStartTooHigh() {
- try {
- ArrayUtils.throwsIfOutOfBounds(10, 11, 0);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- // expected
- }
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_failsWhenRangeEndTooHigh() {
- try {
- ArrayUtils.throwsIfOutOfBounds(10, 5, 6);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- // expected
- }
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_failsWhenLengthNegative() {
- try {
- ArrayUtils.throwsIfOutOfBounds(-1, 0, 0);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- // expected
- }
- }
-
- @SmallTest
- public void testThrowsIfOutOfBounds_failsWhenOverflowRangeEndTooHigh() {
- try {
- ArrayUtils.throwsIfOutOfBounds(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
- fail();
- } catch (ArrayIndexOutOfBoundsException expected) {
- // expected
- }
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
index 9d77d16..a47868d 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -223,15 +223,11 @@
public void testLegacyPasswordToHash() {
String password = "1234";
- LockscreenCredential credential = LockscreenCredential.createPassword(password);
String salt = "6d5331dd120077a0";
String expectedHash =
"2DD04348ADBF8F4CABD7F722DC2E2887FAD4B6020A0C3E02C831E09946F0554FDC13B155";
assertThat(
- credential.legacyPasswordToHash(salt.getBytes()))
- .isEqualTo(expectedHash);
- assertThat(
LockscreenCredential.legacyPasswordToHash(
password.getBytes(), salt.getBytes()))
.isEqualTo(expectedHash);
@@ -239,10 +235,8 @@
public void testLegacyPasswordToHashInvalidInput() {
String password = "1234";
- LockscreenCredential credential = LockscreenCredential.createPassword(password);
String salt = "6d5331dd120077a0";
- assertThat(credential.legacyPasswordToHash(/* salt= */ null)).isNull();
assertThat(LockscreenCredential.legacyPasswordToHash(
password.getBytes(), /* salt= */ null)).isNull();
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 47f70dd..f4a6f02 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -54,7 +54,6 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.UserHandle;
@@ -339,8 +338,7 @@
doNothing().when(packageInfo).updateApplicationInfo(any(), any());
return new ActivityClientRecord(mock(IBinder.class), Intent.makeMainActivity(component),
- 0 /* ident */, info, new Configuration(),
- CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* referrer */,
+ 0 /* ident */, info, new Configuration(), null /* referrer */,
null /* voiceInteractor */, null /* state */, null /* persistentState */,
null /* pendingResults */, null /* pendingNewIntents */,
null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
diff --git a/core/tests/overlaytests/device/AndroidTest.xml b/core/tests/overlaytests/device/AndroidTest.xml
index 2d7d9b4..4099ec1 100644
--- a/core/tests/overlaytests/device/AndroidTest.xml
+++ b/core/tests/overlaytests/device/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="remount-system" value="true" />
<option name="push" value="OverlayDeviceTests.apk->/system/app/OverlayDeviceTests.apk" />
diff --git a/core/tests/screenshothelpertests/OWNERS b/core/tests/screenshothelpertests/OWNERS
new file mode 100644
index 0000000..23dc8fb
--- /dev/null
+++ b/core/tests/screenshothelpertests/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 801321
+file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 4b81737..fd4fb13 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -80,13 +80,14 @@
@Test
public void testFullscreenScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, mHandler, null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
public void testSelectedRegionScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION, false, false, mHandler,
- null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
@@ -101,8 +102,10 @@
long timeoutMs = 10;
CountDownLatch lock = new CountDownLatch(1);
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, timeoutMs,
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
mHandler,
+ timeoutMs,
uri -> {
assertNull(uri);
lock.countDown();
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index cb30b3f..72f3af6 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -16,10 +16,14 @@
package com.android.internal.util;
-import static com.android.internal.util.ArrayUtils.concatElements;
-
import static org.junit.Assert.assertArrayEquals;
+import androidx.test.filters.SmallTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
import junit.framework.TestCase;
/**
@@ -156,61 +160,285 @@
ArrayUtils.removeLong(new long[] { 1, 2, 3, 1 }, 1));
}
- public void testConcatEmpty() throws Exception {
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, null, null));
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, new Long[] {}, null));
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, null, new Long[] {}));
- assertArrayEquals(new Long[] {},
- concatElements(Long.class, new Long[] {}, new Long[] {}));
+ public void testConcat_zeroObjectArrays() {
+ // empty varargs array
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class));
+ // null varargs array
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[][]) null));
}
- public void testconcatElements() throws Exception {
+ public void testConcat_oneObjectArray() {
+ assertArrayEquals(new String[] { "1", "2" },
+ ArrayUtils.concat(String.class, new String[] { "1", "2" }));
+ }
+
+ public void testConcat_oneEmptyObjectArray() {
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, (String[]) null));
+ assertArrayEquals(new String[] {}, ArrayUtils.concat(String.class, new String[] {}));
+ }
+
+ public void testConcat_twoObjectArrays() {
assertArrayEquals(new Long[] { 1L },
- concatElements(Long.class, new Long[] { 1L }, new Long[] {}));
+ ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] {}));
assertArrayEquals(new Long[] { 1L },
- concatElements(Long.class, new Long[] {}, new Long[] { 1L }));
+ ArrayUtils.concat(Long.class, new Long[] {}, new Long[] { 1L }));
assertArrayEquals(new Long[] { 1L, 2L },
- concatElements(Long.class, new Long[] { 1L }, new Long[] { 2L }));
+ ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] { 2L }));
assertArrayEquals(new Long[] { 1L, 2L, 3L, 4L },
- concatElements(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L }));
+ ArrayUtils.concat(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L }));
}
- public void testConcatElements_threeWay() {
+ public void testConcat_twoEmptyObjectArrays() {
+ assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, null));
+ assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, new Long[] {}, null));
+ assertArrayEquals(new Long[] {}, ArrayUtils.concat(Long.class, null, new Long[] {}));
+ assertArrayEquals(new Long[] {},
+ ArrayUtils.concat(Long.class, new Long[] {}, new Long[] {}));
+ }
+
+ public void testConcat_threeObjectArrays() {
String[] array1 = { "1", "2" };
String[] array2 = { "3", "4" };
String[] array3 = { "5", "6" };
- String[] expectation = {"1", "2", "3", "4", "5", "6"};
+ String[] expectation = { "1", "2", "3", "4", "5", "6" };
- String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3);
- assertArrayEquals(expectation, concatResult);
+ assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3));
}
-
- public void testConcatElements_threeWayWithNull() {
+ public void testConcat_threeObjectArraysWithNull() {
String[] array1 = { "1", "2" };
String[] array2 = null;
String[] array3 = { "5", "6" };
- String[] expectation = {"1", "2", "5", "6"};
+ String[] expectation = { "1", "2", "5", "6" };
- String[] concatResult = ArrayUtils.concatElements(String.class, array1, array2, array3);
- assertArrayEquals(expectation, concatResult);
+ assertArrayEquals(expectation, ArrayUtils.concat(String.class, array1, array2, array3));
}
- public void testConcatElements_zeroElements() {
- String[] expectation = new String[0];
-
- String[] concatResult = ArrayUtils.concatElements(String.class);
- assertArrayEquals(expectation, concatResult);
+ public void testConcat_zeroByteArrays() {
+ // empty varargs array
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat());
+ // null varargs array
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[][]) null));
}
- public void testConcatElements_oneNullElement() {
- String[] expectation = new String[0];
-
- String[] concatResult = ArrayUtils.concatElements(String.class, null);
- assertArrayEquals(expectation, concatResult);
+ public void testConcat_oneByteArray() {
+ assertArrayEquals(new byte[] { 1, 2 }, ArrayUtils.concat(new byte[] { 1, 2 }));
}
+ public void testConcat_oneEmptyByteArray() {
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}));
+ }
+
+ public void testConcat_twoByteArrays() {
+ assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] { 1 }, new byte[] {}));
+ assertArrayEquals(new byte[] { 1 }, ArrayUtils.concat(new byte[] {}, new byte[] { 1 }));
+ assertArrayEquals(new byte[] { 1, 2 },
+ ArrayUtils.concat(new byte[] { 1 }, new byte[] { 2 }));
+ assertArrayEquals(new byte[] { 1, 2, 3, 4 },
+ ArrayUtils.concat(new byte[] { 1, 2 }, new byte[] { 3, 4 }));
+ }
+
+ public void testConcat_twoEmptyByteArrays() {
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, null));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, null));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat((byte[]) null, new byte[] {}));
+ assertArrayEquals(new byte[] {}, ArrayUtils.concat(new byte[] {}, new byte[] {}));
+ }
+
+ public void testConcat_threeByteArrays() {
+ byte[] array1 = { 1, 2 };
+ byte[] array2 = { 3, 4 };
+ byte[] array3 = { 5, 6 };
+ byte[] expectation = { 1, 2, 3, 4, 5, 6 };
+
+ assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3));
+ }
+
+ public void testConcat_threeByteArraysWithNull() {
+ byte[] array1 = { 1, 2 };
+ byte[] array2 = null;
+ byte[] array3 = { 5, 6 };
+ byte[] expectation = { 1, 2, 5, 6 };
+
+ assertArrayEquals(expectation, ArrayUtils.concat(array1, array2, array3));
+ }
+
+ @SmallTest
+ public void testUnstableRemoveIf() throws Exception {
+ java.util.function.Predicate<Object> isNull = new java.util.function.Predicate<Object>() {
+ @Override
+ public boolean test(Object o) {
+ return o == null;
+ }
+ };
+
+ final Object a = new Object();
+ final Object b = new Object();
+ final Object c = new Object();
+
+ ArrayList<Object> collection = null;
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+
+ collection = new ArrayList<>();
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+
+ collection = new ArrayList<>(Collections.singletonList(a));
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Collections.singletonList(null));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(0, collection.size());
+
+ collection = new ArrayList<>(Arrays.asList(a, b));
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(a, null));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, a));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, null));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(0, collection.size());
+
+ collection = new ArrayList<>(Arrays.asList(a, b, c));
+ assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(3, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+ assertTrue(collection.contains(c));
+
+ collection = new ArrayList<>(Arrays.asList(a, b, null));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(a, null, b));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(null, a, b));
+ assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(2, collection.size());
+ assertTrue(collection.contains(a));
+ assertTrue(collection.contains(b));
+
+ collection = new ArrayList<>(Arrays.asList(a, null, null));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, null, a));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, a, null));
+ assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(1, collection.size());
+ assertTrue(collection.contains(a));
+
+ collection = new ArrayList<>(Arrays.asList(null, null, null));
+ assertEquals(3, ArrayUtils.unstableRemoveIf(collection, isNull));
+ assertEquals(0, collection.size());
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenRangeInsideArray() {
+ ArrayUtils.throwsIfOutOfBounds(10, 2, 6);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenRangeIsWholeArray() {
+ ArrayUtils.throwsIfOutOfBounds(10, 0, 10);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenEmptyRangeAtStart() {
+ ArrayUtils.throwsIfOutOfBounds(10, 0, 0);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenEmptyRangeAtEnd() {
+ ArrayUtils.throwsIfOutOfBounds(10, 10, 0);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenEmptyArray() {
+ ArrayUtils.throwsIfOutOfBounds(0, 0, 0);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenRangeStartNegative() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, -1, 5);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenCountNegative() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, 5, -1);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenRangeStartTooHigh() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, 11, 0);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenRangeEndTooHigh() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, 5, 6);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenLengthNegative() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(-1, 0, 0);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenOverflowRangeEndTooHigh() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6897c01..9a1b8a9 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -171,10 +171,11 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="audioserver" />
<assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
<assign-permission name="android.permission.WAKE_LOCK" uid="cameraserver" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ae1c9461..505d2a3 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1075,6 +1075,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1075136930": {
+ "message": "startLockTaskMode: Can't lock due to auth",
+ "level": "WARN",
+ "group": "WM_DEBUG_LOCKTASK",
+ "at": "com\/android\/server\/wm\/LockTaskController.java"
+ },
"-1069336896": {
"message": "onRootTaskOrderChanged(): rootTask=%s",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index f4f5e1e..8ae0d3d 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1244,14 +1244,16 @@
nativePtrs[i++] = entry.getValue().native_instance;
writeString(namesBytes, entry.getKey());
}
- int typefacesBytesCount = nativeWriteTypefaces(null, nativePtrs);
// int (typefacesBytesCount), typefaces, namesBytes
+ final int typefaceBytesCountSize = Integer.BYTES;
+ int typefacesBytesCount = nativeWriteTypefaces(null, typefaceBytesCountSize, nativePtrs);
SharedMemory sharedMemory = SharedMemory.create(
- "fontMap", Integer.BYTES + typefacesBytesCount + namesBytes.size());
+ "fontMap", typefaceBytesCountSize + typefacesBytesCount + namesBytes.size());
ByteBuffer writableBuffer = sharedMemory.mapReadWrite().order(ByteOrder.BIG_ENDIAN);
try {
writableBuffer.putInt(typefacesBytesCount);
- int writtenBytesCount = nativeWriteTypefaces(writableBuffer.slice(), nativePtrs);
+ int writtenBytesCount =
+ nativeWriteTypefaces(writableBuffer, writableBuffer.position(), nativePtrs);
if (writtenBytesCount != typefacesBytesCount) {
throw new IOException(String.format("Unexpected bytes written: %d, expected: %d",
writtenBytesCount, typefacesBytesCount));
@@ -1276,7 +1278,9 @@
@NonNull ByteBuffer buffer, @NonNull Map<String, Typeface> out)
throws IOException {
int typefacesBytesCount = buffer.getInt();
- long[] nativePtrs = nativeReadTypefaces(buffer.slice());
+ // Note: Do not call buffer.slice(), as nativeReadTypefaces() expects
+ // that buffer.address() is page-aligned.
+ long[] nativePtrs = nativeReadTypefaces(buffer, buffer.position());
if (nativePtrs == null) {
throw new IOException("Could not read typefaces");
}
@@ -1598,9 +1602,10 @@
private static native void nativeRegisterGenericFamily(String str, long nativePtr);
private static native int nativeWriteTypefaces(
- @Nullable ByteBuffer buffer, @NonNull long[] nativePtrs);
+ @Nullable ByteBuffer buffer, int position, @NonNull long[] nativePtrs);
- private static native @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer);
+ private static native
+ @Nullable long[] nativeReadTypefaces(@NonNull ByteBuffer buffer, int position);
private static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface);
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 6939a72..47de37d 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -359,10 +359,10 @@
/** @hide */
@TestApi
public Region getSafeZone() {
- mMaskMatrix.reset();
+ Path mask = getIconMask();
mMaskMatrix.setScale(SAFEZONE_SCALE, SAFEZONE_SCALE, getBounds().centerX(), getBounds().centerY());
Path p = new Path();
- mMask.transform(mMaskMatrix, p);
+ mask.transform(mMaskMatrix, p);
Region safezoneRegion = new Region(getBounds());
safezoneRegion.setPath(p, safezoneRegion);
return safezoneRegion;
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 4080b99..5cba3b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -31,7 +31,6 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
-import android.content.Context;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
@@ -56,6 +55,7 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
@@ -186,41 +186,45 @@
@Nullable
private RunningTaskInfo mLastFocusedTaskInfo;
- public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
- this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */,
+ public ShellTaskOrganizer(ShellExecutor mainExecutor) {
+ this(null /* shellInit */, null /* taskOrganizerController */, null /* compatUI */,
Optional.empty() /* unfoldAnimationController */,
- Optional.empty() /* recentTasksController */);
+ Optional.empty() /* recentTasksController */,
+ mainExecutor);
}
- public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- CompatUIController compatUI) {
- this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
- Optional.empty() /* unfoldAnimationController */,
- Optional.empty() /* recentTasksController */);
- }
-
- public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
- CompatUIController compatUI,
+ public ShellTaskOrganizer(ShellInit shellInit,
+ @Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasks) {
- this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
- unfoldAnimationController, recentTasks);
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor) {
+ this(shellInit, null /* taskOrganizerController */, compatUI,
+ unfoldAnimationController, recentTasks, mainExecutor);
}
@VisibleForTesting
- protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
- ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
+ protected ShellTaskOrganizer(ShellInit shellInit,
+ ITaskOrganizerController taskOrganizerController,
+ @Nullable CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasks) {
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor) {
super(taskOrganizerController, mainExecutor);
mCompatUI = compatUI;
mRecentTasks = recentTasks;
mUnfoldAnimationController = unfoldAnimationController.orElse(null);
- if (compatUI != null) {
- compatUI.setCompatUICallback(this);
+ if (shellInit != null) {
+ shellInit.addInitCallback(this::onInit, this);
}
}
+ private void onInit() {
+ if (mCompatUI != null) {
+ mCompatUI.setCompatUICallback(this);
+ }
+ registerOrganizer();
+ }
+
@Override
public List<TaskAppearedInfo> registerOrganizer() {
synchronized (mLock) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 82b0270..b305897 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -28,6 +28,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
/**
@@ -38,13 +39,17 @@
private final Context mContext;
private final Transitions mTransitions;
- public ActivityEmbeddingController(Context context, Transitions transitions) {
+ public ActivityEmbeddingController(Context context, ShellInit shellInit,
+ Transitions transitions) {
mContext = context;
mTransitions = transitions;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
/** Registers to handle transitions. */
- public void init() {
+ public void onInit() {
mTransitions.addHandler(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index e35e4ac..d3dc7e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -30,13 +30,20 @@
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.HardwareBuffer;
+import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.Log;
+import android.view.IWindowFocusObserver;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -110,6 +117,24 @@
MAX_TRANSITION_DURATION);
};
+ @VisibleForTesting
+ final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
+ @Override
+ public void focusGained(IBinder inputToken) { }
+ @Override
+ public void focusLost(IBinder inputToken) {
+ mShellExecutor.execute(() -> {
+ if (!mBackGestureStarted || mTransitionInProgress) {
+ // If an uninterruptible transition is already in progress, we should ignore
+ // this due to the transition may cause focus lost. (alpha = 0)
+ return;
+ }
+ setTriggerBack(false);
+ onGestureFinished(false);
+ });
+ }
+ };
+
public BackAnimationController(
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
@@ -269,17 +294,20 @@
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation.
- initAnimation(touchX, touchY);
+ onGestureStarted(touchX, touchY);
}
onMove(touchX, touchY, swipeEdge);
} else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
"Finishing gesture with event action: %d", keyAction);
- onGestureFinished();
+ if (keyAction == MotionEvent.ACTION_CANCEL) {
+ mTriggerBack = false;
+ }
+ onGestureFinished(true);
}
}
- private void initAnimation(float touchX, float touchY) {
+ private void onGestureStarted(float touchX, float touchY) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
if (mBackGestureStarted || mBackNavigationInfo != null) {
Log.e(TAG, "Animation is being initialized but is already started.");
@@ -291,7 +319,8 @@
try {
boolean requestAnimation = mEnableAnimations.get();
- mBackNavigationInfo = mActivityTaskManager.startBackNavigation(requestAnimation);
+ mBackNavigationInfo =
+ mActivityTaskManager.startBackNavigation(requestAnimation, mFocusObserver);
onBackNavigationInfoReceived(mBackNavigationInfo);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
@@ -303,7 +332,6 @@
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo);
if (backNavigationInfo == null) {
Log.e(TAG, "Received BackNavigationInfo is null.");
- finishAnimation();
return;
}
int backType = backNavigationInfo.getType();
@@ -379,11 +407,47 @@
dispatchOnBackProgressed(targetCallback, backEvent);
}
- private void onGestureFinished() {
+ private void injectBackKey() {
+ sendBackEvent(KeyEvent.ACTION_DOWN);
+ sendBackEvent(KeyEvent.ACTION_UP);
+ }
+
+ private void sendBackEvent(int action) {
+ final long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
+ 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
+ if (!InputManager.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) {
+ Log.e(TAG, "Inject input event fail");
+ }
+ }
+
+ private void onGestureFinished(boolean fromTouch) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
- if (!mBackGestureStarted || mBackNavigationInfo == null) {
+ if (fromTouch) {
+ // Let touch reset the flag otherwise it will start a new back navigation and refresh
+ // the info when received a new move event.
+ mBackGestureStarted = false;
+ }
+
+ if (mTransitionInProgress) {
return;
}
+
+ if (mBackNavigationInfo == null) {
+ // No focus window found or core are running recents animation, inject back key as
+ // legacy behavior.
+ if (mTriggerBack) {
+ injectBackKey();
+ }
+ finishAnimation();
+ return;
+ }
+
int backType = mBackNavigationInfo.getType();
boolean shouldDispatchToLauncher = shouldDispatchToLauncher(backType);
IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher
@@ -473,7 +537,6 @@
private void finishAnimation() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
- mBackGestureStarted = false;
mTouchEventDelta.set(0, 0);
mInitTouchLocation.set(0, 0);
BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
@@ -483,6 +546,7 @@
if (backNavigationInfo == null) {
return;
}
+
RemoteAnimationTarget animationTarget = backNavigationInfo.getDepartingAnimationTarget();
if (animationTarget != null) {
if (animationTarget.leash != null && animationTarget.leash.isValid()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d7f1292..3982616 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -70,12 +70,10 @@
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseSetArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -103,14 +101,17 @@
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -177,8 +178,8 @@
private int mCurrentUserId;
// Current profiles of the user (e.g. user with a workprofile)
private SparseArray<UserInfo> mCurrentProfiles;
- // Saves notification keys of active bubbles when users are switched.
- private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+ // Saves data about active bubbles when users are switched.
+ private final SparseArray<UserBubbleData> mSavedUserBubbleData;
// Used when ranking updates occur and we check if things should bubble / unbubble
private NotificationListenerService.Ranking mTmpRanking;
@@ -227,6 +228,7 @@
public BubbleController(Context context,
+ ShellInit shellInit,
ShellController shellController,
BubbleData data,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
@@ -271,7 +273,7 @@
mCurrentUserId = ActivityManager.getCurrentUser();
mBubblePositioner = positioner;
mBubbleData = data;
- mSavedBubbleKeysPerUser = new SparseSetArray<>();
+ mSavedUserBubbleData = new SparseArray<>();
mBubbleIconFactory = new BubbleIconFactory(context);
mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context);
mDisplayController = displayController;
@@ -279,6 +281,7 @@
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
+ shellInit.addInitCallback(this::onInit, this);
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -300,7 +303,7 @@
});
}
- public void initialize() {
+ protected void onInit() {
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
@@ -420,6 +423,13 @@
List<UserInfo> users = mUserManager.getAliveUsers();
mDataRepository.sanitizeBubbles(users);
+ // Init profiles
+ SparseArray<UserInfo> userProfiles = new SparseArray<>();
+ for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+ userProfiles.put(user.id, user);
+ }
+ mCurrentProfiles = userProfiles;
+
mShellController.addConfigurationChangeListener(this);
}
@@ -774,11 +784,13 @@
*/
private void saveBubbles(@UserIdInt int userId) {
// First clear any existing keys that might be stored.
- mSavedBubbleKeysPerUser.remove(userId);
+ mSavedUserBubbleData.remove(userId);
+ UserBubbleData userBubbleData = new UserBubbleData();
// Add in all active bubbles for the current user.
for (Bubble bubble : mBubbleData.getBubbles()) {
- mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
+ userBubbleData.add(bubble.getKey(), bubble.showInShade());
}
+ mSavedUserBubbleData.put(userId, userBubbleData);
}
/**
@@ -787,22 +799,23 @@
* @param userId the id of the user
*/
private void restoreBubbles(@UserIdInt int userId) {
- ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
- if (savedBubbleKeys == null) {
+ UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId);
+ if (savedBubbleData == null) {
// There were no bubbles saved for this used.
return;
}
- mSysuiProxy.getShouldRestoredEntries(savedBubbleKeys, (entries) -> {
+ mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> {
mMainExecutor.execute(() -> {
for (BubbleEntry e : entries) {
if (canLaunchInTaskView(mContext, e)) {
- updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
+ boolean showInShade = savedBubbleData.isShownInShade(e.getKey());
+ updateBubble(e, true /* suppressFlyout */, showInShade);
}
}
});
});
// Finally, remove the entries for this user now that bubbles are restored.
- mSavedBubbleKeysPerUser.remove(userId);
+ mSavedUserBubbleData.remove(userId);
}
@Override
@@ -993,7 +1006,19 @@
*/
@VisibleForTesting
public void updateBubble(BubbleEntry notif) {
- updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+ int bubbleUserId = notif.getStatusBarNotification().getUserId();
+ if (isCurrentProfile(bubbleUserId)) {
+ updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
+ } else {
+ // Skip update, but store it in user bubbles so it gets restored after user switch
+ mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
+ true /* shownInShade */);
+ if (DEBUG_BUBBLE_CONTROLLER) {
+ Log.d(TAG,
+ "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
+ + " current userId=" + mCurrentUserId);
+ }
+ }
}
/**
@@ -1842,4 +1867,33 @@
}
}
}
+
+ /**
+ * Bubble data that is stored per user.
+ * Used to store and restore active bubbles during user switching.
+ */
+ private static class UserBubbleData {
+ private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>();
+
+ /**
+ * Add bubble key and whether it should be shown in notification shade
+ */
+ void add(String key, boolean shownInShade) {
+ mKeyToShownInShadeMap.put(key, shownInShade);
+ }
+
+ /**
+ * Get all bubble keys stored for this user
+ */
+ Set<String> getKeys() {
+ return mKeyToShownInShadeMap.keySet();
+ }
+
+ /**
+ * Check if this bubble with the given key should be shown in the notification shade
+ */
+ boolean isShownInShade(String key) {
+ return mKeyToShownInShadeMap.get(key);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index f8ccf23..cf792cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -23,12 +23,10 @@
import android.app.NotificationChannel;
import android.content.pm.UserInfo;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.util.ArraySet;
import android.util.Pair;
import android.util.SparseArray;
@@ -42,6 +40,7 @@
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -284,7 +283,7 @@
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
- void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ void getShouldRestoredEntries(Set<String> savedBubbleKeys,
Consumer<List<BubbleEntry>> callback);
void setNotificationInterruption(String key);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 28c7367..ae1f433 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -28,6 +28,7 @@
import androidx.annotation.BinderThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -47,10 +48,15 @@
private final CopyOnWriteArrayList<OnDisplayChangingListener> mDisplayChangeListener =
new CopyOnWriteArrayList<>();
- public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) {
+ public DisplayChangeController(IWindowManager wmService, ShellInit shellInit,
+ ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
mWmService = wmService;
mControllerImpl = new DisplayChangeWindowControllerImpl();
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
try {
mWmService.setDisplayChangeWindowController(mControllerImpl);
} catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 764936c..f07ea75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -34,6 +34,7 @@
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.List;
@@ -57,19 +58,23 @@
private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>();
private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>();
- public DisplayController(Context context, IWindowManager wmService,
+ public DisplayController(Context context, IWindowManager wmService, ShellInit shellInit,
ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
mContext = context;
mWmService = wmService;
- mChangeController = new DisplayChangeController(mWmService, mainExecutor);
+ // TODO: Inject this instead
+ mChangeController = new DisplayChangeController(mWmService, shellInit, mainExecutor);
mDisplayContainerListener = new DisplayWindowListenerImpl();
+ // Note, add this after DisplaceChangeController is constructed to ensure that is
+ // initialized first
+ shellInit.addInitCallback(this::onInit, this);
}
/**
* Initializes the window listener.
*/
- public void initialize() {
+ public void onInit() {
try {
int[] displayIds = mWmService.registerDisplayWindowListener(mDisplayContainerListener);
for (int i = 0; i < displayIds.length; i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index b3f6247..266cf29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -44,6 +44,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.view.IInputMethodManager;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.concurrent.Executor;
@@ -74,18 +75,24 @@
private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
- public DisplayImeController(IWindowManager wmService, DisplayController displayController,
+ public DisplayImeController(IWindowManager wmService,
+ ShellInit shellInit,
+ DisplayController displayController,
DisplayInsetsController displayInsetsController,
- Executor mainExecutor, TransactionPool transactionPool) {
+ TransactionPool transactionPool,
+ Executor mainExecutor) {
mWmService = wmService;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
mTransactionPool = transactionPool;
+ shellInit.addInitCallback(this::onInit, this);
}
- /** Starts monitor displays changes and set insets controller for each displays. */
- public void startMonitorDisplays() {
+ /**
+ * Starts monitor displays changes and set insets controller for each displays.
+ */
+ public void onInit() {
mDisplayController.addDisplayWindowListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index f546f11..90a01f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -29,6 +29,7 @@
import androidx.annotation.BinderThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -45,17 +46,20 @@
private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
new SparseArray<>();
- public DisplayInsetsController(IWindowManager wmService, DisplayController displayController,
+ public DisplayInsetsController(IWindowManager wmService,
+ ShellInit shellInit,
+ DisplayController displayController,
ShellExecutor mainExecutor) {
mWmService = wmService;
mDisplayController = displayController;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
/**
* Starts listening for insets for each display.
**/
- public void initialize() {
+ public void onInit() {
mDisplayController.addDisplayWindowListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
index 806f795..10b121b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
@@ -92,6 +92,8 @@
*
* For example, this uses the same setup as above, but the interface provided (if bound) is used
* otherwise the default is created:
+ *
+ * BaseModule:
* @BindsOptionalOf
* @DynamicOverride
* abstract Interface dynamicInterface();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java
new file mode 100644
index 0000000..482b199
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTrigger.java
@@ -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.wm.shell.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * An annotation to specifically mark the provider that is triggering the creation of independent
+ * shell components that are not created as a part of the dependencies for interfaces passed to
+ * SysUI.
+ *
+ * TODO: This will be removed once we have a more explicit method for specifying components to start
+ * with SysUI
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellCreateTrigger {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java
new file mode 100644
index 0000000..31c6789
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/ShellCreateTriggerOverride.java
@@ -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.wm.shell.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * An annotation for non-base modules to specifically mark the provider that is triggering the
+ * creation of independent shell components that are not created as a part of the dependencies for
+ * interfaces passed to SysUI.
+ *
+ * TODO: This will be removed once we have a more explicit method for specifying components to start
+ * with SysUI
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ShellCreateTriggerOverride {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 5dd5149..3add417 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -29,8 +29,6 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.TaskViewFactoryController;
@@ -59,7 +57,7 @@
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
@@ -78,7 +76,9 @@
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
+import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -112,16 +112,20 @@
@WMSingleton
@Provides
static DisplayController provideDisplayController(Context context,
- IWindowManager wmService, @ShellMainThread ShellExecutor mainExecutor) {
- return new DisplayController(context, wmService, mainExecutor);
+ IWindowManager wmService,
+ ShellInit shellInit,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DisplayController(context, wmService, shellInit, mainExecutor);
}
@WMSingleton
@Provides
- static DisplayInsetsController provideDisplayInsetsController( IWindowManager wmService,
+ static DisplayInsetsController provideDisplayInsetsController(IWindowManager wmService,
+ ShellInit shellInit,
DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new DisplayInsetsController(wmService, displayController, mainExecutor);
+ return new DisplayInsetsController(wmService, shellInit, displayController,
+ mainExecutor);
}
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@@ -134,16 +138,17 @@
static DisplayImeController provideDisplayImeController(
@DynamicOverride Optional<DisplayImeController> overrideDisplayImeController,
IWindowManager wmService,
+ ShellInit shellInit,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- @ShellMainThread ShellExecutor mainExecutor,
- TransactionPool transactionPool
+ TransactionPool transactionPool,
+ @ShellMainThread ShellExecutor mainExecutor
) {
if (overrideDisplayImeController.isPresent()) {
return overrideDisplayImeController.get();
}
- return new DisplayImeController(wmService, displayController, displayInsetsController,
- mainExecutor, transactionPool);
+ return new DisplayImeController(wmService, shellInit, displayController,
+ displayInsetsController, transactionPool, mainExecutor);
}
@WMSingleton
@@ -155,42 +160,45 @@
@WMSingleton
@Provides
static DragAndDropController provideDragAndDropController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@ShellMainThread ShellExecutor mainExecutor) {
- return new DragAndDropController(context, shellController, displayController, uiEventLogger,
- iconProvider, mainExecutor);
+ return new DragAndDropController(context, shellInit, shellController, displayController,
+ uiEventLogger, iconProvider, mainExecutor);
}
@WMSingleton
@Provides
- static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
- Context context,
+ static ShellTaskOrganizer provideShellTaskOrganizer(
+ ShellInit shellInit,
CompatUIController compatUI,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasksOptional
+ Optional<RecentTasksController> recentTasksOptional,
+ @ShellMainThread ShellExecutor mainExecutor
) {
- return new ShellTaskOrganizer(mainExecutor, context, compatUI, unfoldAnimationController,
- recentTasksOptional);
+ return new ShellTaskOrganizer(shellInit, compatUI, unfoldAnimationController,
+ recentTasksOptional, mainExecutor);
}
@WMSingleton
@Provides
static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler,
Context context,
+ ShellInit shellInit,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasksOptional
+ Optional<RecentTasksController> recentTasksOptional,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler
) {
- return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue,
+ return new KidsModeTaskOrganizer(context, shellInit, syncTransactionQueue,
displayController, displayInsetsController, unfoldAnimationController,
- recentTasksOptional);
+ recentTasksOptional, mainExecutor, mainHandler);
}
@WMSingleton
@@ -256,6 +264,20 @@
return backAnimationController.map(BackAnimationController::getBackAnimationImpl);
}
+ @WMSingleton
+ @Provides
+ static Optional<BackAnimationController> provideBackAnimationController(
+ Context context,
+ @ShellMainThread ShellExecutor shellExecutor,
+ @ShellBackgroundThread Handler backgroundHandler
+ ) {
+ if (BackAnimationController.IS_ENABLED) {
+ return Optional.of(
+ new BackAnimationController(shellExecutor, backgroundHandler, context));
+ }
+ return Optional.empty();
+ }
+
//
// Bubbles (optional feature)
//
@@ -282,12 +304,15 @@
@Provides
static FullscreenTaskListener provideFullscreenTaskListener(
@DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
Optional<RecentTasksController> recentTasksOptional) {
if (fullscreenTaskListener.isPresent()) {
return fullscreenTaskListener.get();
} else {
- return new FullscreenTaskListener(syncQueue, recentTasksOptional);
+ return new FullscreenTaskListener(shellInit, shellTaskOrganizer, syncQueue,
+ recentTasksOptional);
}
}
@@ -339,15 +364,15 @@
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FreeformTaskListener<?> optionalFreeformTaskListener();
+ abstract FreeformComponents optionalFreeformComponents();
@WMSingleton
@Provides
- static Optional<FreeformTaskListener<?>> provideFreeformTaskListener(
- @DynamicOverride Optional<FreeformTaskListener<?>> freeformTaskListener,
+ static Optional<FreeformComponents> provideFreeformComponents(
+ @DynamicOverride Optional<FreeformComponents> freeformComponents,
Context context) {
- if (FreeformTaskListener.isFreeformEnabled(context)) {
- return freeformTaskListener;
+ if (FreeformComponents.isFreeformEnabled(context)) {
+ return freeformComponents;
}
return Optional.empty();
}
@@ -440,11 +465,13 @@
@Provides
static Optional<RecentTasksController> provideRecentTasksController(
Context context,
+ ShellInit shellInit,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
- RecentTasksController.create(context, taskStackListener, mainExecutor));
+ RecentTasksController.create(context, shellInit, taskStackListener,
+ mainExecutor));
}
//
@@ -459,12 +486,15 @@
@WMSingleton
@Provides
- static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool,
- DisplayController displayController, Context context,
+ static Transitions provideTransitions(Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer organizer,
+ TransactionPool pool,
+ DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
- return new Transitions(organizer, pool, displayController, context, mainExecutor,
+ return new Transitions(context, shellInit, organizer, pool, displayController, mainExecutor,
mainHandler, animExecutor);
}
@@ -542,11 +572,13 @@
@WMSingleton
@Provides
static StartingWindowController provideStartingWindowController(Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
@ShellSplashscreenThread ShellExecutor splashScreenExecutor,
StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
TransactionPool pool) {
- return new StartingWindowController(context, splashScreenExecutor,
- startingWindowTypeAlgorithm, iconProvider, pool);
+ return new StartingWindowController(context, shellInit, shellTaskOrganizer,
+ splashScreenExecutor, startingWindowTypeAlgorithm, iconProvider, pool);
}
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@@ -595,9 +627,11 @@
@WMSingleton
@Provides
- static Optional<ActivityEmbeddingController> provideActivityEmbeddingController(
- Context context, Transitions transitions) {
- return Optional.of(new ActivityEmbeddingController(context, transitions));
+ static ActivityEmbeddingController provideActivityEmbeddingController(
+ Context context,
+ ShellInit shellInit,
+ Transitions transitions) {
+ return new ActivityEmbeddingController(context, shellInit, transitions);
}
//
@@ -606,24 +640,34 @@
@WMSingleton
@Provides
- static ShellInterface provideShellSysuiCallbacks(ShellController shellController) {
+ static ShellInterface provideShellSysuiCallbacks(
+ @ShellCreateTrigger Object createTrigger,
+ ShellController shellController) {
return shellController.asShell();
}
@WMSingleton
@Provides
- static ShellController provideShellController(@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(mainExecutor);
+ static ShellController provideShellController(ShellInit shellInit,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new ShellController(shellInit, mainExecutor);
}
//
// Misc
//
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @ShellCreateTriggerOverride
+ abstract Object provideIndependentShellComponentsToCreateOverride();
+
+ // TODO: Temporarily move dependencies to this instead of ShellInit since that is needed to add
+ // the callback. We will be moving to a different explicit startup mechanism in a follow- up CL.
@WMSingleton
+ @ShellCreateTrigger
@Provides
- static ShellInit provideShellInitImpl(
- ShellController shellController,
+ static Object provideIndependentShellComponentsToCreate(
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
@@ -636,31 +680,19 @@
FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
- Optional<FreeformTaskListener<?>> freeformTaskListener,
+ Optional<FreeformComponents> freeformComponents,
Optional<RecentTasksController> recentTasksOptional,
- Optional<ActivityEmbeddingController> activityEmbeddingOptional,
+ ActivityEmbeddingController activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new ShellInit(shellController,
- displayController,
- displayImeController,
- displayInsetsController,
- dragAndDropController,
- shellTaskOrganizer,
- kidsModeTaskOrganizer,
- bubblesOptional,
- splitScreenOptional,
- pipTouchHandlerOptional,
- fullscreenTaskListener,
- unfoldAnimationController,
- unfoldTransitionHandler,
- freeformTaskListener,
- recentTasksOptional,
- activityEmbeddingOptional,
- transitions,
- startingWindow,
- mainExecutor);
+ @ShellCreateTriggerOverride Optional<Object> overriddenCreateTrigger) {
+ return new Object();
+ }
+
+ @WMSingleton
+ @Provides
+ static ShellInit provideShellInit(@ShellMainThread ShellExecutor mainExecutor) {
+ return new ShellInit(mainExecutor);
}
@WMSingleton
@@ -679,18 +711,4 @@
kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
hideDisplayCutout, recentTasksOptional, mainExecutor);
}
-
- @WMSingleton
- @Provides
- static Optional<BackAnimationController> provideBackAnimationController(
- Context context,
- @ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler
- ) {
- if (BackAnimationController.IS_ENABLED) {
- return Optional.of(
- new BackAnimationController(shellExecutor, backgroundHandler, context));
- }
- return Optional.empty();
- }
}
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 0dc293b..e2bf767 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
@@ -49,7 +49,9 @@
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -73,6 +75,8 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.SplitscreenPipMixedHandler;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -136,6 +140,7 @@
@WMSingleton
@Provides
static BubbleController provideBubbleController(Context context,
+ ShellInit shellInit,
ShellController shellController,
BubbleData data,
FloatingContentCoordinator floatingContentCoordinator,
@@ -156,8 +161,8 @@
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
- return new BubbleController(context, shellController, data, null /* synchronizer */,
- floatingContentCoordinator,
+ return new BubbleController(context, shellInit, shellController, data,
+ null /* synchronizer */, floatingContentCoordinator,
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
@@ -194,10 +199,43 @@
@WMSingleton
@Provides
@DynamicOverride
+ static FreeformComponents provideFreeformComponents(
+ FreeformTaskListener<?> taskListener,
+ FreeformTaskTransitionHandler transitionHandler) {
+ return new FreeformComponents(taskListener, Optional.of(transitionHandler));
+ }
+
+ @WMSingleton
+ @Provides
static FreeformTaskListener<?> provideFreeformTaskListener(
+ Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ WindowDecorViewModel<?> windowDecorViewModel) {
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ ShellInit init = FreeformComponents.isFreeformEnabled(context)
+ ? shellInit
+ : null;
+ return new FreeformTaskListener<>(init, shellTaskOrganizer,
+ windowDecorViewModel);
+ }
+
+ @WMSingleton
+ @Provides
+ static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
+ Context context,
+ ShellInit shellInit,
+ Transitions transitions,
WindowDecorViewModel<?> windowDecorViewModel,
- SyncTransactionQueue syncQueue) {
- return new FreeformTaskListener<>(windowDecorViewModel, syncQueue);
+ FreeformTaskListener<?> freeformTaskListener) {
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ ShellInit init = FreeformComponents.isFreeformEnabled(context)
+ ? shellInit
+ : null;
+ return new FreeformTaskTransitionHandler(init, transitions,
+ windowDecorViewModel, freeformTaskListener);
}
//
@@ -228,20 +266,25 @@
@Provides
@DynamicOverride
static SplitScreenController provideSplitScreenController(
+ Context context,
+ ShellInit shellInit,
ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
+ SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- @ShellMainThread ShellExecutor mainExecutor,
DisplayController displayController,
DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController, Transitions transitions,
- TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks) {
- return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context,
- rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
- displayInsetsController, transitions, transactionPool, iconProvider,
- recentTasks);
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new SplitScreenController(context, shellInit, shellController, shellTaskOrganizer,
+ syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController,
+ displayInsetsController, dragAndDropController, transitions, transactionPool,
+ iconProvider, recentTasks, mainExecutor);
}
//
@@ -313,14 +356,16 @@
@WMSingleton
@Provides
static PipTouchHandler providePipTouchHandler(Context context,
- PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm,
+ ShellInit shellInit,
+ PhonePipMenuController menuPhoneController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
- return new PipTouchHandler(context, menuPhoneController, pipBoundsAlgorithm,
+ return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
pipBoundsState, pipTaskOrganizer, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor);
}
@@ -395,9 +440,31 @@
floatingContentCoordinator);
}
+ @WMSingleton
+ @Provides
+ static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+ return new PipParamsChangedForwarder();
+ }
+
+ //
+ // Transitions
+ //
+
+ @WMSingleton
+ @Provides
+ static SplitscreenPipMixedHandler provideSplitscreenPipMixedHandler(
+ ShellInit shellInit,
+ Optional<SplitScreenController> splitScreenOptional,
+ Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Transitions transitions) {
+ return new SplitscreenPipMixedHandler(shellInit, splitScreenOptional,
+ pipTouchHandlerOptional, transitions);
+ }
+
//
// Unfold transition
//
+
@WMSingleton
@Provides
@DynamicOverride
@@ -407,6 +474,7 @@
@UnfoldTransition SplitTaskUnfoldAnimator splitAnimator,
FullscreenUnfoldTaskAnimator fullscreenAnimator,
Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
+ ShellInit shellInit,
@ShellMainThread ShellExecutor mainExecutor
) {
final List<UnfoldTaskAnimator> animators = new ArrayList<>();
@@ -414,6 +482,7 @@
animators.add(fullscreenAnimator);
return new UnfoldAnimationController(
+ shellInit,
transactionPool,
progressProvider.get(),
animators,
@@ -422,7 +491,6 @@
);
}
-
@Provides
static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
Context context,
@@ -441,6 +509,10 @@
Lazy<Optional<SplitScreenController>> splitScreenOptional,
DisplayInsetsController displayInsetsController
) {
+ // TODO(b/238217847): The lazy reference here causes some dependency issues since it
+ // immediately registers a listener on that controller on init. We should reference the
+ // controller directly once we refactor ShellTaskOrganizer to not depend on the unfold
+ // animation controller directly.
return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional,
backgroundController, displayInsetsController);
}
@@ -466,8 +538,9 @@
@UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator,
TransactionPool transactionPool,
Transitions transitions,
- @ShellMainThread ShellExecutor executor) {
- return new UnfoldTransitionHandler(progressProvider.get(), animator,
+ @ShellMainThread ShellExecutor executor,
+ ShellInit shellInit) {
+ return new UnfoldTransitionHandler(shellInit, progressProvider.get(), animator,
unfoldAnimator, transactionPool, executor, transitions);
}
@@ -483,9 +556,17 @@
);
}
+ //
+ // Misc
+ //
+
+ // TODO: Temporarily move dependencies to this instead of ShellInit since that is needed to add
+ // the callback. We will be moving to a different explicit startup mechanism in a follow- up CL.
@WMSingleton
+ @ShellCreateTriggerOverride
@Provides
- static PipParamsChangedForwarder providePipParamsChangedForwarder() {
- return new PipParamsChangedForwarder();
+ static Object provideIndependentShellComponentsToCreate(
+ SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
+ return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index f4e2f20..2aa933d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -44,10 +44,24 @@
### Component initialization
To initialize the component:
-- On the Shell side, update `ShellInitImpl` to get a signal to initialize when the SysUI is started
+- On the Shell side, you potentially need to do two things to initialize the component:
+ - Inject `ShellInit` into your component and add an init callback
+ - Ensure that your component is a part of the dagger dependency graph, either by:
+ - Making this component a dependency of an existing component already exposed to SystemUI
+ - Explicitly add this component to the WMShellBaseModule @ShellCreateTrigger provider or
+ the @ShellCreateTriggerOverride provider for your product module to expose it explicitly
+ if it is a completely independent component
- On the SysUI side, update `WMShell` to setup any bindings for the component that depend on
SysUI code
+To verify that your component is being initialized at startup, you can enable the `WM_SHELL_INIT`
+protolog group and restart the SysUI process:
+```shell
+adb shell wm logging enable-text WM_SHELL_INIT
+adb shell kill `pid com.android.systemui`
+adb logcat *:S WindowManagerShell
+```
+
### General Do's & Dont's
Do:
- Do add unit tests for all new components
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index c5df53b..4697a01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -62,9 +62,9 @@
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
-import java.util.Optional;
/**
* Handles the global drag and drop handling for the Shell.
@@ -94,6 +94,7 @@
}
public DragAndDropController(Context context,
+ ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
UiEventLogger uiEventLogger,
@@ -105,14 +106,29 @@
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
- public void initialize(Optional<SplitScreenController> splitscreen) {
- mSplitScreen = splitscreen.orElse(null);
- mDisplayController.addDisplayWindowListener(this);
+ /**
+ * Called when the controller is initialized.
+ */
+ public void onInit() {
+ // TODO(b/238217847): The dependency from SplitscreenController on DragAndDropController is
+ // inverted, which leads to SplitscreenController not setting its instance until after
+ // onDisplayAdded. We can remove this post once we fix that dependency.
+ mMainExecutor.executeDelayed(() -> {
+ mDisplayController.addDisplayWindowListener(this);
+ }, 0);
mShellController.addConfigurationChangeListener(this);
}
+ /**
+ * Sets the splitscreen controller to use if the feature is available.
+ */
+ public void setSplitScreenController(SplitScreenController splitscreen) {
+ mSplitScreen = splitscreen;
+ }
+
/** Adds a listener to be notified of drag and drop events. */
public void addListener(DragAndDropListener listener) {
mListeners.add(listener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java
new file mode 100644
index 0000000..41e1b1d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformComponents.java
@@ -0,0 +1,56 @@
+/*
+ * 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.freeform;
+
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Optional;
+
+/**
+ * Class that holds freeform related classes. It serves as the single injection point of
+ * all freeform classes to avoid leaking implementation details to the base Dagger module.
+ */
+public class FreeformComponents {
+ public final ShellTaskOrganizer.TaskListener mTaskListener;
+ public final Optional<Transitions.TransitionHandler> mTaskTransitionHandler;
+
+ /**
+ * Creates an instance with the given components.
+ */
+ public FreeformComponents(
+ ShellTaskOrganizer.TaskListener taskListener,
+ Optional<Transitions.TransitionHandler> taskTransitionHandler) {
+ mTaskListener = taskListener;
+ mTaskTransitionHandler = taskTransitionHandler;
+ }
+
+ /**
+ * Returns if this device supports freeform.
+ */
+ public static boolean isFreeformEnabled(Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
+ || Settings.Global.getInt(context.getContentResolver(),
+ DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 692e6acb..ab66107 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -16,20 +16,21 @@
package com.android.wm.shell.freeform;
-import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
-import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM;
import android.app.ActivityManager.RunningTaskInfo;
-import android.content.Context;
-import android.provider.Settings;
-import android.util.Slog;
+import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.io.PrintWriter;
@@ -44,10 +45,11 @@
implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FreeformTaskListener";
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final WindowDecorViewModel<T> mWindowDecorationViewModel;
- private final SyncTransactionQueue mSyncQueue;
private final SparseArray<State<T>> mTasks = new SparseArray<>();
+ private final SparseArray<T> mWindowDecorOfVanishedTasks = new SparseArray<>();
private static class State<T extends AutoCloseable> {
RunningTaskInfo mTaskInfo;
@@ -56,56 +58,88 @@
}
public FreeformTaskListener(
- WindowDecorViewModel<T> windowDecorationViewModel,
- SyncTransactionQueue syncQueue) {
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ WindowDecorViewModel<T> windowDecorationViewModel) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mWindowDecorationViewModel = windowDecorationViewModel;
- mSyncQueue = syncQueue;
+ if (shellInit != null) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
}
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mTasks.get(taskInfo.taskId) != null) {
- throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
- }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
taskInfo.taskId);
- final State<T> state = new State<>();
+ final State<T> state = createOrUpdateTaskState(taskInfo, leash);
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ state.mWindowDecoration =
+ mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+ t.apply();
+ }
+ }
+
+ private State<T> createOrUpdateTaskState(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ State<T> state = mTasks.get(taskInfo.taskId);
+ if (state != null) {
+ updateTaskInfo(taskInfo);
+ return state;
+ }
+
+ state = new State<>();
state.mTaskInfo = taskInfo;
state.mLeash = leash;
- state.mWindowDecoration =
- mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash);
mTasks.put(taskInfo.taskId, state);
+
+ return state;
}
@Override
public void onTaskVanished(RunningTaskInfo taskInfo) {
- State<T> state = mTasks.get(taskInfo.taskId);
+ final State<T> state = mTasks.get(taskInfo.taskId);
if (state == null) {
- Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
+ // This is possible if the transition happens before this method.
return;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- try {
- state.mWindowDecoration.close();
- } catch (Exception e) {
- Slog.e(TAG, "Failed to release window decoration.", e);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // Save window decorations of closing tasks so that we can hand them over to the
+ // transition system if this method happens before the transition. In case where the
+ // transition didn't happen, it'd be cleared when the next transition finished.
+ if (state.mWindowDecoration != null) {
+ mWindowDecorOfVanishedTasks.put(taskInfo.taskId, state.mWindowDecoration);
+ }
+ return;
}
+ releaseWindowDecor(state.mWindowDecoration);
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- State<T> state = mTasks.get(taskInfo.taskId);
- if (state == null) {
- throw new RuntimeException(
- "Task info changed before appearing: #" + taskInfo.taskId);
- }
+ final State<T> state = updateTaskInfo(taskInfo);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
taskInfo.taskId);
+ if (state.mWindowDecoration != null) {
+ mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
+ }
+ }
+
+ private State<T> updateTaskInfo(RunningTaskInfo taskInfo) {
+ final State<T> state = mTasks.get(taskInfo.taskId);
+ if (state == null) {
+ throw new RuntimeException("Task info changed before appearing: #" + taskInfo.taskId);
+ }
state.mTaskInfo = taskInfo;
- mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
+ return state;
}
@Override
@@ -126,6 +160,93 @@
return mTasks.get(taskId).mLeash;
}
+ /**
+ * Creates a window decoration for a transition.
+ *
+ * @param change the change of this task transition that needs to have the task layer as the
+ * leash
+ * @return {@code true} if it adopts the window decoration; {@code false} otherwise
+ */
+ void createWindowDecoration(
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
+ state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
+ state.mTaskInfo, state.mLeash, startT, finishT);
+ }
+
+ /**
+ * Gives out the ownership of the task's window decoration. The given task is leaving (of has
+ * left) this task listener. This is the transition system asking for the ownership.
+ *
+ * @param taskInfo the maximizing task
+ * @return the window decor of the maximizing task if any
+ */
+ T giveWindowDecoration(
+ RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ T windowDecor = mWindowDecorOfVanishedTasks.get(taskInfo.taskId);
+ mWindowDecorOfVanishedTasks.remove(taskInfo.taskId);
+ final State<T> state = mTasks.get(taskInfo.taskId);
+ if (state != null) {
+ windowDecor = windowDecor == null ? state.mWindowDecoration : windowDecor;
+ state.mWindowDecoration = null;
+ }
+ mWindowDecorationViewModel.setupWindowDecorationForTransition(
+ taskInfo, startT, finishT, windowDecor);
+ return windowDecor;
+ }
+
+ /**
+ * Adopt the incoming window decoration and lets the window decoration prepare for a transition.
+ *
+ * @param change the change of this task transition that needs to have the task layer as the
+ * leash
+ * @param startT the start transaction of this transition
+ * @param finishT the finish transaction of this transition
+ * @param windowDecor the window decoration to adopt
+ * @return {@code true} if it adopts the window decoration; {@code false} otherwise
+ */
+ boolean adoptWindowDecoration(
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT,
+ @Nullable AutoCloseable windowDecor) {
+ final State<T> state = createOrUpdateTaskState(change.getTaskInfo(), change.getLeash());
+ state.mWindowDecoration = mWindowDecorationViewModel.adoptWindowDecoration(windowDecor);
+ if (state.mWindowDecoration != null) {
+ mWindowDecorationViewModel.setupWindowDecorationForTransition(
+ state.mTaskInfo, startT, finishT, state.mWindowDecoration);
+ return true;
+ } else {
+ state.mWindowDecoration = mWindowDecorationViewModel.createWindowDecoration(
+ state.mTaskInfo, state.mLeash, startT, finishT);
+ return false;
+ }
+ }
+
+ void onTaskTransitionFinished() {
+ if (mWindowDecorOfVanishedTasks.size() == 0) {
+ return;
+ }
+ Log.w(TAG, "Clearing window decors of vanished tasks. There could be visual defects "
+ + "if any of them is used later in transitions.");
+ for (int i = 0; i < mWindowDecorOfVanishedTasks.size(); ++i) {
+ releaseWindowDecor(mWindowDecorOfVanishedTasks.valueAt(i));
+ }
+ mWindowDecorOfVanishedTasks.clear();
+ }
+
+ private void releaseWindowDecor(T windowDecor) {
+ try {
+ windowDecor.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to release window decoration.", e);
+ }
+ }
+
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
@@ -137,16 +258,4 @@
public String toString() {
return TAG;
}
-
- /**
- * Checks if freeform support is enabled in system.
- *
- * @param context context used to check settings and package manager.
- * @return {@code true} if freeform is enabled, {@code false} if not.
- */
- public static boolean isFreeformEnabled(Context context) {
- return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
- || Settings.Global.getInt(context.getContentResolver(),
- DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
new file mode 100644
index 0000000..20d7725
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -0,0 +1,244 @@
+/*
+ * 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.freeform;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link Transitions.TransitionHandler} that handles freeform task launches, closes,
+ * maximizing and restoring transitions. It also reports transitions so that window decorations can
+ * be a part of transitions.
+ */
+public class FreeformTaskTransitionHandler
+ implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
+ private static final String TAG = "FreeformTH";
+
+ private final Transitions mTransitions;
+ private final FreeformTaskListener<?> mFreeformTaskListener;
+ private final WindowDecorViewModel<?> mWindowDecorViewModel;
+
+ private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+
+ public FreeformTaskTransitionHandler(
+ ShellInit shellInit,
+ Transitions transitions,
+ WindowDecorViewModel<?> windowDecorViewModel,
+ FreeformTaskListener<?> freeformTaskListener) {
+ mTransitions = transitions;
+ mFreeformTaskListener = freeformTaskListener;
+ mWindowDecorViewModel = windowDecorViewModel;
+ if (shellInit != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ mWindowDecorViewModel.setFreeformTaskTransitionStarter(this);
+ mTransitions.addHandler(this);
+ }
+
+ @Override
+ public void startWindowingModeTransition(
+ int targetWindowingMode, WindowContainerTransaction wct) {
+ final int type;
+ switch (targetWindowingMode) {
+ case WINDOWING_MODE_FULLSCREEN:
+ type = Transitions.TRANSIT_MAXIMIZE;
+ break;
+ case WINDOWING_MODE_FREEFORM:
+ type = Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE;
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected target windowing mode "
+ + WindowConfiguration.windowingModeToString(targetWindowingMode));
+ }
+ final IBinder token = mTransitions.startTransition(type, wct, this);
+ mPendingTransitionTokens.add(token);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ boolean transitionHandled = false;
+ final ArrayList<AutoCloseable> windowDecorsInCloseTransitions = new ArrayList<>();
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
+ continue;
+ }
+
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue;
+ }
+
+ switch (change.getMode()) {
+ case WindowManager.TRANSIT_OPEN:
+ transitionHandled |= startOpenTransition(change, startT, finishT);
+ break;
+ case WindowManager.TRANSIT_CLOSE:
+ transitionHandled |= startCloseTransition(
+ change, windowDecorsInCloseTransitions, startT, finishT);
+ break;
+ case WindowManager.TRANSIT_CHANGE:
+ transitionHandled |= startChangeTransition(
+ transition, info.getType(), change, startT, finishT);
+ break;
+ case WindowManager.TRANSIT_TO_BACK:
+ case WindowManager.TRANSIT_TO_FRONT:
+ transitionHandled = true;
+ break;
+ }
+ }
+
+ mPendingTransitionTokens.remove(transition);
+
+ if (!transitionHandled) {
+ return false;
+ }
+
+ startT.apply();
+ mTransitions.getMainExecutor().execute(
+ () -> finishTransition(windowDecorsInCloseTransitions, finishCallback));
+ return true;
+ }
+
+ private boolean startOpenTransition(
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ if (change.getTaskInfo().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+ mFreeformTaskListener.createWindowDecoration(change, startT, finishT);
+ return true;
+ }
+
+ private boolean startCloseTransition(
+ TransitionInfo.Change change,
+ ArrayList<AutoCloseable> windowDecors,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ if (change.getTaskInfo().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+ final AutoCloseable windowDecor =
+ mFreeformTaskListener.giveWindowDecoration(change.getTaskInfo(), startT, finishT);
+ if (windowDecor != null) {
+ windowDecors.add(windowDecor);
+ }
+
+ return true;
+ }
+
+ private boolean startChangeTransition(
+ IBinder transition,
+ int type,
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ AutoCloseable windowDecor = null;
+
+ if (!mPendingTransitionTokens.contains(transition)) {
+ return false;
+ }
+
+ boolean handled = false;
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (type == Transitions.TRANSIT_MAXIMIZE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ handled = true;
+ windowDecor = mFreeformTaskListener.giveWindowDecoration(
+ change.getTaskInfo(), startT, finishT);
+ // TODO(b/235638450): Let fullscreen task listener adopt the window decor.
+ }
+
+ if (type == Transitions.TRANSIT_RESTORE_FROM_MAXIMIZE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ handled = true;
+ // TODO(b/235638450): Let fullscreen task listener transfer the window decor.
+ mFreeformTaskListener.adoptWindowDecoration(change, startT, finishT, windowDecor);
+ }
+
+ releaseWindowDecor(windowDecor);
+
+ return handled;
+ }
+
+ private void finishTransition(
+ ArrayList<AutoCloseable> windowDecorsInCloseTransitions,
+ Transitions.TransitionFinishCallback finishCallback) {
+ for (AutoCloseable windowDecor : windowDecorsInCloseTransitions) {
+ releaseWindowDecor(windowDecor);
+ }
+ mFreeformTaskListener.onTaskTransitionFinished();
+ // TODO(b/235638450): Dispatch it to fullscreen task listener.
+ finishCallback.onTransitionFinished(null, null);
+ }
+
+ private void releaseWindowDecor(AutoCloseable windowDecor) {
+ if (windowDecor == null) {
+ return;
+ }
+ try {
+ windowDecor.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to release window decoration.", e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo taskInfo = request.getTriggerTask();
+ if (taskInfo == null || taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ return null;
+ }
+ switch (request.getType()) {
+ case WindowManager.TRANSIT_OPEN:
+ case WindowManager.TRANSIT_CLOSE:
+ case WindowManager.TRANSIT_TO_FRONT:
+ case WindowManager.TRANSIT_TO_BACK:
+ return new WindowContainerTransaction();
+ default:
+ return null;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
new file mode 100644
index 0000000..25eaa0e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.freeform;
+
+import android.window.WindowContainerTransaction;
+
+/**
+ * The interface around {@link FreeformTaskTransitionHandler} for task listeners to start freeform
+ * task transitions.
+ */
+public interface FreeformTaskTransitionStarter {
+
+ /**
+ * Starts a windowing mode transition.
+ *
+ * @param targetWindowingMode the target windowing mode
+ * @param wct the {@link WindowContainerTransaction} that changes the windowing mode
+ */
+ void startWindowingModeTransition(int targetWindowingMode, WindowContainerTransaction wct);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 79e363b..0ba4afc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -32,6 +32,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -43,19 +44,34 @@
public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskListener";
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
+ /**
+ * This constructor is used by downstream products.
+ */
public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
- this(syncQueue, Optional.empty());
+ this(null /* shellInit */, null /* shellTaskOrganizer */, syncQueue, Optional.empty());
}
- public FullscreenTaskListener(SyncTransactionQueue syncQueue,
- Optional<RecentTasksController> recentTasks) {
+ public FullscreenTaskListener(ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ Optional<RecentTasksController> recentTasksOptional) {
+ mShellTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
- mRecentTasksOptional = recentTasks;
+ mRecentTasksOptional = recentTasksOptional;
+ // Note: Some derivative FullscreenTaskListener implementations do not use ShellInit
+ if (shellInit != null) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FULLSCREEN);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index 9478b34..f376e1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -64,8 +64,8 @@
@VisibleForTesting
final Rect mCurrentDisplayBounds = new Rect();
// The default display cutout in natural orientation.
- private Insets mDefaultCutoutInsets;
- private Insets mCurrentCutoutInsets;
+ private Insets mDefaultCutoutInsets = Insets.NONE;
+ private Insets mCurrentCutoutInsets = Insets.NONE;
private boolean mIsDefaultPortrait;
private int mStatusBarHeight;
@VisibleForTesting
@@ -78,27 +78,35 @@
private final DisplayController.OnDisplaysChangedListener mListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
+ public void onDisplayAdded(int displayId) {
+ onDisplayChanged(displayId);
+ }
+
+ @Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
- DisplayLayout displayLayout =
- mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
- if (displayLayout == null) {
- return;
- }
- final boolean rotationChanged = mRotation != displayLayout.rotation();
- mRotation = displayLayout.rotation();
- if (rotationChanged || isDisplayBoundsChanged()) {
- updateBoundsAndOffsets(true /* enabled */);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- applyAllBoundsAndOffsets(wct, t);
- applyTransaction(wct, t);
- }
+ onDisplayChanged(displayId);
}
};
+ private void onDisplayChanged(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
+ if (displayLayout == null) {
+ return;
+ }
+ final boolean rotationChanged = mRotation != displayLayout.rotation();
+ mRotation = displayLayout.rotation();
+ if (rotationChanged || isDisplayBoundsChanged()) {
+ updateBoundsAndOffsets(true /* enabled */);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ applyAllBoundsAndOffsets(wct, t);
+ applyTransaction(wct, t);
+ }
+ }
+
HideDisplayCutoutOrganizer(Context context, DisplayController displayController,
ShellExecutor mainExecutor) {
super(mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 2c8ba09..73b9b89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -50,7 +50,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
@@ -140,18 +140,18 @@
@VisibleForTesting
KidsModeTaskOrganizer(
- ITaskOrganizerController taskOrganizerController,
- ShellExecutor mainExecutor,
- Handler mainHandler,
Context context,
+ ITaskOrganizerController taskOrganizerController,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
- KidsModeSettingsObserver kidsModeSettingsObserver) {
- super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null,
- unfoldAnimationController, recentTasks);
+ KidsModeSettingsObserver kidsModeSettingsObserver,
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
+ super(/* shellInit= */ null, taskOrganizerController, /* compatUI= */ null,
+ unfoldAnimationController, recentTasks, mainExecutor);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
@@ -161,27 +161,30 @@
}
public KidsModeTaskOrganizer(
- ShellExecutor mainExecutor,
- Handler mainHandler,
Context context,
+ ShellInit shellInit,
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<RecentTasksController> recentTasks) {
- super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks);
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor,
+ Handler mainHandler) {
+ // Note: we don't call super with the shell init because we will be initializing manually
+ super(/* shellInit= */ null, /* compatUI= */ null, unfoldAnimationController, recentTasks,
+ mainExecutor);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
+ shellInit.addInitCallback(this::onInit, this);
}
/**
* Initializes kids mode status.
*/
- public void initialize(StartingWindowController startingWindowController) {
- initStartingWindow(startingWindowController);
+ public void onInit() {
if (mKidsModeSettingsObserver == null) {
mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 420d606..586e3a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -495,14 +495,15 @@
mPipBoundsState.getBounds(),
mPipBoundsState.getAspectRatio());
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
- mPipTaskOrganizer.scheduleAnimateResizePip(destinationBounds,
- mEnterAnimationDuration,
- null /* updateBoundsCallback */);
-
- mTouchHandler.onAspectRatioChanged();
- updateMovementBounds(null /* toBounds */, false /* fromRotation */,
- false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */);
+ if (!destinationBounds.equals(mPipBoundsState.getBounds())) {
+ mPipTaskOrganizer.scheduleAnimateResizePip(destinationBounds,
+ mEnterAnimationDuration,
+ null /* updateBoundsCallback */);
+ mTouchHandler.onAspectRatioChanged();
+ updateMovementBounds(null /* toBounds */, false /* fromRotation */,
+ false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
+ null /* windowContainerTransaction */);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index a2ff972..c86c136 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -57,6 +57,7 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
@@ -165,6 +166,7 @@
@SuppressLint("InflateParams")
public PipTouchHandler(Context context,
+ ShellInit shellInit,
PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
@@ -173,7 +175,6 @@
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor) {
- // Initialize the Pip input consumer
mContext = context;
mMainExecutor = mainExecutor;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -213,9 +214,17 @@
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
this::onAccessibilityShowMenu, this::updateMovementBounds,
this::animateToUnStashedState, mainExecutor);
+
+ // TODO(b/181599115): This should really be initializes as part of the pip controller, but
+ // until all PIP implementations derive from the controller, just initialize the touch handler
+ // if it is needed
+ shellInit.addInitCallback(this::onInit, this);
}
- public void init() {
+ /**
+ * Called when the touch handler is initialized.
+ */
+ public void onInit() {
Resources res = mContext.getResources();
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
index 3a6ce81..b212ea9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java
@@ -122,9 +122,9 @@
cancelScheduledPlacement();
applyPlacementBounds(placement.getUnstashedBounds(), animationDuration);
} else if (immediate) {
+ boolean shouldStash = mUnstashRunnable != null || placement.getTriggerStash();
cancelScheduledPlacement();
- applyPlacementBounds(placement.getBounds(), animationDuration);
- scheduleUnstashIfNeeded(placement);
+ applyPlacement(placement, shouldStash, animationDuration);
} else {
applyPlacementBounds(mCurrentPlacementBounds, animationDuration);
schedulePinnedStackPlacement(placement, animationDuration);
@@ -176,22 +176,21 @@
"%s: applyPendingPlacement()", TAG);
}
if (mPendingPlacement != null) {
- if (mPendingStash) {
- mPendingStash = false;
- scheduleUnstashIfNeeded(mPendingPlacement);
- }
+ applyPlacement(mPendingPlacement, mPendingStash, mPendingPlacementAnimationDuration);
+ mPendingStash = false;
+ mPendingPlacement = null;
+ }
+ }
- if (mUnstashRunnable != null) {
- // currently stashed, use stashed pos
- applyPlacementBounds(mPendingPlacement.getBounds(),
- mPendingPlacementAnimationDuration);
- } else {
- applyPlacementBounds(mPendingPlacement.getUnstashedBounds(),
- mPendingPlacementAnimationDuration);
- }
+ private void applyPlacement(@NonNull final Placement placement, boolean shouldStash,
+ int animationDuration) {
+ if (placement.getStashType() != STASH_TYPE_NONE && shouldStash) {
+ scheduleUnstashIfNeeded(placement);
}
- mPendingPlacement = null;
+ Rect bounds =
+ mUnstashRunnable != null ? placement.getBounds() : placement.getUnstashedBounds();
+ applyPlacementBounds(bounds, animationDuration);
}
void onPipDismissed() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 49042e6..3d1a7e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -85,28 +86,32 @@
@Nullable
public static RecentTasksController create(
Context context,
+ ShellInit shellInit,
TaskStackListenerImpl taskStackListener,
@ShellMainThread ShellExecutor mainExecutor
) {
if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) {
return null;
}
- return new RecentTasksController(context, taskStackListener, mainExecutor);
+ return new RecentTasksController(context, shellInit, taskStackListener, mainExecutor);
}
- RecentTasksController(Context context, TaskStackListenerImpl taskStackListener,
+ RecentTasksController(Context context,
+ ShellInit shellInit,
+ TaskStackListenerImpl taskStackListener,
ShellExecutor mainExecutor) {
mContext = context;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
public RecentTasks asRecentTasks() {
return mImpl;
}
- public void init() {
+ private void onInit() {
mTaskStackListener.addListener(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7fb961f..de7e7bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -21,7 +21,6 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -32,6 +31,7 @@
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
@@ -46,6 +46,7 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -76,13 +77,14 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.transition.LegacyTransitions;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -139,6 +141,7 @@
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final DisplayInsetsController mDisplayInsetsController;
+ private final DragAndDropController mDragAndDropController;
private final Transitions mTransitions;
private final TransactionPool mTransactionPool;
private final SplitscreenEventLogger mLogger;
@@ -150,15 +153,21 @@
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mSplitTasksContainerLayer;
- public SplitScreenController(ShellController shellController,
+ public SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
+ SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- ShellExecutor mainExecutor, DisplayController displayController,
+ DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
- Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks) {
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
+ ShellExecutor mainExecutor) {
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -168,17 +177,40 @@
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
mTransitions = transitions;
mTransactionPool = transactionPool;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
public SplitScreen asSplitScreen() {
return mImpl;
}
+ /**
+ * This will be called after ShellTaskOrganizer has initialized/registered because of the
+ * dependency order.
+ */
+ @VisibleForTesting
+ void onInit() {
+ mShellController.addKeyguardChangeListener(this);
+ if (mStageCoordinator == null) {
+ // TODO: Multi-display
+ mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mTaskOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mIconProvider, mMainExecutor, mRecentTasksOptional);
+ }
+ mDragAndDropController.setSplitScreenController(this);
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -189,17 +221,6 @@
return mMainExecutor;
}
- public void onOrganizerRegistered() {
- mShellController.addKeyguardChangeListener(this);
- if (mStageCoordinator == null) {
- // TODO: Multi-display
- mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
- mTaskOrganizer, mDisplayController, mDisplayImeController,
- mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
- mIconProvider, mMainExecutor, mRecentTasksOptional);
- }
- }
-
public boolean isSplitScreenVisible() {
return mStageCoordinator.isSplitScreenVisible();
}
@@ -334,17 +355,37 @@
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
+ }
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ }
+ };
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
null /* wct */);
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictChildTasks(position, evictWct);
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+ 0 /* duration */, 0 /* statusBarTransitionDelay */);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
try {
- LauncherApps launcherApps =
- mContext.getSystemService(LauncherApps.class);
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- options, user);
- mSyncQueue.queue(evictWct);
+ activityOptions.toBundle(), user);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to launch shortcut", e);
}
@@ -368,62 +409,13 @@
}
if (!ENABLE_SHELL_TRANSITIONS) {
- startIntentLegacy(intent, fillInIntent, position, options);
+ mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
return;
}
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
- private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictChildTasks(position, evictWct);
-
- LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback,
- SurfaceControl.Transaction t) {
- if (apps == null || apps.length == 0) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- mStageCoordinator.getSideStagePosition()));
- }
-
- // Do nothing when the animation was cancelled.
- t.apply();
- return;
- }
-
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
- }
- t.apply();
-
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error finishing legacy transition: ", e);
- }
- }
-
- mSyncQueue.queue(evictWct);
- }
- };
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
-
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
- }
-
/** Returns {@code true} if it's launching the same component on both sides of the split. */
@VisibleForTesting
boolean isLaunchingAdjacently(@Nullable Intent startIntent,
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 3c7db33..f2340d5 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
@@ -26,6 +26,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -68,7 +69,6 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -119,6 +119,7 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
+import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
@@ -196,7 +197,6 @@
private boolean mExitSplitScreenOnHide;
private boolean mIsDividerRemoteAnimating;
private boolean mIsExiting;
- private boolean mResizingSplits;
/** The target stage to dismiss to when unlock after folded. */
@StageType
@@ -211,7 +211,11 @@
@Override
public void onLeashReady(SurfaceControl leash) {
- mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ // This is for avoiding divider invisible due to delay of creating so only need
+ // to do when divider should visible case.
+ if (mDividerVisible) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
}
};
@@ -434,6 +438,63 @@
});
}
+ /** Launches an activity into split by legacy transition. */
+ void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictChildTasks(position, evictWct);
+
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ if (apps == null || apps.length == 0) {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePosition(SplitLayout.reversePosition(
+ getSideStagePosition()), null);
+ }
+
+ // Do nothing when the animation was cancelled.
+ t.apply();
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+
+ mSyncQueue.queue(evictWct);
+ }
+ };
+
+ 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);
+ }
+
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
@@ -522,14 +583,8 @@
finishedCallback.onAnimationFinished();
}
};
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
try {
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- adapter.getCallingApplication());
- } catch (SecurityException e) {
- Slog.e(TAG, "Unable to boost animation thread. This should only happen"
- + " during unit tests");
- }
adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
augmentedNonApps, wrapCallback);
} catch (RemoteException e) {
@@ -617,6 +672,15 @@
}
}
+ void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps,
+ WindowContainerTransaction wct) {
+ if (position == mSideStagePosition) {
+ mSideStage.evictNonOpeningChildren(apps, wct);
+ } else {
+ mMainStage.evictNonOpeningChildren(apps, wct);
+ }
+ }
+
void prepareEvictInvisibleChildTasks(WindowContainerTransaction wct) {
mMainStage.evictInvisibleChildren(wct);
mSideStage.evictInvisibleChildren(wct);
@@ -847,6 +911,7 @@
.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
@@ -856,7 +921,7 @@
WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
mIsExiting = false;
childrenToTop.dismiss(finishedWCT, true /* toTop */);
- wct.reorder(mRootTaskInfo.token, false /* toTop */);
+ finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
mTaskOrganizer.applyTransaction(finishedWCT);
onTransitionAnimationComplete();
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index f6dc68b..f414d69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -20,6 +20,7 @@
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.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
@@ -33,6 +34,7 @@
import android.graphics.Rect;
import android.os.IBinder;
import android.util.SparseArray;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -334,6 +336,19 @@
}
}
+ void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) {
+ final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone();
+ for (int i = 0; i < apps.length; i++) {
+ if (apps[i].mode == MODE_OPENING) {
+ toBeEvict.remove(apps[i].taskId);
+ }
+ }
+ for (int i = toBeEvict.size() - 1; i >= 0; i--) {
+ final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i);
+ wct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
+ }
+ }
+
void evictInvisibleChildren(WindowContainerTransaction wct) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index fbc9923..379af21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -42,10 +42,12 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.TriConsumer;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
/**
* Implementation to draw the starting window to an application, and remove the starting window
@@ -74,6 +76,7 @@
private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback;
private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl();
private final Context mContext;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final ShellExecutor mSplashScreenExecutor;
/**
* Need guarded because it has exposed to StartingSurface
@@ -81,14 +84,20 @@
@GuardedBy("mTaskBackgroundColors")
private final SparseIntArray mTaskBackgroundColors = new SparseIntArray();
- public StartingWindowController(Context context, ShellExecutor splashScreenExecutor,
- StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, IconProvider iconProvider,
+ public StartingWindowController(Context context,
+ ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ ShellExecutor splashScreenExecutor,
+ StartingWindowTypeAlgorithm startingWindowTypeAlgorithm,
+ IconProvider iconProvider,
TransactionPool pool) {
mContext = context;
+ mShellTaskOrganizer = shellTaskOrganizer;
mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor,
iconProvider, pool);
mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm;
mSplashScreenExecutor = splashScreenExecutor;
+ shellInit.addInitCallback(this::onInit, this);
}
/**
@@ -98,6 +107,10 @@
return mImpl;
}
+ private void onInit() {
+ mShellTaskOrganizer.initStartingWindow(this);
+ }
+
@Override
public Context getContext() {
return mContext;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 19d3acb..b70bde3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -228,12 +228,13 @@
final InsetsState tmpInsetsState = new InsetsState();
final InputChannel tmpInputChannel = new InputChannel();
+ final float[] sizeCompatScale = { 1f };
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls,
- new Rect());
+ new Rect(), sizeCompatScale);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index 0427efb..f4fc0c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -45,7 +45,6 @@
private final Optional<RecentTasksController> mRecentTasks;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
- private final ShellExecutor mMainExecutor;
public ShellCommandHandler(
ShellController shellController,
@@ -64,7 +63,6 @@
mPipOptional = pipOptional;
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutout = hideDisplayCutout;
- mMainExecutor = mainExecutor;
// TODO(238217847): To be removed once the command handler dependencies are inverted
shellController.setShellCommandHandler(this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 618028c..f1f317f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -44,10 +44,10 @@
public class ShellController {
private static final String TAG = ShellController.class.getSimpleName();
+ private final ShellInit mShellInit;
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
- private ShellInit mShellInit;
private ShellCommandHandler mShellCommandHandler;
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -57,7 +57,8 @@
private Configuration mLastConfiguration;
- public ShellController(ShellExecutor mainExecutor) {
+ public ShellController(ShellInit shellInit, ShellExecutor mainExecutor) {
+ mShellInit = shellInit;
mMainExecutor = mainExecutor;
}
@@ -69,15 +70,6 @@
}
/**
- * Sets the init handler to call back to.
- * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
- * init handler to other classes.
- */
- public void setShellInit(ShellInit shellInit) {
- mShellInit = shellInit;
- }
-
- /**
* Sets the command handler to call back to.
* TODO(238217847): This is only exposed this way until we can remove the dependencies from the
* command handler to other classes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 2619b37..c250e03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.sysui;
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
import android.os.Build;
@@ -26,147 +25,27 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.freeform.FreeformTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.transition.DefaultMixedHandler;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.unfold.UnfoldAnimationController;
-import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import java.util.ArrayList;
-import java.util.Optional;
/**
* The entry point implementation into the shell for initializing shell internal state. Classes
- * which need to setup on start should inject an instance of this class and add an init callback.
+ * which need to initialize on start of the host SysUI should inject an instance of this class and
+ * add an init callback.
*/
public class ShellInit {
private static final String TAG = ShellInit.class.getSimpleName();
- private final DisplayController mDisplayController;
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private final DragAndDropController mDragAndDropController;
- private final ShellTaskOrganizer mShellTaskOrganizer;
- private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
- private final Optional<BubbleController> mBubblesOptional;
- private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
- private final FullscreenTaskListener mFullscreenTaskListener;
- private final Optional<UnfoldAnimationController> mUnfoldController;
- private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
- private final Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor;
- private final Transitions mTransitions;
- private final StartingWindowController mStartingWindow;
- private final Optional<RecentTasksController> mRecentTasks;
- private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional;
// An ordered list of init callbacks to be made once shell is first started
private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>();
private boolean mHasInitialized;
- public ShellInit(
- ShellController shellController,
- DisplayController displayController,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController,
- DragAndDropController dragAndDropController,
- ShellTaskOrganizer shellTaskOrganizer,
- KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<BubbleController> bubblesOptional,
- Optional<SplitScreenController> splitScreenOptional,
- Optional<PipTouchHandler> pipTouchHandlerOptional,
- FullscreenTaskListener fullscreenTaskListener,
- Optional<UnfoldAnimationController> unfoldAnimationController,
- Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
- Optional<FreeformTaskListener<?>> freeformTaskListenerOptional,
- Optional<RecentTasksController> recentTasks,
- Optional<ActivityEmbeddingController> activityEmbeddingOptional,
- Transitions transitions,
- StartingWindowController startingWindow,
- ShellExecutor mainExecutor) {
- mDisplayController = displayController;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mDragAndDropController = dragAndDropController;
- mShellTaskOrganizer = shellTaskOrganizer;
- mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
- mBubblesOptional = bubblesOptional;
- mSplitScreenOptional = splitScreenOptional;
- mFullscreenTaskListener = fullscreenTaskListener;
- mPipTouchHandlerOptional = pipTouchHandlerOptional;
- mUnfoldController = unfoldAnimationController;
- mUnfoldTransitionHandler = unfoldTransitionHandler;
- mFreeformTaskListenerOptional = freeformTaskListenerOptional;
- mRecentTasks = recentTasks;
- mActivityEmbeddingOptional = activityEmbeddingOptional;
- mTransitions = transitions;
+
+ public ShellInit(ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
- mStartingWindow = startingWindow;
- // TODO(238217847): To be removed once the init dependencies are inverted
- shellController.setShellInit(this);
- }
-
- private void legacyInit() {
- // Start listening for display and insets changes
- mDisplayController.initialize();
- mDisplayInsetsController.initialize();
- mDisplayImeController.startMonitorDisplays();
-
- // Setup the shell organizer
- mShellTaskOrganizer.addListenerForType(
- mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mShellTaskOrganizer.initStartingWindow(mStartingWindow);
- mShellTaskOrganizer.registerOrganizer();
-
- mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
- mBubblesOptional.ifPresent(BubbleController::initialize);
-
- // Bind the splitscreen impl to the drag drop controller
- mDragAndDropController.initialize(mSplitScreenOptional);
-
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.register(mShellTaskOrganizer);
- mActivityEmbeddingOptional.ifPresent(ActivityEmbeddingController::init);
- mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init);
- if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
- final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
- mPipTouchHandlerOptional.get().getTransitionHandler(),
- mSplitScreenOptional.get().getTransitionHandler());
- // Added at end so that it has highest priority.
- mTransitions.addHandler(mixedHandler);
- }
- }
-
- // TODO(b/181599115): This should really be the pip controller, but until we can provide the
- // controller instead of the feature interface, can just initialize the touch handler if
- // needed
- mPipTouchHandlerOptional.ifPresent((handler) -> handler.init());
-
- // Initialize optional freeform
- mFreeformTaskListenerOptional.ifPresent(f ->
- mShellTaskOrganizer.addListenerForType(
- f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
-
- mUnfoldController.ifPresent(UnfoldAnimationController::init);
- mRecentTasks.ifPresent(RecentTasksController::init);
-
- // Initialize kids mode task organizer
- mKidsModeTaskOrganizer.initialize(mStartingWindow);
}
/**
@@ -199,13 +78,9 @@
final long t1 = SystemClock.uptimeMillis();
info.second.run();
final long t2 = SystemClock.uptimeMillis();
- ProtoLog.v(WM_SHELL_INIT, "\t%s took %dms", info.first, (t2 - t1));
+ ProtoLog.v(WM_SHELL_INIT, "\t%s init took %dms", info.first, (t2 - t1));
}
mInitCallbacks.clear();
-
- // TODO: To be removed
- legacyInit();
-
mHasInitialized = true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 0bec543..08eb2c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -109,6 +109,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.List;
@@ -136,6 +137,7 @@
private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
private final Context mContext;
+ private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
@@ -152,8 +154,6 @@
private final int mCurrentUserId;
- private ScreenRotationAnimation mRotationAnimation;
-
private Drawable mEnterpriseThumbnailDrawable;
private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() {
@@ -167,27 +167,33 @@
}
};
- DefaultTransitionHandler(@NonNull DisplayController displayController,
- @NonNull TransactionPool transactionPool, Context context,
+ DefaultTransitionHandler(@NonNull Context context,
+ @NonNull ShellInit shellInit,
+ @NonNull DisplayController displayController,
+ @NonNull TransactionPool transactionPool,
@NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor) {
mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
+ mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
mCurrentUserId = UserHandle.myUserId();
+ mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+ shellInit.addInitCallback(this::onInit, this);
+ }
- mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ private void onInit() {
updateEnterpriseThumbnailDrawable();
mContext.registerReceiver(
mEnterpriseResourceUpdatedReceiver,
new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED),
/* broadcastPermission = */ null,
- mainHandler);
+ mMainHandler);
- AttributeCache.init(context);
+ AttributeCache.init(mContext);
}
private void updateEnterpriseThumbnailDrawable() {
@@ -332,12 +338,6 @@
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
-
- if (mRotationAnimation != null) {
- mRotationAnimation.kill();
- mRotationAnimation = null;
- }
-
mAnimations.remove(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
};
@@ -357,11 +357,8 @@
isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController);
final int anim = getRotationAnimation(info);
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
- mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
- mTransactionPool, startTransaction, change, info.getRootLeash(),
- anim);
- mRotationAnimation.startAnimation(animations, onAnimFinish,
- mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
+ startRotationAnimation(startTransaction, change, info, anim, animations,
+ onAnimFinish);
continue;
}
} else {
@@ -405,6 +402,13 @@
startTransaction.setWindowCrop(change.getLeash(),
change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
}
+ // Rotation change of independent non display window container.
+ if (change.getParent() == null
+ && change.getStartRotation() != change.getEndRotation()) {
+ startRotationAnimation(startTransaction, change, info,
+ ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
+ continue;
+ }
}
// Don't animate anything that isn't independent.
@@ -535,6 +539,31 @@
}
}
+ private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
+ TransitionInfo.Change change, TransitionInfo info, int animHint,
+ ArrayList<Animator> animations, Runnable onAnimFinish) {
+ final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
+ mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+ // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
+ // content, and background color. The item of "animGroup" will be removed if the sub
+ // animation is finished. Then if the list becomes empty, the rotation animation is done.
+ final ArrayList<Animator> animGroup = new ArrayList<>(3);
+ final ArrayList<Animator> animGroupStore = new ArrayList<>(3);
+ final Runnable finishCallback = () -> {
+ if (!animGroup.isEmpty()) return;
+ anim.kill();
+ animations.removeAll(animGroupStore);
+ onAnimFinish.run();
+ };
+ anim.startAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
+ mMainExecutor, mAnimExecutor);
+ for (int i = animGroup.size() - 1; i >= 0; i--) {
+ final Animator animator = animGroup.get(i);
+ animGroupStore.add(animator);
+ animations.add(animator);
+ }
+ }
+
private void edgeExtendWindow(TransitionInfo.Change change,
Animation a, SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index ebaece2..4e1fa29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -18,11 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Slog;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
@@ -87,17 +85,11 @@
});
}
};
+ Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
try {
if (mRemote.asBinder() != null) {
mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
}
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- mRemote.getAppThread());
- } catch (SecurityException e) {
- Slog.e(Transitions.TAG, "Unable to boost animation thread. This should only happen"
- + " during unit tests");
- }
mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index b15c48c..cedb340 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
@@ -129,15 +128,9 @@
});
}
};
+ Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
try {
handleDeath(remote.asBinder(), finishCallback);
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- remote.getAppThread());
- } catch (SecurityException e) {
- Log.e(Transitions.TAG, "Unable to boost animation thread. This should only happen"
- + " during unit tests");
- }
remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
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 46f73fd..a843b2a 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
@@ -34,7 +34,6 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -85,9 +84,7 @@
private final Context mContext;
private final TransactionPool mTransactionPool;
private final float[] mTmpFloats = new float[9];
- // Complete transformations being applied.
- private final Matrix mSnapshotInitialMatrix = new Matrix();
- /** The leash of display. */
+ /** The leash of the changing window container. */
private final SurfaceControl mSurfaceControl;
private final Rect mStartBounds = new Rect();
private final Rect mEndBounds = new Rect();
@@ -169,16 +166,15 @@
.setName("RotationLayer")
.build();
- GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
- screenshotBuffer.getHardwareBuffer());
-
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.setPosition(mAnimLeash, 0, 0);
t.setAlpha(mAnimLeash, 1);
t.show(mAnimLeash);
- t.setBuffer(mScreenshotLayer, buffer);
- t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
+ final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
+ final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+ t.setDataSpace(mScreenshotLayer, colorSpace.getDataSpace());
+ t.setBuffer(mScreenshotLayer, hardwareBuffer);
t.show(mScreenshotLayer);
if (!isCustomRotate()) {
@@ -189,8 +185,7 @@
.setName("BackColorSurface")
.build();
- HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
+ mStartLuma = getMedianBorderLuma(hardwareBuffer, colorSpace);
t.setLayer(mBackColorSurface, -1);
t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
@@ -202,7 +197,7 @@
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
- setRotation(t);
+ setScreenshotTransform(t);
t.apply();
}
@@ -210,19 +205,36 @@
return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
}
- private void setRotation(SurfaceControl.Transaction t) {
- // Compute the transformation matrix that must be applied
- // to the snapshot to make it stay in the same original position
- // with the current screen rotation.
- int delta = deltaRotation(mEndRotation, mStartRotation);
- createRotationMatrix(delta, mStartWidth, mStartHeight, mSnapshotInitialMatrix);
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
-
- private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
+ private void setScreenshotTransform(SurfaceControl.Transaction t) {
if (mScreenshotLayer == null) {
return;
}
+ final Matrix matrix = new Matrix();
+ final int delta = deltaRotation(mEndRotation, mStartRotation);
+ if (delta != 0) {
+ // Compute the transformation matrix that must be applied to the snapshot to make it
+ // stay in the same original position with the current screen rotation.
+ switch (delta) {
+ case Surface.ROTATION_90:
+ matrix.setRotate(90, 0, 0);
+ matrix.postTranslate(mStartHeight, 0);
+ break;
+ case Surface.ROTATION_180:
+ matrix.setRotate(180, 0, 0);
+ matrix.postTranslate(mStartWidth, mStartHeight);
+ break;
+ case Surface.ROTATION_270:
+ matrix.setRotate(270, 0, 0);
+ matrix.postTranslate(0, mStartWidth);
+ break;
+ }
+ } else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
+ && (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
+ // Display resizes without rotation change.
+ final float scale = Math.max((float) mEndWidth / mStartHeight,
+ (float) mEndHeight / mStartHeight);
+ matrix.setScale(scale, scale);
+ }
matrix.getValues(mTmpFloats);
float x = mTmpFloats[Matrix.MTRANS_X];
float y = mTmpFloats[Matrix.MTRANS_Y];
@@ -230,9 +242,7 @@
t.setMatrix(mScreenshotLayer,
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-
t.setAlpha(mScreenshotLayer, (float) 1.0);
- t.show(mScreenshotLayer);
}
/**
@@ -486,27 +496,6 @@
return getMedianBorderLuma(buffer.getHardwareBuffer(), buffer.getColorSpace());
}
- private static void createRotationMatrix(int rotation, int width, int height,
- Matrix outMatrix) {
- switch (rotation) {
- case Surface.ROTATION_0:
- outMatrix.reset();
- break;
- case Surface.ROTATION_90:
- outMatrix.setRotate(90, 0, 0);
- outMatrix.postTranslate(height, 0);
- break;
- case Surface.ROTATION_180:
- outMatrix.setRotate(180, 0, 0);
- outMatrix.postTranslate(width, height);
- break;
- case Surface.ROTATION_270:
- outMatrix.setRotate(270, 0, 0);
- outMatrix.postTranslate(0, width);
- break;
- }
- }
-
private static void applyColor(int startColor, int endColor, float[] rgbFloat,
float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java
new file mode 100644
index 0000000..678e91f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SplitscreenPipMixedHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.transition;
+
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.Optional;
+
+/**
+ * Handles transitions between the Splitscreen and PIP components.
+ */
+public class SplitscreenPipMixedHandler {
+
+ private final Optional<SplitScreenController> mSplitScreenOptional;
+ private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
+ private final Transitions mTransitions;
+
+ public SplitscreenPipMixedHandler(ShellInit shellInit,
+ Optional<SplitScreenController> splitScreenControllerOptional,
+ Optional<PipTouchHandler> pipTouchHandlerOptional,
+ Transitions transitions) {
+ mSplitScreenOptional = splitScreenControllerOptional;
+ mPipTouchHandlerOptional = pipTouchHandlerOptional;
+ mTransitions = transitions;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS
+ && mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ private void onInit() {
+ // Special handling for initializing based on multiple components
+ final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
+ mPipTouchHandlerOptional.get().getTransitionHandler(),
+ mSplitScreenOptional.get().getTransitionHandler());
+ // Added at end so that it has highest priority.
+ mTransitions.addHandler(mixedHandler);
+ }
+}
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 fa22c7c..cd29741 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
@@ -31,6 +31,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.app.IApplicationThread;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -57,13 +59,13 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.Arrays;
@@ -98,11 +100,18 @@
/** Transition type for dismissing split-screen. */
public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 7;
+ /** Transition type for freeform to maximize transition. */
+ public static final int TRANSIT_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 8;
+
+ /** Transition type for maximize to freeform transition. */
+ public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
+ private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
private final DisplayController mDisplayController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
@@ -128,8 +137,11 @@
/** Keeps track of currently playing transitions in the order of receipt. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
- public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
- @NonNull DisplayController displayController, @NonNull Context context,
+ public Transitions(@NonNull Context context,
+ @NonNull ShellInit shellInit,
+ @NonNull WindowOrganizer organizer,
+ @NonNull TransactionPool pool,
+ @NonNull DisplayController displayController,
@NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
@@ -138,33 +150,35 @@
mAnimExecutor = animExecutor;
mDisplayController = displayController;
mPlayerImpl = new TransitionPlayerImpl();
+ mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
+ displayController, pool, mainExecutor, mainHandler, animExecutor);
+ mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
// The very last handler (0 in the list) should be the default one.
- mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
- mainHandler, animExecutor));
+ mHandlers.add(mDefaultTransitionHandler);
// Next lowest priority is remote transitions.
- mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor);
mHandlers.add(mRemoteTransitionHandler);
- ContentResolver resolver = context.getContentResolver();
+ ContentResolver resolver = mContext.getContentResolver();
mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
Settings.Global.TRANSITION_ANIMATION_SCALE,
- context.getResources().getFloat(
+ mContext.getResources().getFloat(
R.dimen.config_appTransitionAnimationDurationScaleDefault));
dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
new SettingsObserver());
- }
- private Transitions() {
- mOrganizer = null;
- mContext = null;
- mMainExecutor = null;
- mAnimExecutor = null;
- mDisplayController = null;
- mPlayerImpl = null;
- mRemoteTransitionHandler = null;
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // Register this transition handler with Core
+ mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ // Pre-load the instance.
+ TransitionMetrics.getInstance();
+ }
}
public ShellTransitions asRemoteTransitions() {
@@ -187,14 +201,6 @@
}
}
- /** Register this transition handler with Core */
- public void register(ShellTaskOrganizer taskOrganizer) {
- if (mPlayerImpl == null) return;
- taskOrganizer.registerTransitionPlayer(mPlayerImpl);
- // Pre-load the instance.
- TransitionMetrics.getInstance();
- }
-
/**
* Adds a handler candidate.
* @see TransitionHandler
@@ -228,6 +234,19 @@
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
+ /** Boosts the process priority of remote animation player. */
+ public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
+ if (appThread == null) return;
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(appThread);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Unable to boost animation process. This should only happen"
+ + " during unit tests");
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Runs the given {@code runnable} when the last active transition has finished, or immediately
* if there are currently no active transitions.
@@ -337,7 +356,7 @@
// Put all the OPEN/SHOW on top
if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
// Wallpaper is always at the bottom.
- layer = 0;
+ layer = -zSplitLine;
} else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
// put on top
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 05a024a..6b59e31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -24,13 +24,14 @@
import android.util.SparseArray;
import android.view.SurfaceControl;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import java.util.List;
import java.util.Optional;
-import java.util.concurrent.Executor;
import dagger.Lazy;
@@ -47,7 +48,7 @@
public class UnfoldAnimationController implements UnfoldListener {
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
- private final Executor mExecutor;
+ private final ShellExecutor mExecutor;
private final TransactionPool mTransactionPool;
private final List<UnfoldTaskAnimator> mAnimators;
private final Lazy<Optional<UnfoldTransitionHandler>> mUnfoldTransitionHandler;
@@ -55,28 +56,36 @@
private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
- public UnfoldAnimationController(@NonNull TransactionPool transactionPool,
+ public UnfoldAnimationController(
+ @NonNull ShellInit shellInit,
+ @NonNull TransactionPool transactionPool,
@NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
@NonNull List<UnfoldTaskAnimator> animators,
@NonNull Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
- @NonNull Executor executor) {
+ @NonNull ShellExecutor executor) {
mUnfoldProgressProvider = unfoldProgressProvider;
mUnfoldTransitionHandler = unfoldTransitionHandler;
mTransactionPool = transactionPool;
mExecutor = executor;
mAnimators = animators;
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ if (unfoldProgressProvider != ShellUnfoldProgressProvider.NO_PROVIDER) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
/**
* Initializes the controller, starts listening for the external events
*/
- public void init() {
+ public void onInit() {
mUnfoldProgressProvider.addListener(mExecutor, this);
for (int i = 0; i < mAnimators.size(); i++) {
final UnfoldTaskAnimator animator = mAnimators.get(i);
animator.init();
- animator.start();
+ // TODO(b/238217847): See #provideSplitTaskUnfoldAnimatorBase
+ mExecutor.executeDelayed(animator::start, 0);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 9bf32fa..5d7b629 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -28,6 +28,7 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
import com.android.wm.shell.transition.Transitions.TransitionHandler;
@@ -59,11 +60,13 @@
private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
- public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
+ public UnfoldTransitionHandler(ShellInit shellInit,
+ ShellUnfoldProgressProvider unfoldProgressProvider,
FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator,
SplitTaskUnfoldAnimator splitUnfoldTaskAnimator,
TransactionPool transactionPool,
- Executor executor, Transitions transitions) {
+ Executor executor,
+ Transitions transitions) {
mUnfoldProgressProvider = unfoldProgressProvider;
mTransactionPool = transactionPool;
mExecutor = executor;
@@ -71,9 +74,18 @@
mAnimators.add(splitUnfoldTaskAnimator);
mAnimators.add(fullscreenUnfoldAnimator);
+ // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
+ // override for this controller from the base module
+ if (unfoldProgressProvider != ShellUnfoldProgressProvider.NO_PROVIDER
+ && Transitions.ENABLE_SHELL_TRANSITIONS) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
- public void init() {
+ /**
+ * Called when the transition handler is initialized.
+ */
+ public void onInit() {
for (int i = 0; i < mAnimators.size(); i++) {
mAnimators.get(i).init();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index fe3724b..08d6c50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -36,6 +36,8 @@
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.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.transition.Transitions;
/**
* View model for the window decoration with a caption and shadows. Works with
@@ -50,6 +52,8 @@
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
+ private FreeformTaskTransitionStarter mTransitionStarter;
+
public CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
@@ -67,8 +71,16 @@
}
@Override
+ public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
+ mTransitionStarter = transitionStarter;
+ }
+
+ @Override
public CaptionWindowDecoration createWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface) {
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
mContext,
mDisplayController,
@@ -83,18 +95,39 @@
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
- onTaskInfoChanged(taskInfo, windowDecoration);
+ setupWindowDecorationForTransition(taskInfo, startT, finishT, windowDecoration);
+ setupCaptionColor(taskInfo, windowDecoration);
return windowDecoration;
}
@Override
+ public CaptionWindowDecoration adoptWindowDecoration(AutoCloseable windowDecor) {
+ return (windowDecor instanceof CaptionWindowDecoration)
+ ? (CaptionWindowDecoration) windowDecor
+ : null;
+ }
+
+ @Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
decoration.relayout(taskInfo);
+ setupCaptionColor(taskInfo, decoration);
+ }
+
+ private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
decoration.setCaptionColor(statusBarColor);
}
+ @Override
+ public void setupWindowDecorationForTransition(
+ RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT,
+ CaptionWindowDecoration decoration) {
+ decoration.relayout(taskInfo, startT, finishT);
+ }
+
private class CaptionTouchEventListener implements
View.OnClickListener, View.OnTouchListener {
@@ -105,7 +138,8 @@
private int mDragPointerId = -1;
private CaptionTouchEventListener(
- RunningTaskInfo taskInfo, DragResizeCallback dragResizeCallback) {
+ RunningTaskInfo taskInfo,
+ DragResizeCallback dragResizeCallback) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback;
@@ -113,7 +147,7 @@
@Override
public void onClick(View v) {
- int id = v.getId();
+ final int id = v.getId();
if (id == R.id.close_window) {
mActivityTaskManager.removeTask(mTaskId);
} else if (id == R.id.maximize_window) {
@@ -129,7 +163,11 @@
if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
wct.setBounds(mTaskToken, null);
}
- mSyncQueue.queue(wct);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitionStarter.startWindowingModeTransition(targetWindowingMode, wct);
+ } else {
+ mSyncQueue.queue(wct);
+ }
}
}
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 2df7104..dc212fc 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
@@ -101,6 +101,16 @@
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ relayout(taskInfo, t, t);
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ }
+
+ void relayout(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
final int shadowRadiusDp = taskInfo.isFocused
? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
@@ -110,18 +120,12 @@
WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final WindowContainerTransaction wct = new WindowContainerTransaction();
relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
- DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, t, wct, mResult);
+ DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, startT, finishT, wct, mResult);
taskInfo = null; // Clear it just in case we use it accidentally
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
-
- mTaskOrganizer.applyTransaction(wct);
- });
+ mTaskOrganizer.applyTransaction(wct);
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 6f9ceff..c234949 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -19,6 +19,10 @@
import android.app.ActivityManager;
import android.view.SurfaceControl;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+
/**
* The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help
* customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
@@ -30,13 +34,34 @@
public interface WindowDecorViewModel<T extends AutoCloseable> {
/**
+ * Sets the transition starter that starts freeform task transitions.
+ *
+ * @param transitionStarter the transition starter that starts freeform task transitions
+ */
+ void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
+
+ /**
* Creates a window decoration for the given task.
*
* @param taskInfo the initial task info of the task
* @param taskSurface the surface of the task
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
* @return the window decoration object
*/
- T createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface);
+ T createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT);
+
+ /**
+ * Adopts the window decoration if possible.
+ *
+ * @param windowDecor the potential window decoration to adopt
+ * @return the window decoration if it can be adopted, or {@code null} otherwise.
+ */
+ T adoptWindowDecoration(@Nullable AutoCloseable windowDecor);
/**
* Notifies a task info update on the given task, with the window decoration created previously
@@ -46,4 +71,18 @@
* @param windowDecoration the window decoration created for the task
*/
void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+
+ /**
+ * Notifies a transition is about to start about the given task to give the window decoration a
+ * chance to prepare for this transition.
+ *
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
+ * @param windowDecoration the window decoration created for the task
+ */
+ void setupWindowDecorationForTransition(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT,
+ T windowDecoration);
}
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 4855fbd..4380bdc 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
@@ -140,8 +140,9 @@
abstract void relayout(RunningTaskInfo taskInfo);
void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
- Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction t,
- WindowContainerTransaction wct, RelayoutResult<T> outResult) {
+ Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT, WindowContainerTransaction wct,
+ RelayoutResult<T> outResult) {
outResult.reset();
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
@@ -151,7 +152,7 @@
if (!mTaskInfo.isVisible) {
releaseViews();
- t.hide(mTaskSurface);
+ finishT.hide(mTaskSurface);
return;
}
@@ -192,7 +193,7 @@
.setParent(mTaskSurface)
.build();
- t.setTrustedOverlay(mDecorationContainerSurface, true);
+ startT.setTrustedOverlay(mDecorationContainerSurface, true);
}
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
@@ -205,7 +206,8 @@
outResult.mHeight = taskBounds.height()
+ (int) (outsetsDp.bottom * outResult.mDensity)
- decorContainerOffsetY;
- t.setPosition(mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+ startT.setPosition(
+ mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
.setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
.show(mDecorationContainerSurface);
@@ -225,7 +227,7 @@
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- t.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
+ startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor);
@@ -264,9 +266,9 @@
decorContainerOffsetY,
outResult.mWidth + decorContainerOffsetX,
outResult.mHeight + decorContainerOffsetY);
- t.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setCrop(mTaskSurface, mTaskSurfaceCrop)
- .show(mTaskSurface);
+ startT.show(mTaskSurface);
+ finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+ .setCrop(mTaskSurface, mTaskSurfaceCrop);
}
/**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index c1d743b..1e4d23c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -47,6 +47,14 @@
layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
}
+fun FlickerTestParameter.splitScreenDividerBecomesInvisible() {
+ assertLayers {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ .then()
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+}
+
fun FlickerTestParameter.layerBecomesVisible(
component: IComponentMatcher
) {
@@ -57,6 +65,16 @@
}
}
+fun FlickerTestParameter.layerBecomesInvisible(
+ component: IComponentMatcher
+) {
+ assertLayers {
+ this.isVisible(component)
+ .then()
+ .isInvisible(component)
+ }
+}
+
fun FlickerTestParameter.layerIsVisibleAtEnd(
component: IComponentMatcher
) {
@@ -66,7 +84,6 @@
}
fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
- rotation: Int,
component: IComponentMatcher,
splitLeftTop: Boolean
) {
@@ -75,29 +92,50 @@
this.isInvisible(component)
.then()
.invoke("splitAppLayerBoundsBecomesVisible") {
- it.visibleRegion(component).overlaps(
+ it.visibleRegion(component).coversAtMost(
if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, rotation)
+ getSplitLeftTopRegion(dividerRegion, endRotation)
} else {
- getSplitRightBottomRegion(dividerRegion, rotation)
+ getSplitRightBottomRegion(dividerRegion, endRotation)
}
)
}
}
}
+fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
+ component: IComponentMatcher,
+ splitLeftTop: Boolean
+) {
+ assertLayers {
+ val dividerRegion = this.first().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ this.invoke("splitAppLayerBoundsBecomesVisible") {
+ it.visibleRegion(component).coversAtMost(
+ if (splitLeftTop) {
+ getSplitLeftTopRegion(dividerRegion, endRotation)
+ } else {
+ getSplitRightBottomRegion(dividerRegion, endRotation)
+ }
+ )
+ }
+ .then()
+ .isVisible(component, true)
+ .then()
+ .isInvisible(component)
+ }
+}
+
fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
- rotation: Int,
component: IComponentMatcher,
splitLeftTop: Boolean
) {
assertLayersEnd {
val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(component).overlaps(
+ visibleRegion(component).coversAtMost(
if (splitLeftTop) {
- getSplitLeftTopRegion(dividerRegion, rotation)
+ getSplitLeftTopRegion(dividerRegion, endRotation)
} else {
- getSplitRightBottomRegion(dividerRegion, rotation)
+ getSplitRightBottomRegion(dividerRegion, endRotation)
}
)
}
@@ -113,6 +151,16 @@
}
}
+fun FlickerTestParameter.appWindowBecomesInvisible(
+ component: IComponentMatcher
+) {
+ assertWm {
+ this.isAppWindowVisible(component)
+ .then()
+ .isAppWindowInvisible(component)
+ }
+}
+
fun FlickerTestParameter.appWindowIsVisibleAtEnd(
component: IComponentMatcher
) {
@@ -226,9 +274,17 @@
fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (displayBounds.width > displayBounds.height) {
- Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom)
+ Region.from(
+ 0,
+ 0,
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ displayBounds.bounds.bottom)
} else {
- Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top)
+ Region.from(
+ 0,
+ 0,
+ displayBounds.bounds.right,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2)
}
}
@@ -236,12 +292,16 @@
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (displayBounds.width > displayBounds.height) {
Region.from(
- dividerRegion.bounds.right, 0, displayBounds.bounds.right,
+ (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+ 0,
+ displayBounds.bounds.right,
displayBounds.bounds.bottom
)
} else {
Region.from(
- 0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
+ 0,
+ (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+ displayBounds.bounds.right,
displayBounds.bounds.bottom
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index a558450..1950e48 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -75,7 +75,7 @@
Until.findObjects(
By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)
), FIND_OBJECT_TIMEOUT
- )
+ ) ?: error("No bubbles found")
for (entry in bubbles) {
entry?.run { entry.click() } ?: error("Bubble not found")
SystemClock.sleep(1000)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index ccd5f02..4877442 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -21,6 +21,7 @@
import android.os.SystemClock
import android.view.InputDevice
import android.view.MotionEvent
+import android.view.ViewConfiguration
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
@@ -43,12 +44,15 @@
const val TIMEOUT_MS = 3_000L
const val DRAG_DURATION_MS = 1_000L
const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+ const val DIVIDER_BAR = "docked_divider_handle"
const val GESTURE_STEP_MS = 16L
private val notificationScrollerSelector: BySelector
get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
private val notificationContentSelector: BySelector
get() = By.text("Notification content")
+ private val dividerBarSelector: BySelector
+ get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
SplitScreenHelper(
@@ -78,6 +82,19 @@
Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
)
+ fun waitForSplitComplete(
+ wmHelper: WindowManagerStateHelper,
+ primaryApp: IComponentMatcher,
+ secondaryApp: IComponentMatcher,
+ ) {
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(primaryApp)
+ .withWindowSurfaceAppeared(secondaryApp)
+ .withSplitDividerVisible()
+ .waitForAndVerify()
+ }
+
fun dragFromNotificationToSplit(
instrumentation: Instrumentation,
device: UiDevice,
@@ -190,12 +207,11 @@
}
fun createShortcutOnHotseatIfNotExist(
- taplInstrumentation: LauncherInstrumentation,
+ tapl: LauncherInstrumentation,
appName: String
) {
- taplInstrumentation.workspace
- .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
- val allApps = taplInstrumentation.workspace.switchToAllApps()
+ tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+ val allApps = tapl.workspace.switchToAllApps()
allApps.freeze()
try {
allApps.getAppIcon(appName).dragToHotseat(0)
@@ -203,5 +219,26 @@
allApps.unfreeze()
}
}
+
+ fun dragDividerToDismissSplit(
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper
+ ) {
+ val displayBounds = wmHelper.currentState.layerState
+ .displays.firstOrNull { !it.isVirtual }
+ ?.layerStackSpace
+ ?: error("Display not found")
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ dividerBar.drag(Point(displayBounds.width * 4 / 5, displayBounds.height * 4 / 5))
+ }
+
+ fun doubleTapDividerToSwitch(device: UiDevice) {
+ val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+ val interval = (ViewConfiguration.getDoubleTapTimeout() +
+ ViewConfiguration.getDoubleTapMinTime()) / 2
+ dividerBar.click()
+ SystemClock.sleep(interval.toLong())
+ dividerBar.click()
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 77dcbe0..b71a9d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -69,7 +69,7 @@
*/
@Presubmit
@Test
- fun focusDoesNotChange() {
+ fun focusChanges() {
testSpec.assertEventLog {
this.focusChanges("PipMenuView", "NexusLauncherActivity")
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 2abe6d5..09248a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -90,21 +89,21 @@
/**
* Checks that all parts of the screen are covered at the start and end of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
override fun entireScreenCovered() = testSpec.entireScreenCovered()
/**
* Checks the position of the navigation bar at the start and end of the transition
*/
- @FlakyTest
+ @FlakyTest(bugId = 240499181)
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/**
* Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun fixedAppLayer_StartingBounds() {
testSpec.assertLayersStart {
@@ -115,7 +114,7 @@
/**
* Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun fixedAppLayer_EndingBounds() {
testSpec.assertLayersEnd {
@@ -127,7 +126,7 @@
* Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the start
* of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun appLayers_StartingBounds() {
testSpec.assertLayersStart {
@@ -139,7 +138,7 @@
* Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the end
* of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun appLayers_EndingBounds() {
testSpec.assertLayersEnd {
@@ -159,7 +158,7 @@
/**
* Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun pipLayerRotates_StartingBounds() {
pipLayerRotates_StartingBounds_internal()
@@ -168,7 +167,7 @@
/**
* Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun pipLayerRotates_EndingBounds() {
testSpec.assertLayersEnd {
@@ -180,7 +179,7 @@
* Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the
* transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun pipIsAboveFixedAppWindow_Start() {
testSpec.assertWmStart {
@@ -192,7 +191,7 @@
* Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the
* transition
*/
- @Presubmit
+ @FlakyTest(bugId = 240499181)
@Test
fun pipIsAboveFixedAppWindow_End() {
testSpec.assertWmEnd {
@@ -200,6 +199,60 @@
}
}
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ super.navBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ super.statusBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {
+ super.statusBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ super.taskBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest(bugId = 240499181)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
/**
* Creates the test configurations.
@@ -212,7 +265,8 @@
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance().getConfigRotationTests(
supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
- repetitions = 3)
+ repetitions = 3
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
new file mode 100644
index 0000000..cd92db7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dismiss split screen by dragging the divider bar.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ tapl.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+ transitions {
+ SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper)
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withFullScreenApp(secondaryApp)
+ .waitForAndVerify()
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsFullscreenAtEnd() {
+ testSpec.assertLayers {
+ this.isVisible(secondaryApp)
+ .then()
+ .isInvisible(secondaryApp)
+ .then()
+ .isVisible(secondaryApp)
+ .invoke("secondaryAppBoundsIsFullscreenAtEnd") {
+ val displayBounds = WindowUtils.getDisplayBounds(testSpec.endRotation)
+ it.visibleRegion(secondaryApp).coversExactly(displayBounds)
+ }
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
new file mode 100644
index 0000000..127ac1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dismiss split screen by go home.
+ *
+ * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class DismissSplitScreenByGoHome(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+ transitions {
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
+ secondaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 779be0a..9564d97 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -72,15 +72,16 @@
}
transitions {
tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
@Presubmit
@Test
@@ -93,12 +94,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp, false /* splitLeftTop */)
+ primaryApp, splitLeftTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, secondaryApp, true /* splitLeftTop */)
+ secondaryApp, splitLeftTop = true)
@Presubmit
@Test
@@ -106,8 +107,7 @@
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index d47d81b..3b59716 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -84,6 +84,7 @@
}
transitions {
SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
}
teardown {
eachRun {
@@ -94,7 +95,7 @@
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
@Presubmit
@Test
@@ -108,14 +109,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp, false /* splitLeftTop */
- )
+ primaryApp, splitLeftTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, sendNotificationApp, true /* splitLeftTop */
- )
+ sendNotificationApp, splitLeftTop = true)
@Presubmit
@Test
@@ -123,8 +122,7 @@
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() =
- testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 1493d1f..3de9872 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -77,16 +77,14 @@
transitions {
tapl.launchedAppState.taskbar
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(
- secondaryApp.`package`,
- primaryApp.`package`
- )
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@Presubmit
@Test
- fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
@Presubmit
@Test
@@ -99,14 +97,12 @@
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- testSpec.endRotation, primaryApp, splitLeftTop = false
- )
+ primaryApp, splitLeftTop = false)
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
- testSpec.endRotation, secondaryApp, splitLeftTop = true
- )
+ secondaryApp, splitLeftTop = true)
@Presubmit
@Test
@@ -114,8 +110,7 @@
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() =
- testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
new file mode 100644
index 0000000..38279a3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -0,0 +1,192 @@
+/*
+ * 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.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test double tap the divider bar to switch the two apps.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+ transitions {
+ SplitScreenHelper.doubleTapDividerToSwitch(device)
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerKeepVisible() {
+ testSpec.assertLayers {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
new file mode 100644
index 0000000..c48f3f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test switch back to split pair after go home
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+ transitions {
+ tapl.workspace.quickSwitchToPreviousApp()
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ secondaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
index 219e5ab..4bcdcaa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
@@ -24,25 +24,8 @@
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.freeform.FreeformTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowController;
-import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.unfold.UnfoldAnimationController;
-import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import org.junit.Before;
import org.junit.Test;
@@ -51,31 +34,12 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class ShellInitTest extends ShellTestCase {
- @Mock private ShellController mShellController;
- @Mock private DisplayController mDisplayController;
- @Mock private DisplayImeController mDisplayImeController;
- @Mock private DisplayInsetsController mDisplayInsetsController;
- @Mock private DragAndDropController mDragAndDropController;
- @Mock private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock private KidsModeTaskOrganizer mKidsModeTaskOrganizer;
- @Mock private Optional<BubbleController> mBubblesOptional;
- @Mock private Optional<SplitScreenController> mSplitScreenOptional;
- @Mock private Optional<PipTouchHandler> mPipTouchHandlerOptional;
- @Mock private FullscreenTaskListener mFullscreenTaskListener;
- @Mock private Optional<UnfoldAnimationController> mUnfoldAnimationController;
- @Mock private Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
- @Mock private Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional;
- @Mock private Optional<RecentTasksController> mRecentTasks;
- @Mock private Optional<ActivityEmbeddingController> mActivityEmbeddingController;
- @Mock private Transitions mTransitions;
- @Mock private StartingWindowController mStartingWindow;
@Mock private ShellExecutor mMainExecutor;
private ShellInit mImpl;
@@ -83,12 +47,7 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mImpl = new ShellInit(mShellController, mDisplayController, mDisplayImeController,
- mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer,
- mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional,
- mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController,
- mUnfoldTransitionHandler, mFreeformTaskListenerOptional, mRecentTasks,
- mActivityEmbeddingController, mTransitions, mStartingWindow, mMainExecutor);
+ mImpl = new ShellInit(mMainExecutor);
}
@Test
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 3dd0032..7517e8a 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
@@ -36,11 +36,11 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.content.Context;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -58,9 +58,8 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -84,13 +83,11 @@
@Mock
private ITaskOrganizerController mTaskOrganizerController;
@Mock
- private Context mContext;
- @Mock
private CompatUIController mCompatUI;
+ @Mock
+ private ShellInit mShellInit;
ShellTaskOrganizer mOrganizer;
- private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
- private final TransactionPool mTransactionPool = mock(TransactionPool.class);
private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
@@ -135,8 +132,13 @@
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
- mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
- mCompatUI, Optional.empty(), Optional.empty()));
+ mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mTaskOrganizerController,
+ mCompatUI, Optional.empty(), Optional.empty(), mTestExecutor));
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
new file mode 100644
index 0000000..bfe3b54
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -0,0 +1,67 @@
+/*
+ * 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.activityembedding;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the activity embedding controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ActivityEmbeddingControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityEmbeddingControllerTests extends ShellTestCase {
+
+ private @Mock Context mContext;
+ private @Mock ShellInit mShellInit;
+ private @Mock Transitions mTransitions;
+ private ActivityEmbeddingController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = spy(new ActivityEmbeddingController(mContext, mShellInit, mTransitions));
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 31e55e7..ba81602 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -39,6 +40,7 @@
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.provider.Settings;
@@ -121,24 +123,22 @@
HardwareBuffer hardwareBuffer,
int backType,
IOnBackInvokedCallback onBackInvokedCallback) {
- BackNavigationInfo navigationInfo = new BackNavigationInfo(
- backType,
- topAnimationTarget,
- screenshotSurface,
- hardwareBuffer,
- new WindowConfiguration(),
- new RemoteCallback((bundle) -> {}),
- onBackInvokedCallback);
- try {
- doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation(anyBoolean());
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
- }
+ BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
+ .setType(backType)
+ .setDepartingAnimationTarget(topAnimationTarget)
+ .setScreenshotSurface(screenshotSurface)
+ .setScreenshotBuffer(hardwareBuffer)
+ .setTaskWindowConfiguration(new WindowConfiguration())
+ .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
+ .setOnBackInvokedCallback(onBackInvokedCallback);
+
+ createNavigationInfo(builder);
}
private void createNavigationInfo(BackNavigationInfo.Builder builder) {
try {
- doReturn(builder.build()).when(mActivityTaskManager).startBackNavigation(anyBoolean());
+ doReturn(builder.build()).when(mActivityTaskManager)
+ .startBackNavigation(anyBoolean(), any());
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
@@ -297,6 +297,34 @@
verify(mIOnBackInvokedCallback).onBackStarted();
}
+
+ @Test
+ public void cancelBackInvokeWhenLostFocus() throws RemoteException {
+ mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
+ RemoteAnimationTarget animationTarget = createAnimationTarget();
+
+ createNavigationInfo(animationTarget, null, null,
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+
+ doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+ // Check that back start and progress is dispatched when first move.
+ doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ verify(mIOnBackInvokedCallback).onBackStarted();
+
+ // Check that back invocation is dispatched.
+ mController.setTriggerBack(true); // Fake trigger back
+
+ // In case the focus has been changed.
+ IBinder token = mock(IBinder.class);
+ mController.mFocusObserver.focusLost(token);
+ mShellExecutor.flushAll();
+ verify(mIOnBackInvokedCallback).onBackCancelled();
+
+ // No more back invoke.
+ doMotionEvent(MotionEvent.ACTION_UP, 0);
+ verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+ }
+
private void doMotionEvent(int actionDown, int coordinate) {
mController.onMotionEvent(
coordinate, coordinate,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
index 0972cf2..1636c5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -25,6 +25,7 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertTrue
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,6 +62,12 @@
bubbles.put(1, user1Bubbles)
}
+ @After
+ fun teardown() {
+ // Clean up the any persisted bubbles for the next run
+ repository.persistsToDisk(SparseArray())
+ }
+
@Test
fun testReadWriteOperation() {
// Verify read before write doesn't cause FileNotFoundException
@@ -71,4 +78,4 @@
repository.persistsToDisk(bubbles)
assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk()))
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
new file mode 100644
index 0000000..b8aa8e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
@@ -0,0 +1,64 @@
+/*
+ * 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.common;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.view.IWindowManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the display change controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DisplayChangeControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayChangeControllerTests extends ShellTestCase {
+
+ private @Mock IWindowManager mWM;
+ private @Mock ShellInit mShellInit;
+ private @Mock ShellExecutor mMainExecutor;
+ private DisplayChangeController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = spy(new DisplayChangeController(mWM, mShellInit, mMainExecutor));
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
new file mode 100644
index 0000000..1e5e153
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
@@ -0,0 +1,65 @@
+/*
+ * 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.common;
+
+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 android.content.Context;
+import android.view.IWindowManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the display controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DisplayControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayControllerTests extends ShellTestCase {
+
+ private @Mock Context mContext;
+ private @Mock IWindowManager mWM;
+ private @Mock ShellInit mShellInit;
+ private @Mock ShellExecutor mMainExecutor;
+ private DisplayController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = new DisplayController(mContext, mWM, mShellInit, mMainExecutor);
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mController));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 587782c..9967e5f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -40,26 +41,32 @@
import com.android.internal.view.IInputMethodManager;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.concurrent.Executor;
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
+ @Mock
private SurfaceControl.Transaction mT;
- private DisplayImeController.PerDisplay mPerDisplay;
+ @Mock
private IInputMethodManager mMock;
+ @Mock
+ private ShellInit mShellInit;
+ private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@Before
public void setUp() throws Exception {
- mT = mock(SurfaceControl.Transaction.class);
- mMock = mock(IInputMethodManager.class);
+ MockitoAnnotations.initMocks(this);
mExecutor = spy(Runnable::run);
- mPerDisplay = new DisplayImeController(null, null, null, mExecutor, new TransactionPool() {
+ mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
@Override
public SurfaceControl.Transaction acquire() {
return mT;
@@ -68,7 +75,7 @@
@Override
public void release(SurfaceControl.Transaction t) {
}
- }) {
+ }, mExecutor) {
@Override
public IInputMethodManager getImms() {
return mMock;
@@ -79,6 +86,11 @@
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void insetsControlChanged_schedulesNoWorkOnExecutor() {
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
verifyZeroInteractions(mExecutor);
@@ -122,7 +134,7 @@
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
- ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0), Insets.NONE)
+ ITYPE_IME, mock(SurfaceControl.class), false, new Point(0, 0), Insets.NONE)
};
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 4a7fd3d..5f5a3c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -19,6 +19,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.times;
@@ -37,6 +38,7 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -55,6 +57,8 @@
private IWindowManager mWm;
@Mock
private DisplayController mDisplayController;
+ @Mock
+ private ShellInit mShellInit;
private DisplayInsetsController mController;
private SparseArray<IDisplayWindowInsetsController> mInsetsControllersByDisplayId;
private TestShellExecutor mExecutor;
@@ -69,11 +73,16 @@
mInsetsControllersByDisplayId = new SparseArray<>();
mDisplayIdCaptor = ArgumentCaptor.forClass(Integer.class);
mInsetsControllerCaptor = ArgumentCaptor.forClass(IDisplayWindowInsetsController.class);
- mController = new DisplayInsetsController(mWm, mDisplayController, mExecutor);
+ mController = new DisplayInsetsController(mWm, mShellInit, mDisplayController, mExecutor);
addDisplay(DEFAULT_DISPLAY);
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void testOnDisplayAdded_setsDisplayWindowInsetsControllerOnWMService()
throws RemoteException {
addDisplay(SECOND_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index e20997199..b6dbcf2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -50,6 +50,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -57,8 +58,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for the drag and drop controller.
*/
@@ -69,6 +68,8 @@
@Mock
private Context mContext;
@Mock
+ private ShellInit mShellInit;
+ @Mock
private ShellController mShellController;
@Mock
private DisplayController mDisplayController;
@@ -88,9 +89,14 @@
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
- mController = new DragAndDropController(mContext, mShellController, mDisplayController,
- mUiEventLogger, mIconProvider, mMainExecutor);
- mController.initialize(Optional.of(mSplitScreenController));
+ mController = new DragAndDropController(mContext, mShellInit, mShellController,
+ mDisplayController, mUiEventLogger, mIconProvider, mMainExecutor);
+ mController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index 184a8df..a919ad0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -49,7 +49,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -73,7 +73,7 @@
@Mock private WindowContainerToken mToken;
@Mock private WindowContainerTransaction mTransaction;
@Mock private KidsModeSettingsObserver mObserver;
- @Mock private StartingWindowController mStartingWindowController;
+ @Mock private ShellInit mShellInit;
@Mock private DisplayInsetsController mDisplayInsetsController;
KidsModeTaskOrganizer mOrganizer;
@@ -87,10 +87,9 @@
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
- mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
- mHandler, mContext, mSyncTransactionQueue, mDisplayController,
- mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver));
- mOrganizer.initialize(mStartingWindowController);
+ mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mTaskOrganizerController,
+ mSyncTransactionQueue, mDisplayController, mDisplayInsetsController,
+ Optional.empty(), Optional.empty(), mObserver, mTestExecutor, mHandler));
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 74519ea..ecefd89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -38,6 +38,7 @@
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -78,6 +79,9 @@
private PipUiEventLogger mPipUiEventLogger;
@Mock
+ private ShellInit mShellInit;
+
+ @Mock
private ShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState;
@@ -104,11 +108,11 @@
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
- mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
- mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
- pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger,
- mMainExecutor);
- mPipTouchHandler.init();
+ mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
+ mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper,
+ mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+ // We aren't actually using ShellInit, so just call init directly
+ mPipTouchHandler.onInit();
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
mPipTouchHandler.setPipMotionHelper(mMotionHelper);
@@ -133,6 +137,11 @@
}
@Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void updateMovementBounds_minMaxBounds() {
final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
mPipBoundsState.getDisplayBounds().height());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
index 05e4722..cc51efd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipBoundsControllerTest.kt
@@ -179,6 +179,16 @@
}
@Test
+ fun testImmediatePlacement_DoNotStashIfAlreadyUnstashed() {
+ triggerImmediatePlacement(STASHED_PLACEMENT_RESTASH)
+ assertMovement(STASHED_BOUNDS)
+ assertMovementAt(time + STASH_DURATION, ANCHOR_BOUNDS)
+
+ triggerImmediatePlacement(STASHED_PLACEMENT)
+ assertNoMovementUpTo(time + FAR_FUTURE)
+ }
+
+ @Test
fun testInMoveMode_KeepAtAnchor() {
startMoveMode()
triggerImmediatePlacement(STASHED_MOVED_PLACEMENT_RESTASH)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index b1e0911..d406a4e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -48,6 +48,7 @@
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
@@ -71,6 +72,8 @@
private Context mContext;
@Mock
private TaskStackListenerImpl mTaskStackListener;
+ @Mock
+ private ShellInit mShellInit;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -80,10 +83,11 @@
public void setUp() {
mMainExecutor = new TestShellExecutor();
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
- mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener,
- mMainExecutor));
- mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext,
- null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController));
+ mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
+ mTaskStackListener, mMainExecutor));
+ mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit,
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
+ mMainExecutor);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index c7a261f..10788f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -51,8 +51,10 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -71,6 +73,7 @@
public class SplitScreenControllerTests extends ShellTestCase {
@Mock ShellController mShellController;
+ @Mock ShellInit mShellInit;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock SyncTransactionQueue mSyncQueue;
@Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -78,6 +81,7 @@
@Mock DisplayController mDisplayController;
@Mock DisplayImeController mDisplayImeController;
@Mock DisplayInsetsController mDisplayInsetsController;
+ @Mock DragAndDropController mDragAndDropController;
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
@@ -88,16 +92,21 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer,
- mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController,
- mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks));
+ mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
+ mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController,
+ mDisplayImeController, mDisplayInsetsController, mDragAndDropController,
+ mTransitions, mTransactionPool, mIconProvider, mRecentTasks, mMainExecutor));
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
public void testControllerRegistersKeyguardChangeListener() {
when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
- mSplitScreenController.onOrganizerRegistered();
+ mSplitScreenController.onInit();
verify(mShellController, times(1)).addKeyguardChangeListener(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 46b040f..e5ae296 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -251,7 +251,7 @@
anyInt() /* viewVisibility */, anyInt() /* displayId */,
any() /* requestedVisibility */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */,
- any() /* outAttachedFrame */);
+ any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
mBinder,
snapshot, mTestExecutor, () -> {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
new file mode 100644
index 0000000..35515e3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -0,0 +1,80 @@
+/*
+ * 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.startingsurface;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the starting window controller.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:StartingWindowControllerTests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StartingWindowControllerTests extends ShellTestCase {
+
+ private @Mock Context mContext;
+ private @Mock DisplayManager mDisplayManager;
+ private @Mock ShellInit mShellInit;
+ private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private @Mock ShellExecutor mMainExecutor;
+ private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm;
+ private @Mock IconProvider mIconProvider;
+ private @Mock TransactionPool mTransactionPool;
+ private StartingWindowController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt());
+ doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
+ mController = new StartingWindowController(mContext, mShellInit, mTaskOrganizer,
+ mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 1c0e46f..02311ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -43,6 +43,8 @@
public class ShellControllerTest extends ShellTestCase {
@Mock
+ private ShellInit mShellInit;
+ @Mock
private ShellExecutor mExecutor;
private ShellController mController;
@@ -54,7 +56,7 @@
MockitoAnnotations.initMocks(this);
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
- mController = new ShellController(mExecutor);
+ mController = new ShellController(mShellInit, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e2f2b71..388792b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -84,6 +84,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -117,6 +118,14 @@
}
@Test
+ public void instantiate_addInitCallback() {
+ ShellInit shellInit = mock(ShellInit.class);
+ final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
+ createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ verify(shellInit, times(1)).addInitCallback(any(), eq(t));
+ }
+
+ @Test
public void testBasicTransitionFlow() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -832,14 +841,18 @@
} catch (RemoteException e) {
// No remote stuff happening, so this can't be hit
}
- DisplayController out = new DisplayController(mContext, mockWM, mMainExecutor);
- out.initialize();
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ DisplayController out = new DisplayController(mContext, mockWM, shellInit, mMainExecutor);
+ shellInit.init();
return out;
}
private Transitions createTestTransitions() {
- return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(),
- mContext, mMainExecutor, mMainHandler, mAnimExecutor);
+ ShellInit shellInit = new ShellInit(mMainExecutor);
+ final Transitions t = new Transitions(mContext, shellInit, mOrganizer, mTransactionPool,
+ createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor);
+ shellInit.init();
+ return t;
}
//
// private class TestDisplayController extends DisplayController {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
index 46de607..81eefe2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
@@ -20,7 +20,10 @@
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.ActivityManager.RunningTaskInfo;
@@ -33,6 +36,7 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import org.junit.Before;
@@ -65,6 +69,8 @@
@Mock
private UnfoldTransitionHandler mUnfoldTransitionHandler;
@Mock
+ private ShellInit mShellInit;
+ @Mock
private SurfaceControl mLeash;
private UnfoldAnimationController mUnfoldAnimationController;
@@ -85,6 +91,7 @@
animators.add(mTaskAnimator1);
animators.add(mTaskAnimator2);
mUnfoldAnimationController = new UnfoldAnimationController(
+ mShellInit,
mTransactionPool,
mProgressProvider,
animators,
@@ -94,6 +101,11 @@
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void testAppearedMatchingTask_appliesUnfoldProgress() {
mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
@@ -244,7 +256,8 @@
@Test
public void testInit_initsAndStartsAnimators() {
- mUnfoldAnimationController.init();
+ mUnfoldAnimationController.onInit();
+ mShellExecutor.flushAll();
assertThat(mTaskAnimator1.mInitialized).isTrue();
assertThat(mTaskAnimator1.mStarted).isTrue();
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 d1b837e..b318bb2 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
@@ -98,11 +98,13 @@
private WindowContainerTransaction mMockWindowContainerTransaction;
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
- private SurfaceControl.Transaction mMockSurfaceControlTransaction;
+ private SurfaceControl.Transaction mMockSurfaceControlStartT;
+ private SurfaceControl.Transaction mMockSurfaceControlFinishT;
@Before
public void setUp() {
- mMockSurfaceControlTransaction = createMockSurfaceControlTransaction();
+ mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
+ mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any(), anyBoolean());
@@ -149,7 +151,7 @@
verify(mMockSurfaceControlViewHostFactory, never())
.create(any(), any(), any(), anyBoolean());
- verify(mMockSurfaceControlTransaction).hide(taskSurface);
+ verify(mMockSurfaceControlFinishT).hide(taskSurface);
assertNull(mRelayoutResult.mRootView);
}
@@ -192,16 +194,16 @@
verify(decorContainerSurfaceBuilder).setParent(taskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlTransaction).setTrustedOverlay(decorContainerSurface, true);
- verify(mMockSurfaceControlTransaction).setPosition(decorContainerSurface, -20, -40);
- verify(mMockSurfaceControlTransaction).setWindowCrop(decorContainerSurface, 380, 220);
+ verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
+ verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
+ verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
verify(taskBackgroundSurfaceBuilder).setEffectLayer();
- verify(mMockSurfaceControlTransaction).setWindowCrop(taskBackgroundSurface, 300, 100);
- verify(mMockSurfaceControlTransaction)
+ verify(mMockSurfaceControlStartT).setWindowCrop(taskBackgroundSurface, 300, 100);
+ verify(mMockSurfaceControlStartT)
.setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
- verify(mMockSurfaceControlTransaction).setShadowRadius(taskBackgroundSurface, 10);
+ verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10);
verify(mMockSurfaceControlViewHostFactory)
.create(any(), eq(defaultDisplay), any(), anyBoolean());
@@ -218,11 +220,11 @@
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
- verify(mMockSurfaceControlTransaction)
+ verify(mMockSurfaceControlFinishT)
.setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
- verify(mMockSurfaceControlTransaction)
+ verify(mMockSurfaceControlFinishT)
.setCrop(taskSurface, new Rect(-20, -40, 360, 180));
- verify(mMockSurfaceControlTransaction)
+ verify(mMockSurfaceControlStartT)
.show(taskSurface);
assertEquals(380, mRelayoutResult.mWidth);
@@ -319,8 +321,8 @@
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
- mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlTransaction,
- mMockWindowContainerTransaction, mRelayoutResult);
+ mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlStartT,
+ mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mRelayoutResult);
}
}
}
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 19efc5f..6a7119b 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -140,15 +140,13 @@
static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) {
Typeface* face = toTypeface(faceHandle);
- const std::unordered_set<minikin::AxisTag>& tagSet = face->fFontCollection->getSupportedTags();
- const size_t length = tagSet.size();
+ const size_t length = face->fFontCollection->getSupportedAxesCount();
if (length == 0) {
return nullptr;
}
std::vector<jint> tagVec(length);
- int index = 0;
- for (const auto& tag : tagSet) {
- tagVec[index++] = tag;
+ for (size_t i = 0; i < length; i++) {
+ tagVec[i] = face->fFontCollection->getSupportedAxisAt(i);
}
std::sort(tagVec.begin(), tagVec.end());
const jintArray result = env->NewIntArray(length);
@@ -305,7 +303,8 @@
}
}
-static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) {
+static jint Typeface_writeTypefaces(JNIEnv* env, jobject, jobject buffer, jint position,
+ jlongArray faceHandles) {
MinikinFontSkiaFactory::init();
ScopedLongArrayRO faces(env, faceHandles);
std::vector<Typeface*> typefaces;
@@ -314,7 +313,12 @@
typefaces.push_back(toTypeface(faces[i]));
}
void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer);
- minikin::BufferWriter writer(addr);
+ if (addr != nullptr &&
+ reinterpret_cast<intptr_t>(addr) % minikin::BufferReader::kMaxAlignment != 0) {
+ ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr);
+ return 0;
+ }
+ minikin::BufferWriter writer(addr, position);
std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections;
std::unordered_map<std::shared_ptr<minikin::FontCollection>, size_t> fcToIndex;
for (Typeface* typeface : typefaces) {
@@ -334,11 +338,18 @@
return static_cast<jint>(writer.size());
}
-static jlongArray Typeface_readTypefaces(JNIEnv *env, jobject, jobject buffer) {
+static jlongArray Typeface_readTypefaces(JNIEnv* env, jobject, jobject buffer, jint position) {
MinikinFontSkiaFactory::init();
void* addr = buffer == nullptr ? nullptr : env->GetDirectBufferAddress(buffer);
- if (addr == nullptr) return nullptr;
- minikin::BufferReader reader(addr);
+ if (addr == nullptr) {
+ ALOGE("Passed a null buffer.");
+ return nullptr;
+ }
+ if (reinterpret_cast<intptr_t>(addr) % minikin::BufferReader::kMaxAlignment != 0) {
+ ALOGE("addr (%p) must be aligned at kMaxAlignment, but it was not.", addr);
+ return nullptr;
+ }
+ minikin::BufferReader reader(addr, position);
std::vector<std::shared_ptr<minikin::FontCollection>> fontCollections =
minikin::FontCollection::readVector(&reader);
uint32_t typefaceCount = reader.read<uint32_t>();
@@ -357,7 +368,6 @@
return result;
}
-
static void Typeface_forceSetStaticFinalField(JNIEnv *env, jclass cls, jstring fieldName,
jobject typeface) {
ScopedUtfChars fieldNameChars(env, fieldName);
@@ -417,8 +427,8 @@
{"nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes},
{"nativeRegisterGenericFamily", "(Ljava/lang/String;J)V",
(void*)Typeface_registerGenericFamily},
- {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;[J)I", (void*)Typeface_writeTypefaces},
- {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;)[J", (void*)Typeface_readTypefaces},
+ {"nativeWriteTypefaces", "(Ljava/nio/ByteBuffer;I[J)I", (void*)Typeface_writeTypefaces},
+ {"nativeReadTypefaces", "(Ljava/nio/ByteBuffer;I)[J", (void*)Typeface_readTypefaces},
{"nativeForceSetStaticFinalField", "(Ljava/lang/String;Landroid/graphics/Typeface;)V",
(void*)Typeface_forceSetStaticFinalField},
{"nativeGetFamilySize", "(J)I", (void*)Typeface_getFamilySize},
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 61bb665..fb7d5f7 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -419,14 +419,28 @@
indices = (const uint16_t*)(indexA.ptr() + indexIndex);
}
- SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle);
+ SkVertices::VertexMode vertexMode = static_cast<SkVertices::VertexMode>(modeHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount,
- reinterpret_cast<const SkPoint*>(verts),
- reinterpret_cast<const SkPoint*>(texs),
- reinterpret_cast<const SkColor*>(colors),
- indexCount, indices).get(),
- SkBlendMode::kModulate, *paint);
+
+ // Preserve legacy Skia behavior: ignore the shader if there are no texs set.
+ Paint noShaderPaint;
+ if (jtexs == NULL) {
+ noShaderPaint = Paint(*paint);
+ noShaderPaint.setShader(nullptr);
+ paint = &noShaderPaint;
+ }
+ // Since https://skia-review.googlesource.com/c/skia/+/473676, Skia will blend paint and vertex
+ // colors when no shader is provided. This ternary uses kDst to mimic the old behavior of
+ // ignoring the paint and using the vertex colors directly when no shader is provided.
+ SkBlendMode blendMode = paint->getShader() ? SkBlendMode::kModulate : SkBlendMode::kDst;
+
+ get_canvas(canvasHandle)
+ ->drawVertices(SkVertices::MakeCopy(
+ vertexMode, vertexCount, reinterpret_cast<const SkPoint*>(verts),
+ reinterpret_cast<const SkPoint*>(texs),
+ reinterpret_cast<const SkColor*>(colors), indexCount, indices)
+ .get(),
+ blendMode, *paint);
}
static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 2c421f8..f17129c 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -228,7 +228,7 @@
static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
std::string path = std::string(reader.readString());
if (path.empty()) {
return nullptr;
@@ -270,7 +270,7 @@
static jint Font_getIndex(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
return reader.read<int>();
} else {
@@ -283,7 +283,7 @@
static jint Font_getAxisCount(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
reader.skip<int>(); // fontIndex
return reader.readArray<minikin::FontVariation>().second;
@@ -298,7 +298,7 @@
FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
minikin::BufferReader reader = font->font->typefaceMetadataReader();
minikin::FontVariation var;
- if (reader.data() != nullptr) {
+ if (reader.current() != nullptr) {
reader.skipString(); // fontPath
reader.skip<int>(); // fontIndex
var = reader.readArray<minikin::FontVariation>().first[index];
diff --git a/libs/hwui/thread/WorkQueue.h b/libs/hwui/thread/WorkQueue.h
index 46b8bc0..f2751d2 100644
--- a/libs/hwui/thread/WorkQueue.h
+++ b/libs/hwui/thread/WorkQueue.h
@@ -57,7 +57,7 @@
public:
WorkQueue(std::function<void()>&& wakeFunc, std::mutex& lock)
- : mWakeFunc(move(wakeFunc)), mLock(lock) {}
+ : mWakeFunc(std::move(wakeFunc)), mLock(lock) {}
void process() {
auto now = clock::now();
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index fe58cca..c708876 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -61,10 +61,12 @@
private static IAudioService sService;
- private final String mPackageName;
+ private final @NonNull String mPackageName;
+ private final @Nullable String mAttributionTag;
public AudioDeviceVolumeManager(Context context) {
mPackageName = context.getApplicationContext().getOpPackageName();
+ mAttributionTag = context.getApplicationContext().getAttributionTag();
}
/**
@@ -287,7 +289,6 @@
* @hide
* Removes a previously added listener of changes to device volume behavior.
*/
-
@RequiresPermission(anyOf = {
android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.QUERY_AUDIO_STATE
@@ -299,6 +300,21 @@
}
/**
+ * @hide
+ * Sets the volume on the given audio device
+ * @param vi the volume information, only stream-based volumes are supported
+ * @param ada the device for which volume is to be modified
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
+ try {
+ getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return human-readable name for volume behavior
* @param behavior one of the volume behaviors defined in AudioManager
* @return a string for the given behavior
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 457fccf..c213fcc 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -172,6 +172,11 @@
* The state used to update port id, does not actually change the state of the player
*/
public static final int PLAYER_UPDATE_PORT_ID = 6;
+ /**
+ * @hide
+ * Used to update the mute state of a player through its port id
+ */
+ public static final int PLAYER_UPDATE_MUTED = 7;
/** @hide */
@IntDef({
@@ -183,6 +188,7 @@
PLAYER_STATE_STOPPED,
PLAYER_UPDATE_DEVICE_ID,
PLAYER_UPDATE_PORT_ID,
+ PLAYER_UPDATE_MUTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerState {}
@@ -198,11 +204,55 @@
case PLAYER_STATE_STOPPED: return "PLAYER_STATE_STOPPED";
case PLAYER_UPDATE_DEVICE_ID: return "PLAYER_UPDATE_DEVICE_ID";
case PLAYER_UPDATE_PORT_ID: return "PLAYER_UPDATE_PORT_ID";
+ case PLAYER_UPDATE_MUTED: return "PLAYER_UPDATE_MUTED";
default:
return "invalid state " + state;
}
}
+ /**
+ * @hide
+ * Used to update the mute state of a player through its port ID. Must be kept in sync with
+ * frameworks/native/include/audiomanager/AudioManager.h
+ */
+ public static final String EXTRA_PLAYER_EVENT_MUTE =
+ "android.media.extra.PLAYER_EVENT_MUTE";
+
+ /**
+ * @hide
+ * Mute state used for anonymization.
+ */
+ public static final int PLAYER_MUTE_INVALID = -1;
+ /**
+ * @hide
+ * Flag used when muted by master volume.
+ */
+ public static final int PLAYER_MUTE_MASTER = (1 << 0);
+ /**
+ * @hide
+ * Flag used when muted by stream volume.
+ */
+ public static final int PLAYER_MUTE_STREAM_VOLUME = (1 << 1);
+ /**
+ * @hide
+ * Flag used when muted by stream mute.
+ */
+ public static final int PLAYER_MUTE_STREAM_MUTED = (1 << 2);
+ /**
+ * @hide
+ * Flag used when playback is restricted by AppOps manager with OP_PLAY_AUDIO.
+ */
+ public static final int PLAYER_MUTE_PLAYBACK_RESTRICTED = (1 << 3);
+
+ /** @hide */
+ @IntDef(
+ flag = true,
+ value = {PLAYER_MUTE_MASTER, PLAYER_MUTE_STREAM_VOLUME, PLAYER_MUTE_STREAM_MUTED,
+ PLAYER_MUTE_PLAYBACK_RESTRICTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PlayerMuteEvent {
+ }
+
// immutable data
private final int mPlayerIId;
@@ -220,6 +270,8 @@
private int mSessionId;
+ @PlayerMuteEvent private int mMutedState;
+
/**
* Never use without initializing parameters afterwards
*/
@@ -240,6 +292,7 @@
mPlayerType = pic.mPlayerType;
mClientUid = uid;
mClientPid = pid;
+ mMutedState = PLAYER_MUTE_INVALID;
mDeviceId = PLAYER_DEVICEID_INVALID;
mPlayerState = PLAYER_STATE_IDLE;
mPlayerAttr = pic.mAttributes;
@@ -288,6 +341,7 @@
anonymCopy.mPlayerAttr = builder.build();
anonymCopy.mDeviceId = in.mDeviceId;
// anonymized data
+ anonymCopy.mMutedState = PLAYER_MUTE_INVALID;
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -348,6 +402,14 @@
/**
* @hide
+ * @return the mute state as a combination of {@link PlayerMuteEvent} flags
+ */
+ @PlayerMuteEvent public int getMutedState() {
+ return mMutedState;
+ }
+
+ /**
+ * @hide
* Return the type of player linked to this configuration.
* <br>Note that player types not exposed in the system API will be represented as
* {@link #PLAYER_TYPE_UNKNOWN}.
@@ -426,7 +488,7 @@
/**
* @hide
- * Handle a change of audio session Id
+ * Handle a change of audio session ID
* @param sessionId the audio session ID
*/
public boolean handleSessionIdEvent(int sessionId) {
@@ -437,6 +499,18 @@
/**
* @hide
+ * Handle a change of the muted state
+ * @param mutedState the mute reason as a combination of {@link PlayerMuteEvent} flags
+ * @return true if the state changed, false otherwise
+ */
+ public boolean handleMutedEvent(@PlayerMuteEvent int mutedState) {
+ final boolean changed = mMutedState != mutedState;
+ mMutedState = mutedState;
+ return changed;
+ }
+
+ /**
+ * @hide
* Handle a player state change
* @param event
* @param deviceId active device id or {@Code PLAYER_DEVICEID_INVALID}
@@ -486,16 +560,17 @@
/**
* @hide
- * Returns true if the player is considered "active", i.e. actively playing, and thus
- * in a state that should make it considered for the list public (sanitized) active playback
- * configurations
+ * Returns true if the player is considered "active", i.e. actively playing with unmuted
+ * volume, and thus in a state that should make it considered for the list public (sanitized)
+ * active playback configurations
* @return true if active
*/
@SystemApi
public boolean isActive() {
switch (mPlayerState) {
case PLAYER_STATE_STARTED:
- return true;
+ return mMutedState == 0
+ || mMutedState == PLAYER_MUTE_INVALID; // only send true if not muted
case PLAYER_STATE_UNKNOWN:
case PLAYER_STATE_RELEASED:
case PLAYER_STATE_IDLE:
@@ -532,7 +607,7 @@
@Override
public int hashCode() {
- return Objects.hash(mPlayerIId, mDeviceId, mPlayerType, mClientUid, mClientPid,
+ return Objects.hash(mPlayerIId, mDeviceId, mMutedState, mPlayerType, mClientUid, mClientPid,
mSessionId);
}
@@ -545,6 +620,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mPlayerIId);
dest.writeInt(mDeviceId);
+ dest.writeInt(mMutedState);
dest.writeInt(mPlayerType);
dest.writeInt(mClientUid);
dest.writeInt(mClientPid);
@@ -561,6 +637,7 @@
private AudioPlaybackConfiguration(Parcel in) {
mPlayerIId = in.readInt();
mDeviceId = in.readInt();
+ mMutedState = in.readInt();
mPlayerType = in.readInt();
mClientUid = in.readInt();
mClientPid = in.readInt();
@@ -580,6 +657,7 @@
return ((mPlayerIId == that.mPlayerIId)
&& (mDeviceId == that.mDeviceId)
+ && (mMutedState == that.mMutedState)
&& (mPlayerType == that.mPlayerType)
&& (mClientUid == that.mClientUid)
&& (mClientPid == that.mClientPid))
@@ -594,7 +672,13 @@
+ " u/pid:" + mClientUid + "/" + mClientPid
+ " state:" + toLogFriendlyPlayerState(mPlayerState)
+ " attr:" + mPlayerAttr
- + " sessionId:" + mSessionId;
+ + " sessionId:" + mSessionId
+ + " mutedState:"
+ + " muteFromMasterMute=" + ((mMutedState & PLAYER_MUTE_MASTER) != 0)
+ + " muteFromStreamVolume=" + ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0)
+ + " muteFromStreamMuted=" + ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0)
+ + " muteFromPlaybackRestricted=" + ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED)
+ != 0);
}
//=====================================================================
@@ -678,6 +762,7 @@
case PLAYER_STATE_STOPPED: return "stopped";
case PLAYER_UPDATE_DEVICE_ID: return "device updated";
case PLAYER_UPDATE_PORT_ID: return "port updated";
+ case PLAYER_UPDATE_MUTED: return "muted updated";
default:
return "unknown player state - FIXME";
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 955bfcc..444366a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -65,6 +65,8 @@
private static final String TAG = "AudioSystem";
+ private static final int SOURCE_CODEC_TYPE_OPUS = 6; // TODO remove in U
+
// private constructor to prevent instantiating AudioSystem
private AudioSystem() {
throw new UnsupportedOperationException("Trying to instantiate AudioSystem");
@@ -243,6 +245,8 @@
public static final int AUDIO_FORMAT_LDAC = 0x23000000;
/** @hide */
public static final int AUDIO_FORMAT_LC3 = 0x2B000000;
+ /** @hide */
+ public static final int AUDIO_FORMAT_OPUS = 0x08000000;
/** @hide */
@@ -254,7 +258,9 @@
AUDIO_FORMAT_APTX,
AUDIO_FORMAT_APTX_HD,
AUDIO_FORMAT_LDAC,
- AUDIO_FORMAT_LC3}
+ AUDIO_FORMAT_LC3,
+ AUDIO_FORMAT_OPUS
+ }
)
@Retention(RetentionPolicy.SOURCE)
public @interface AudioFormatNativeEnumForBtCodec {}
@@ -287,6 +293,7 @@
case AUDIO_FORMAT_APTX_HD: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD;
case AUDIO_FORMAT_LDAC: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC;
case AUDIO_FORMAT_LC3: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3;
+ case AUDIO_FORMAT_OPUS: return SOURCE_CODEC_TYPE_OPUS; // TODO update in U
default:
Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat)
+ " for conversion to BT codec");
@@ -329,6 +336,8 @@
return AudioSystem.AUDIO_FORMAT_LDAC;
case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3:
return AudioSystem.AUDIO_FORMAT_LC3;
+ case SOURCE_CODEC_TYPE_OPUS: // TODO update in U
+ return AudioSystem.AUDIO_FORMAT_OPUS;
default:
Log.e(TAG, "Unknown BT codec 0x" + Integer.toHexString(btCodec)
+ " for conversion to audio format");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 92d81c6..7c9e494 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -54,6 +54,7 @@
import android.media.audiopolicy.IAudioPolicyCallback;
import android.media.projection.IMediaProjection;
import android.net.Uri;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.view.KeyEvent;
@@ -86,6 +87,8 @@
oneway void playerSessionId(in int piid, in int sessionId);
+ oneway void portEvent(in int portId, in int event, in @nullable PersistableBundle extras);
+
// Java-only methods below.
void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
@@ -98,6 +101,9 @@
void setStreamVolumeWithAttribution(int streamType, int index, int flags,
in String callingPackage, in String attributionTag);
+ void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
+ in String callingPackage, in String attributionTag);
+
oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv,
String callingPackage, String caller);
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 9bf126b..31fb8d0 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -35,7 +35,7 @@
ISessionController getController();
void setFlags(int flags);
void setActive(boolean active);
- void setMediaButtonReceiver(in PendingIntent mbr, String sessionPackageName);
+ void setMediaButtonReceiver(in PendingIntent mbr);
void setMediaButtonBroadcastReceiver(in ComponentName broadcastReceiver);
void setLaunchPendingIntent(in PendingIntent pi);
void destroySession();
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 9e265d8..1bd12af 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -286,7 +286,7 @@
@Deprecated
public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
try {
- mBinder.setMediaButtonReceiver(mbr, mContext.getPackageName());
+ mBinder.setMediaButtonReceiver(mbr);
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
}
diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h
index fbb7fb3..2b31703 100644
--- a/media/native/midi/include/amidi/AMidi.h
+++ b/media/native/midi/include/amidi/AMidi.h
@@ -66,7 +66,7 @@
*
* Introduced in API 33.
*/
-enum AMidiDevice_Protocol : int32_t {
+typedef enum AMidiDevice_Protocol : int32_t {
/**
* Constant representing a default protocol with Universal MIDI Packets (UMP).
* UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
@@ -131,7 +131,7 @@
* MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
*/
AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1
-};
+} AMidiDevice_Protocol;
/*
* Device API
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index 60f0e9e..cb74cfc 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -86,6 +86,7 @@
?audio/x-matroska mka
?audio/x-pn-realaudio ra
?audio/x-mpeg mp3
+?audio/mp3 mp3
?image/bmp bmp
?image/gif gif
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml
index a596a9a..7c3f5a5 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v31/settingslib_action_buttons.xml
@@ -28,8 +28,7 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
<View
android:id="@+id/divider1"
@@ -44,8 +43,7 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
<View
android:id="@+id/divider2"
@@ -60,8 +58,7 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
<View
android:id="@+id/divider3"
@@ -76,6 +73,5 @@
style="@style/SettingsLibActionButton"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_weight="1"
- android:hyphenationFrequency="normalFast"/>
+ android:layout_weight="1"/>
</LinearLayout>
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v33/settingslib_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v33/settingslib_action_buttons.xml
new file mode 100644
index 0000000..b4640ee
--- /dev/null
+++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v33/settingslib_action_buttons.xml
@@ -0,0 +1,85 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:paddingHorizontal="8dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/button1"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+
+ <View
+ android:id="@+id/divider1"
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="4dp"
+ android:visibility="gone"
+ android:background="?android:colorBackground" />
+
+ <Button
+ android:id="@+id/button2"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+
+ <View
+ android:id="@+id/divider2"
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="4dp"
+ android:visibility="gone"
+ android:background="?android:colorBackground" />
+
+ <Button
+ android:id="@+id/button3"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+
+ <View
+ android:id="@+id/divider3"
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="4dp"
+ android:visibility="gone"
+ android:background="?android:colorBackground" />
+
+ <Button
+ android:id="@+id/button4"
+ style="@style/SettingsLibActionButton"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:lineBreakWordStyle="phrase"
+ android:hyphenationFrequency="normalFast"/>
+</LinearLayout>
diff --git a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
index 5ce08b7..2742558 100644
--- a/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
+++ b/packages/SettingsLib/ActivityEmbedding/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.activityembedding">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="21" />
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
new file mode 100644
index 0000000..8975857
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -0,0 +1,100 @@
+<?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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/secondary_app_icon_size"
+ android:orientation="horizontal"
+ android:paddingEnd="16dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/secondary_app_icon_size"
+ android:layout_height="@dimen/secondary_app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <TextView
+ android:id="@+id/appendix"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:visibility="gone"/>
+
+ <ProgressBar
+ android:id="@android:id/progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:max="100"
+ android:visibility="gone"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|end"
+ android:minWidth="@dimen/two_target_min_width"
+ android:orientation="vertical"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app_header.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app_header.xml
new file mode 100644
index 0000000..3a219b9
--- /dev/null
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app_header.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingBottom="16dp"
+ android:paddingTop="8dp"
+ android:clickable="false">
+
+ <TextView
+ android:id="@+id/apps_top_intro_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:clickable="false"
+ android:longClickable="false"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="@style/TextAppearance.TopIntroText" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index 12db901..e65f7de 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -55,7 +55,6 @@
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<TextView
@@ -63,7 +62,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textDirection="locale"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"/>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
index 1c47f5f..244b367 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.collapsingtoolbar">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="29" />
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
index 8ebbac3..3582897 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/BasePreferencesFragment.java
@@ -20,6 +20,7 @@
import androidx.preference.PreferenceFragmentCompat;
import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 77d6583..8c8b478 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -27,6 +27,7 @@
import androidx.fragment.app.FragmentActivity;
import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 31e8cc7..f4af9c7 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -19,6 +19,7 @@
import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
import android.app.ActionBar;
+import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,6 +31,8 @@
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import com.android.settingslib.widget.R;
+
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -86,6 +89,12 @@
mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mCollapsingToolbarLayout.setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL_FAST);
+ mCollapsingToolbarLayout.setStaticLayoutBuilderConfigurer(builder ->
+ builder.setLineBreakConfig(
+ new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ .build()));
}
}
disableCollapsingToolbarLayoutScrollingBehavior();
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 1ead2f3..522de93 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -22,6 +22,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -36,7 +37,7 @@
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import com.android.settingslib.collapsingtoolbar.R;
+import com.android.settingslib.widget.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -110,6 +111,12 @@
mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mCollapsingToolbarLayout.setHyphenationFrequency(HYPHENATION_FREQUENCY_NORMAL_FAST);
+ mCollapsingToolbarLayout.setStaticLayoutBuilderConfigurer(builder ->
+ builder.setLineBreakConfig(
+ new LineBreakConfig.Builder()
+ .setLineBreakWordStyle(
+ LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+ .build()));
}
if (!TextUtils.isEmpty(mToolbarTitle)) {
mCollapsingToolbarLayout.setTitle(mToolbarTitle);
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
index 2c1fdd4..42700b3 100644
--- a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
+++ b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml
@@ -53,7 +53,6 @@
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:ellipsize="marquee" />
<com.android.settingslib.widget.LinkTextView
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v33/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v33/preference_footer.xml
new file mode 100644
index 0000000..a2f2510
--- /dev/null
+++ b/packages/SettingsLib/FooterPreference/res/layout-v33/preference_footer.xml
@@ -0,0 +1,71 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|top"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:ellipsize="marquee" />
+
+ <com.android.settingslib.widget.LinkTextView
+ android:id="@+id/settingslib_learn_more"
+ android:text="@string/settingslib_learn_more_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
+ android:clickable="true"
+ android:visibility="gone"
+ style="@style/TextAppearance.Footer.Title.SettingsLib"/>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
new file mode 100644
index 0000000..35d1323
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -0,0 +1,70 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:background="?android:attr/colorBackground"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/frame"
+ android:minHeight="@dimen/settingslib_min_switch_bar_height"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_margin="@dimen/settingslib_switchbar_margin"
+ android:paddingStart="@dimen/settingslib_switchbar_padding_left"
+ android:paddingEnd="@dimen/settingslib_switchbar_padding_right">
+
+ <TextView
+ android:id="@+id/switch_text"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/settingslib_switch_title_margin"
+ android:layout_marginVertical="@dimen/settingslib_switch_title_margin"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ style="@style/MainSwitchText.Settingslib" />
+
+ <ImageView
+ android:id="@+id/restricted_icon"
+ android:layout_width="@dimen/settingslib_restricted_icon_size"
+ android:layout_height="@dimen/settingslib_restricted_icon_size"
+ android:tint="?android:attr/colorAccent"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
+ android:src="@drawable/settingslib_ic_info"
+ android:visibility="gone" />
+
+ <Switch
+ android:id="@android:id/switch_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:focusable="false"
+ android:clickable="false"
+ android:theme="@style/Switch.SettingsLib"/>
+ </LinearLayout>
+
+</LinearLayout>
+
+
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml
new file mode 100644
index 0000000..bb8ac28
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout-v33/preference_radio.xml
@@ -0,0 +1,127 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="20dp"
+ android:gravity="center"
+ android:minWidth="56dp"
+ android:orientation="vertical"/>
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minWidth="32dp"
+ android:orientation="horizontal"
+ android:layout_marginEnd="16dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ settings:maxWidth="@dimen/secondary_app_icon_size"
+ settings:maxHeight="@dimen/secondary_app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ <LinearLayout
+ android:id="@+id/summary_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <TextView
+ android:id="@+id/appendix"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewEnd"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="1"
+ android:visibility="gone"
+ android:ellipsize="end"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/radio_extra_widget_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <View
+ android:layout_width=".75dp"
+ android:layout_height="32dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="16dp"
+ android:background="?android:attr/dividerVertical" />
+ <ImageView
+ android:id="@+id/radio_extra_widget"
+ android:layout_width="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:layout_height="fill_parent"
+ android:src="@drawable/ic_settings_accent"
+ android:contentDescription="@string/settings_label"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
index 64d100a..906ff2c 100644
--- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
@@ -66,7 +66,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<LinearLayout
@@ -81,7 +80,6 @@
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAlignment="viewStart"
- android:hyphenationFrequency="normalFast"
android:textColor="?android:attr/textColorSecondary"/>
<TextView
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
new file mode 100644
index 0000000..0b27464
--- /dev/null
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
@@ -0,0 +1,127 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingHorizontal="20dp"
+ android:gravity="center"
+ android:minWidth="56dp"
+ android:orientation="vertical"/>
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minWidth="32dp"
+ android:orientation="horizontal"
+ android:layout_marginEnd="16dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ settings:maxWidth="@dimen/secondary_app_icon_size"
+ settings:maxHeight="@dimen/secondary_app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ <LinearLayout
+ android:id="@+id/summary_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewStart"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <TextView
+ android:id="@+id/appendix"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="viewEnd"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="1"
+ android:visibility="gone"
+ android:ellipsize="end"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/selector_extra_widget_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <View
+ android:layout_width=".75dp"
+ android:layout_height="32dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="16dp"
+ android:background="?android:attr/dividerVertical" />
+ <ImageView
+ android:id="@+id/selector_extra_widget"
+ android:layout_width="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:layout_height="fill_parent"
+ android:src="@drawable/ic_settings_accent"
+ android:contentDescription="@string/settings_label"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
index 2a550ef..8bb56ff 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout/preference_selector_with_widget.xml
@@ -66,7 +66,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<LinearLayout
@@ -81,7 +80,6 @@
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAlignment="viewStart"
- android:hyphenationFrequency="normalFast"
android:textColor="?android:attr/textColorSecondary"/>
<TextView
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
index d4d466a..23aa993 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
@@ -43,7 +43,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
@@ -58,7 +57,6 @@
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10"
- android:hyphenationFrequency="normalFast"
style="@style/PreferenceSummaryTextStyle"/>
</RelativeLayout>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
new file mode 100644
index 0000000..70ce374
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
@@ -0,0 +1,80 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false"
+ android:baselineAligned="false">
+
+ <include layout="@layout/settingslib_icon_frame"/>
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignLeft="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="10"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ style="@style/PreferenceSummaryTextStyle"/>
+
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingLeft="16dp"
+ android:paddingStart="16dp"
+ android:paddingRight="0dp"
+ android:paddingEnd="0dp"
+ android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
index b6aff53..244b367 100644
--- a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
+++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.transition">
+ package="com.android.settingslib.widget">
<uses-sdk android:minSdkVersion="29" />
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
new file mode 100644
index 0000000..6046d91
--- /dev/null
+++ b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
@@ -0,0 +1,39 @@
+<?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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingBottom="16dp"
+ android:paddingTop="8dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="false"
+ android:longClickable="false"
+ android:maxLines="10"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="@style/TextAppearance.TopIntroText"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
index b2a9037..4d6e1b7 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
@@ -33,6 +33,5 @@
android:clickable="false"
android:longClickable="false"
android:maxLines="10"
- android:hyphenationFrequency="normalFast"
android:textAppearance="@style/TextAppearance.TopIntroText"/>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
index ac5807d..2c35772 100644
--- a/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v31/preference_two_target.xml
@@ -41,7 +41,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
@@ -53,7 +52,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10"/>
</RelativeLayout>
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v33/preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v33/preference_two_target.xml
new file mode 100644
index 0000000..0bca9ab
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v33/preference_two_target.xml
@@ -0,0 +1,74 @@
+<?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.
+ -->
+
+<!-- Based off preference_material_settings.xml except that ripple on only on the left side. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <include layout="@layout/settingslib_icon_frame"/>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10"/>
+
+ </RelativeLayout>
+
+ <include layout="@layout/preference_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout-v33/preference_access_point.xml b/packages/SettingsLib/res/layout-v33/preference_access_point.xml
new file mode 100644
index 0000000..81bfeffd
--- /dev/null
+++ b/packages/SettingsLib/res/layout-v33/preference_access_point.xml
@@ -0,0 +1,110 @@
+<?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.
+ -->
+
+<!-- Based off preference_two_target.xml with Material ripple moved to parent for full ripple. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="48dp"
+ android:orientation="horizontal"
+ android:clipToPadding="false"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ settings:maxWidth="48dp"
+ settings:maxHeight="48dp" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+ </RelativeLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/preference_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+ <ImageButton
+ android:id="@+id/icon_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:minHeight="@dimen/min_tap_target_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone">
+ </ImageButton>
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout-v33/preference_checkable_two_target.xml b/packages/SettingsLib/res/layout-v33/preference_checkable_two_target.xml
new file mode 100644
index 0000000..7ad018c
--- /dev/null
+++ b/packages/SettingsLib/res/layout-v33/preference_checkable_two_target.xml
@@ -0,0 +1,97 @@
+<?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.
+ -->
+
+<!-- Based off preference_material_settings.xml except that ripple on only on the left side. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="@android:color/transparent"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="start|center_vertical"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/checkbox_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:orientation="horizontal"
+ android:clipToPadding="false"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <include layout="@layout/preference_widget_checkbox" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+ </RelativeLayout>
+
+ </LinearLayout>
+
+ <include layout="@layout/preference_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
new file mode 100644
index 0000000..31e9696
--- /dev/null
+++ b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
@@ -0,0 +1,97 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingEnd="12dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <com.android.internal.widget.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="48dp"
+ android:maxHeight="48dp" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+ <TextView
+ android:id="@+id/additional_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/summary"
+ android:layout_alignStart="@android:id/summary"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="10"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:visibility="gone" />
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingStart="16dp"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/preference_access_point.xml b/packages/SettingsLib/res/layout/preference_access_point.xml
index 4ad9d80..802d604 100644
--- a/packages/SettingsLib/res/layout/preference_access_point.xml
+++ b/packages/SettingsLib/res/layout/preference_access_point.xml
@@ -65,7 +65,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
@@ -77,7 +76,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10" />
</RelativeLayout>
diff --git a/packages/SettingsLib/res/layout/preference_checkable_two_target.xml b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
index cbe49cd..f512f9b 100644
--- a/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_checkable_two_target.xml
@@ -62,7 +62,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
@@ -74,7 +73,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10" />
</RelativeLayout>
diff --git a/packages/SettingsLib/res/layout/restricted_switch_preference.xml b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
index edea144..169ae97 100644
--- a/packages/SettingsLib/res/layout/restricted_switch_preference.xml
+++ b/packages/SettingsLib/res/layout/restricted_switch_preference.xml
@@ -52,7 +52,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
- android:hyphenationFrequency="normalFast"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
@@ -63,7 +62,6 @@
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:hyphenationFrequency="normalFast"
android:maxLines="10" />
<TextView android:id="@+id/additional_summary"
@@ -74,7 +72,6 @@
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10"
- android:hyphenationFrequency="normalFast"
android:visibility="gone" />
</RelativeLayout>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 29a1831..663e8e4 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -154,6 +154,8 @@
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
<item>LDAC</item>
+ <item>LC3</item>
+ <item>Opus</item>
</string-array>
<!-- Values for Bluetooth Audio Codec selection preference. -->
@@ -164,6 +166,8 @@
<item>2</item>
<item>3</item>
<item>4</item>
+ <item>5</item>
+ <item>6</item>
</string-array>
<!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
@@ -174,6 +178,8 @@
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
<item>LDAC</item>
+ <item>LC3</item>
+ <item>Opus</item>
</string-array>
<!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f1745ec..a79cbb6 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1608,6 +1608,8 @@
<string name="dream_complication_title_aqi">Air Quality</string>
<!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] -->
<string name="dream_complication_title_cast_info">Cast Info</string>
+ <!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] -->
+ <string name="dream_complication_title_home_controls">Home Controls</string>
<!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] -->
<string name="avatar_picker_title">Choose a profile picture</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index df19c67..1940986 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -43,6 +43,8 @@
public class A2dpProfile implements LocalBluetoothProfile {
private static final String TAG = "A2dpProfile";
+ private static final int SOURCE_CODEC_TYPE_OPUS = 6; // TODO remove in U
+
private Context mContext;
private BluetoothA2dp mService;
@@ -328,6 +330,12 @@
case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
index = 5;
break;
+ case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3:
+ index = 6;
+ break;
+ case SOURCE_CODEC_TYPE_OPUS: // TODO update in U
+ index = 7;
+ break;
}
if (index < 0) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bf69757..01d581e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -303,6 +303,18 @@
}
public BluetoothLeBroadcastMetadata getLatestBluetoothLeBroadcastMetadata() {
+ if (mService == null) {
+ Log.d(TAG, "The BluetoothLeBroadcast is null");
+ return null;
+ }
+ if (mBluetoothLeBroadcastMetadata == null) {
+ final List<BluetoothLeBroadcastMetadata> metadataList =
+ mService.getAllBroadcastMetadata();
+ mBluetoothLeBroadcastMetadata = metadataList.stream()
+ .filter(i -> i.getBroadcastId() == mBroadcastId)
+ .findFirst()
+ .orElse(null);
+ }
return mBluetoothLeBroadcastMetadata;
}
@@ -372,7 +384,12 @@
}
public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
- return new LocalBluetoothLeBroadcastMetadata(mBluetoothLeBroadcastMetadata);
+ final BluetoothLeBroadcastMetadata metadata = getLatestBluetoothLeBroadcastMetadata();
+ if (metadata == null) {
+ Log.d(TAG, "The BluetoothLeBroadcastMetadata is null.");
+ return null;
+ }
+ return new LocalBluetoothLeBroadcastMetadata(metadata);
}
public boolean isProfileReady() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 01d0cc4..a46e232 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -91,7 +91,8 @@
COMPLICATION_TYPE_DATE,
COMPLICATION_TYPE_WEATHER,
COMPLICATION_TYPE_AIR_QUALITY,
- COMPLICATION_TYPE_CAST_INFO
+ COMPLICATION_TYPE_CAST_INFO,
+ COMPLICATION_TYPE_HOME_CONTROLS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComplicationType {}
@@ -101,6 +102,7 @@
public static final int COMPLICATION_TYPE_WEATHER = 3;
public static final int COMPLICATION_TYPE_AIR_QUALITY = 4;
public static final int COMPLICATION_TYPE_CAST_INFO = 5;
+ public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
private final Context mContext;
private final IDreamManager mDreamManager;
@@ -346,6 +348,9 @@
case COMPLICATION_TYPE_CAST_INFO:
res = R.string.dream_complication_title_cast_info;
break;
+ case COMPLICATION_TYPE_HOME_CONTROLS:
+ res = R.string.dream_complication_title_home_controls;
+ break;
default:
return null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 281501e..f4355c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -84,7 +84,8 @@
private InfoMediaManager mInfoMediaManager;
private String mPackageName;
private MediaDevice mOnTransferBluetoothDevice;
- private AudioManager mAudioManager;
+ @VisibleForTesting
+ AudioManager mAudioManager;
@VisibleForTesting
List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 8e850b2..24bb1bc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static com.google.common.truth.Truth.assertThat;
@@ -29,10 +28,12 @@
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
@@ -71,6 +72,7 @@
private static final String TEST_CURRENT_DEVICE_ID = "currentDevice_id";
private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
private static final String TEST_SESSION_ID = "session_id";
+ private static final String TEST_ADDRESS = "00:01:02:03:04:05";
@Mock
private InfoMediaManager mInfoMediaManager;
@@ -90,6 +92,8 @@
private MediaRoute2Info mRouteInfo1;
@Mock
private MediaRoute2Info mRouteInfo2;
+ @Mock
+ private AudioManager mAudioManager;
private Context mContext;
private LocalMediaManager mLocalMediaManager;
@@ -118,6 +122,7 @@
TEST_PACKAGE_NAME);
mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
mInfoMediaManager, "com.test.packagename");
+ mLocalMediaManager.mAudioManager = mAudioManager;
}
@Test
@@ -551,16 +556,12 @@
}
@Test
- public void onDeviceListAdded_haveDisconnectedDevice_addDisconnectedDevice() {
+ public void onDeviceListAdded_haveMutingExpectedDevice_addMutingExpectedDevice() {
final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
- final MediaDevice device2 = mock(MediaDevice.class);
- final MediaDevice device3 = mock(MediaDevice.class);
mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
devices.add(device1);
- devices.add(device2);
- mLocalMediaManager.mMediaDevices.add(device3);
- mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+ devices.add(mLocalMediaManager.mPhoneDevice);
final List<LocalBluetoothProfile> profiles = new ArrayList<>();
final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
@@ -573,22 +574,25 @@
bluetoothDevices.add(bluetoothDevice);
mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
+ AudioDeviceAttributes audioDeviceAttributes = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, TEST_ADDRESS);
+
+ when(mAudioManager.getMutingExpectedDevice()).thenReturn(audioDeviceAttributes);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
+ when(cachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice);
when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>());
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
- when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(0);
mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
@@ -634,68 +638,6 @@
}
@Test
- public void onDeviceListAdded_haveDisconnectedDevice_list5DisconnectedDevice() {
- final List<MediaDevice> devices = new ArrayList<>();
- final MediaDevice device1 = mock(MediaDevice.class);
- final MediaDevice device2 = mock(MediaDevice.class);
- final MediaDevice device3 = mock(MediaDevice.class);
- mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
- devices.add(device1);
- devices.add(device2);
- mLocalMediaManager.mMediaDevices.add(device3);
- mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
-
- final List<LocalBluetoothProfile> profiles = new ArrayList<>();
- final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
- profiles.add(a2dpProfile);
-
- final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
- final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice3 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice4 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice5 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice6 = mock(BluetoothDevice.class);
- final BluetoothClass bluetoothClass = mock(BluetoothClass.class);
- final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
- final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class);
- bluetoothDevices.add(bluetoothDevice);
- bluetoothDevices.add(bluetoothDevice2);
- bluetoothDevices.add(bluetoothDevice3);
- bluetoothDevices.add(bluetoothDevice4);
- bluetoothDevices.add(bluetoothDevice5);
- bluetoothDevices.add(bluetoothDevice6);
- mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
-
- when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
- when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice2)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice3)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice4)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice5)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice6)).thenReturn(cachedDevice);
- when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(cachedDevice.isConnected()).thenReturn(false);
- when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
- when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
- when(bluetoothDevice.getBluetoothClass()).thenReturn(bluetoothClass);
- when(bluetoothClass.getDeviceClass()).thenReturn(AUDIO_VIDEO_HEADPHONES);
-
- when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
- when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
- when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
- when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
-
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
- mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
-
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(7);
- verify(mCallback).onDeviceListUpdate(any());
- }
-
- @Test
public void onDeviceListAdded_bluetoothAdapterIsNull_noDisconnectedDeviceAdded() {
final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 5088533..6aa08f2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -875,16 +875,16 @@
String[] whitelist;
Map<String, Validator> validators = null;
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
- whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
+ whitelist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP,
Settings.Secure.LEGACY_RESTORE_SETTINGS,
DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
validators = SecureSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.System.CONTENT_URI)) {
- whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
+ whitelist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP,
Settings.System.LEGACY_RESTORE_SETTINGS);
validators = SystemSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
- whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
+ whitelist = ArrayUtils.concat(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
Settings.Global.LEGACY_RESTORE_SETTINGS);
validators = GlobalSettingsValidators.VALIDATORS;
} else {
@@ -1076,7 +1076,9 @@
SoftApConfiguration storedConfig = mWifiManager.getSoftApConfiguration();
if (isNeedToNotifyUserConfigurationHasChanged(configInCloud, storedConfig)) {
- Log.d(TAG, "restored ap configuration requires a conversion, notify the user");
+ Log.d(TAG, "restored ap configuration requires a conversion, notify the user"
+ + ", configInCloud is " + configInCloud + " but storedConfig is "
+ + storedConfig);
WifiSoftApConfigChangedNotifier.notifyUserOfConfigConversion(this);
}
}
@@ -1115,9 +1117,6 @@
== storedConfig.getBridgedModeOpportunisticShutdownTimeoutMillis()
&& Objects.equals(configInCloud.getVendorElements(),
storedConfig.getVendorElements())
- && (configInCloud.getPersistentRandomizedMacAddress() != null
- ? Objects.equals(configInCloud.getPersistentRandomizedMacAddress(),
- storedConfig.getPersistentRandomizedMacAddress()) : true)
&& Arrays.equals(configInCloud.getAllowedAcsChannels(
SoftApConfiguration.BAND_2GHZ),
storedConfig.getAllowedAcsChannels(SoftApConfiguration.BAND_2GHZ))
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4aa4006..634df39 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -146,25 +146,10 @@
filegroup {
name: "SystemUI-tests-utils",
srcs: [
- "tests/src/com/android/systemui/SysuiBaseFragmentTest.java",
- "tests/src/com/android/systemui/SysuiTestCase.java",
- "tests/src/com/android/systemui/TestableDependency.java",
- "tests/src/com/android/systemui/classifier/FalsingManagerFake.java",
- "tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java",
- "tests/src/com/android/systemui/statusbar/RankingBuilder.java",
- "tests/src/com/android/systemui/statusbar/SbnBuilder.java",
- "tests/src/com/android/systemui/SysuiTestableContext.java",
- "tests/src/com/android/systemui/util/**/*Fake.java",
- "tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java",
- "tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java",
- "tests/src/com/android/systemui/**/Fake*.java",
- "tests/src/com/android/systemui/**/Fake*.kt",
+ "tests/utils/src/**/*.java",
+ "tests/utils/src/**/*.kt",
],
- exclude_srcs: [
- "tests/src/com/android/systemui/**/*Test.java",
- "tests/src/com/android/systemui/**/*Test.kt",
- ],
- path: "tests/src",
+ path: "tests/utils/src",
}
java_library {
@@ -172,8 +157,8 @@
srcs: [
"src/com/android/systemui/util/concurrency/DelayableExecutor.java",
"src/com/android/systemui/util/time/SystemClock.java",
- "tests/src/com/android/systemui/util/concurrency/FakeExecutor.java",
- "tests/src/com/android/systemui/util/time/FakeSystemClock.java",
+ "tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java",
+ "tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java",
],
jarjar_rules: ":jarjar-rules-shared",
}
@@ -196,6 +181,7 @@
"src/**/*.java",
"src/**/I*.aidl",
":ReleaseJavaFiles",
+ ":SystemUI-tests-utils",
],
static_libs: [
"WifiTrackerLib",
@@ -226,6 +212,7 @@
"androidx.exifinterface_exifinterface",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
+ "kotlinx_coroutines_test",
"iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
diff --git a/packages/SystemUI/docs/device-entry/keyguard.md b/packages/SystemUI/docs/device-entry/keyguard.md
index 337f73b..8634c95 100644
--- a/packages/SystemUI/docs/device-entry/keyguard.md
+++ b/packages/SystemUI/docs/device-entry/keyguard.md
@@ -30,6 +30,10 @@
### How the device locks
+### Quick Affordances
+
+These are interactive UI elements that appear on the lockscreen when the device is locked. They allow the user to perform quick actions without unlocking their device. To learn more about them, please see [this dedicated document](quickaffordance.md)
+
## Debugging Tips
Enable verbose keyguard logs that will print to logcat. Should only be used temporarily for debugging. See [KeyguardConstants][5].
```
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
new file mode 100644
index 0000000..a96e5339
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -0,0 +1,25 @@
+# Keyguard Quick Affordances
+These are interactive UI elements that appear at the bottom of the lockscreen when the device is
+locked. They allow the user to perform quick actions without unlocking their device. For example:
+opening an screen that lets them control the smart devices in their home, access their touch-to-pay
+credit card, etc.
+
+## Adding a new Quick Affordance
+### Step 1: create a new quick affordance config
+* Create a new class under the [systemui/keyguard/data/quickaffordance](../../src/com/android/systemui/keyguard/data/quickaffordance) directory
+* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+ * The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
+ * It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
+ * When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
+* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/data/quickaffordance)
+
+### Step 2: choose a position and priority
+* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceConfigs](../../src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt)
+* Place the new class in one of the available positions in the `configsByPosition` property, note:
+ * In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
+ * Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
+
+### Step 3: manually verify the new quick affordance
+* Build and launch SysUI on a device
+* Verify that the quick affordance button for the new implementation is correctly visible and clicking it does the right thing
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 84ebe77..a3b4b38 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -14,6 +14,11 @@
-keep class * extends com.android.systemui.CoreStartable
-keep class * implements com.android.systemui.CoreStartable$Injector
+# Needed for builds to properly initialize KeyFrames from xml scene
+-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
+ public <init>();
+}
+
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 7ce6f0e..712f657 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -48,10 +48,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintTop_toTopOf="parent"
androidprv:layout_constraintEnd_toEndOf="parent"
androidprv:layout_constraintStart_toStartOf="parent"
androidprv:layout_constraintBottom_toTopOf="@id/key1"
- androidprv:layout_constraintVertical_bias="0.0">
+ androidprv:layout_constraintVertical_bias="1.0">
<com.android.keyguard.PasswordTextView
android:id="@+id/pinEntry"
diff --git a/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..3dd997f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..80aba01
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..b3f89ed
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png b/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png
new file mode 100644
index 0000000..efa2cb9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/textfield_default_filled.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/edit_text_filled.xml b/packages/SystemUI/res/drawable/edit_text_filled.xml
new file mode 100644
index 0000000..cca34d4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/edit_text_filled.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="4dp"
+ android:insetRight="4dp"
+ android:insetTop="10dp"
+ android:insetBottom="10dp">
+ <selector>
+ <item android:state_enabled="false">
+ <nine-patch android:src="@drawable/textfield_default_filled"
+ android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item android:state_pressed="false" android:state_focused="false">
+ <nine-patch android:src="@drawable/textfield_default_filled"
+ android:tint="?android:attr/colorControlNormal" />
+ </item>
+ <item>
+ <nine-patch android:src="@drawable/textfield_default_filled"
+ android:tint="?android:attr/colorControlActivated" />
+ </item>
+ </selector>
+</inset>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index da76c8d..bc8e540 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -18,44 +18,70 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
+ android:orientation="horizontal"
android:elevation="@dimen/biometric_dialog_elevation">
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ <RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <ImageView
+ android:id="@+id/icon"
+ style="@style/TextAppearance.AuthNonBioCredential.Icon"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthNonBioCredential.Title"
+ android:layout_below="@id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:minHeight="48dp"
- android:gravity="center"
- android:inputType="textPassword"
- android:maxLength="500"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ android:layout_below="@id/title"
+ android:layout_alignParentLeft="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/error"
- android:layout_width="match_parent"
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthNonBioCredential.Description"
+ android:layout_below="@id/subtitle"
+ android:layout_alignParentLeft="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/auth_credential_input"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ android:orientation="vertical">
+
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthNonBioCredential.Error"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 0ff1db2..75a80bc 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -22,68 +22,63 @@
android:orientation="vertical">
<RelativeLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/icon"
+ style="@style/TextAppearance.AuthNonBioCredential.Icon"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthNonBioCredential.Title"
+ android:layout_below="@id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
+ android:layout_below="@id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthNonBioCredential.Description"
+ android:layout_below="@id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/auth_credential_input"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:orientation="vertical">
- <LinearLayout
- android:id="@+id/auth_credential_header"
- style="@style/AuthCredentialHeaderStyle"
- android:layout_width="match_parent"
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ android:layout_width="208dp"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true">
+ android:layout_gravity="center_horizontal"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
- <ImageView
- android:id="@+id/icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:contentDescription="@null" />
-
- <TextView
- android:id="@+id/title"
- style="@style/TextAppearance.AuthNonBioCredential.Title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/subtitle"
- style="@style/TextAppearance.AuthNonBioCredential.Subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <TextView
- android:id="@+id/description"
- style="@style/TextAppearance.AuthNonBioCredential.Description"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- </LinearLayout>
-
- <LinearLayout
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthNonBioCredential.Error"
+ android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_alignParentBottom="true">
+ android:layout_height="wrap_content" />
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- android:inputType="textPassword"
- android:minHeight="48dp" />
-
- <TextView
- android:id="@+id/error"
- style="@style/TextAppearance.AuthNonBioCredential.Error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
- </LinearLayout>
-
- </RelativeLayout>
+ </LinearLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
new file mode 100644
index 0000000..4f0a78e
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/home_controls_chip"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|start"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:src="@drawable/controls_icon"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:contentDescription="@string/quick_controls_title" />
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 12dfa10..0ca19d9 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -60,6 +60,30 @@
</LinearLayout>
<ImageView
+ android:id="@+id/start_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|start"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/end_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|end"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:visibility="gone" />
+
+ <ImageView
android:id="@+id/wallet_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index f57d65a..ab38dd2 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -148,6 +148,20 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
+
+ <!-- Temporary entrypoint for the partial screensharing used for teamfooding -->
+ <!-- TODO(b/236838395) remove this and use redesigned dialog -->
+ <TextView
+ android:id="@+id/button_app"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:layout_gravity="end"
+ android:layout_marginEnd="8dp"
+ android:text="App"
+ style="@style/Widget.Dialog.Button.BorderButton" />
+
<TextView
android:id="@+id/button_start"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml
index 8919198..ac9a947 100644
--- a/packages/SystemUI/res/values-land/styles.xml
+++ b/packages/SystemUI/res/values-land/styles.xml
@@ -18,4 +18,13 @@
<style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer">
<item name="android:layout_width">360dp</item>
</style>
+
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">48dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingTop">48dp</item>
+ <item name="android:paddingBottom">10dp</item>
+ <item name="android:gravity">top|center_horizontal</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 36cc0ad..c0071cb 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -40,4 +40,6 @@
<bool name="config_use_large_screen_shade_header">true</bool>
+ <!-- Whether to show the side fps hint while on bouncer -->
+ <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 82a3b58..ec22c60 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -548,6 +548,9 @@
<!-- Package name of the preferred system app to perform eSOS action -->
<string name="config_preferredEmergencySosPackage" translatable="false"></string>
+ <!-- Whether to show the side fps hint while on bouncer -->
+ <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ddbe6d6..60932a7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -940,6 +940,9 @@
<!-- Y translation for credential contents when animating in -->
<dimen name="biometric_dialog_credential_translation_offset">60dp</dimen>
+ <!-- Biometric Auth Credential values -->
+ <dimen name="biometric_auth_icon_size">48dp</dimen>
+
<!-- Starting text size in sp of batteryLevel for wireless charging animation -->
<item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
0
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2227978..f7acf06 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -198,6 +198,11 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
+ <style name="TextAppearance.AuthNonBioCredential.Icon">
+ <item name="android:layout_width">@dimen/biometric_auth_icon_size</item>
+ <item name="android:layout_height">@dimen/biometric_auth_icon_size</item>
+ </style>
+
<style name="TextAppearance.AuthNonBioCredential.Title">
<item name="android:fontFamily">google-sans</item>
<item name="android:layout_marginTop">20dp</item>
@@ -227,14 +232,16 @@
<style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:gravity">center</item>
+ <item name="android:paddingTop">28dp</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">?android:attr/colorForeground</item>
<item name="android:textSize">24sp</item>
+ <item name="android:background">@drawable/edit_text_filled</item>
</style>
<style name="AuthCredentialHeaderStyle">
<item name="android:paddingStart">48dp</item>
- <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingEnd">48dp</item>
<item name="android:paddingTop">28dp</item>
<item name="android:paddingBottom">20dp</item>
<item name="android:orientation">vertical</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 916526d..ee30972 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -19,7 +19,6 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static android.app.ActivityTaskManager.getService;
import android.annotation.NonNull;
@@ -27,7 +26,6 @@
import android.app.Activity;
import android.app.ActivityClient;
import android.app.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -337,7 +335,8 @@
* Shows a voice session identified by {@code token}
* @return true if the session was shown, false otherwise
*/
- public boolean showVoiceSession(IBinder token, Bundle args, int flags) {
+ public boolean showVoiceSession(@NonNull IBinder token, @NonNull Bundle args, int flags,
+ @Nullable String attributionTag) {
IVoiceInteractionManagerService service = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
if (service == null) {
@@ -346,7 +345,7 @@
args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime());
try {
- return service.showSessionFromSession(token, args, flags);
+ return service.showSessionFromSession(token, args, flags, attributionTag);
} catch (RemoteException e) {
return false;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 249133a..ef9e095 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -79,6 +79,8 @@
// Fields used only to unrap into RemoteAnimationTarget
private final Rect startBounds;
+ public final boolean willShowImeOnTarget;
+
public RemoteAnimationTargetCompat(RemoteAnimationTarget app) {
taskId = app.taskId;
mode = app.mode;
@@ -102,6 +104,7 @@
windowType = app.windowType;
windowConfiguration = app.windowConfiguration;
startBounds = app.startBounds;
+ willShowImeOnTarget = app.willShowImeOnTarget;
}
private static int newModeToLegacyMode(int newMode) {
@@ -118,14 +121,15 @@
}
public RemoteAnimationTarget unwrap() {
- return new RemoteAnimationTarget(
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId, mode, leash, isTranslucent, clipRect, contentInsets,
prefixOrderIndex, position, localBounds, screenSpaceBounds, windowConfiguration,
isNotInRecents, mStartLeash, startBounds, taskInfo, allowEnterPip, windowType
);
+ target.setWillShowImeOnTarget(willShowImeOnTarget);
+ return target;
}
-
/**
* Almost a copy of Transitions#setupStartState.
* TODO: remove when there is proper cross-process transaction sync.
@@ -205,8 +209,7 @@
// TODO: once we can properly sync transactions across process, then get rid of this leash.
leash = createLeash(info, change, order, t);
- isTranslucent = (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0
- || (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0;
+ isTranslucent = (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0;
clipRect = null;
position = null;
localBounds = new Rect(change.getEndAbsBounds());
@@ -235,6 +238,7 @@
? change.getTaskInfo().configuration.windowConfiguration
: new WindowConfiguration();
startBounds = change.getStartAbsBounds();
+ willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;
}
public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 609846e..7c1ef8c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -230,6 +230,7 @@
private IBinder mTransition = null;
private boolean mKeyguardLocked = false;
private RemoteAnimationTargetCompat[] mAppearedTargets;
+ private boolean mWillFinishToHome = false;
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
IRemoteTransitionFinishedCallback finishCB,
@@ -392,7 +393,7 @@
if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
else wct.restoreTransientOrder(mRecentsTask);
}
- if (!toHome && mPausingTasks != null && mOpeningLeashes == null) {
+ if (!toHome && !mWillFinishToHome && mPausingTasks != null && mOpeningLeashes == null) {
// The gesture went back to opening the app rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
// re-showing it's task).
@@ -476,6 +477,7 @@
}
@Override public void setWillFinishToHome(boolean willFinishToHome) {
+ mWillFinishToHome = willFinishToHome;
if (mWrapped != null) mWrapped.setWillFinishToHome(willFinishToHome);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index f289105..c5190e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -35,7 +35,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.dagger.KeyguardBouncerScope;
-import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.EmergencyDialerConstants;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5ee659b..0b3fe46 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -343,7 +343,8 @@
if (!mSidefpsController.isPresent()) {
return;
}
- if (mBouncerVisible && mView.isSidedSecurityMode()
+ if (mBouncerVisible
+ && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
&& mUpdateMonitor.isFingerprintDetectionRunning()
&& !mUpdateMonitor.userNeedsStrongAuth()) {
mSidefpsController.get().show();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 758ec54..489b4be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -65,7 +65,6 @@
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.nfc.NfcAdapter;
-import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IRemoteCallback;
@@ -95,6 +94,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.Dumpable;
@@ -140,12 +140,6 @@
public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable {
private static final String TAG = "KeyguardUpdateMonitor";
- private static final boolean DEBUG = KeyguardConstants.DEBUG;
- private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
- private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_ACTIVE_UNLOCK = Build.IS_DEBUGGABLE;
- private static final boolean DEBUG_SPEW = false;
private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
// Callback messages
@@ -225,6 +219,7 @@
"com.android.settings", "com.android.settings.FallbackHome");
private final Context mContext;
+ private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsPrimaryUser;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
@@ -321,17 +316,9 @@
private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms
private static final int HAL_ERROR_RETRY_MAX = 20;
- private final Runnable mFpCancelNotReceived = () -> {
- Log.e(TAG, "Fp cancellation not received, transitioning to STOPPED");
- mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
- };
+ private final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
- private final Runnable mFaceCancelNotReceived = () -> {
- Log.e(TAG, "Face cancellation not received, transitioning to STOPPED");
- mFaceRunningState = BIOMETRIC_STATE_STOPPED;
- updateFaceListeningState(BIOMETRIC_ACTION_STOP);
- };
+ private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
private final Handler mHandler;
@@ -454,17 +441,15 @@
private void handleSimSubscriptionInfoChanged() {
Assert.isMainThread();
- if (DEBUG_SIM_STATES) {
- Log.v(TAG, "onSubscriptionInfoChanged()");
- List<SubscriptionInfo> sil = mSubscriptionManager
- .getCompleteActiveSubscriptionInfoList();
- if (sil != null) {
- for (SubscriptionInfo subInfo : sil) {
- Log.v(TAG, "SubInfo:" + subInfo);
- }
- } else {
- Log.v(TAG, "onSubscriptionInfoChanged: list is null");
+ mLogger.v("onSubscriptionInfoChanged()");
+ List<SubscriptionInfo> sil = mSubscriptionManager
+ .getCompleteActiveSubscriptionInfoList();
+ if (sil != null) {
+ for (SubscriptionInfo subInfo : sil) {
+ mLogger.logSubInfo(subInfo);
}
+ } else {
+ mLogger.v("onSubscriptionInfoChanged: list is null");
}
List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
@@ -488,8 +473,7 @@
while (iter.hasNext()) {
Map.Entry<Integer, SimData> simData = iter.next();
if (!activeSubIds.contains(simData.getKey())) {
- Log.i(TAG, "Previously active sub id " + simData.getKey() + " is now invalid, "
- + "will remove");
+ mLogger.logInvalidSubId(simData.getKey());
iter.remove();
SimData data = simData.getValue();
@@ -674,7 +658,7 @@
try {
mDreamManager.awaken();
} catch (RemoteException e) {
- Log.e(TAG, "Unable to awaken from dream");
+ mLogger.logException(e, "Unable to awaken from dream");
}
}
}
@@ -758,15 +742,15 @@
try {
userId = ActivityManager.getService().getCurrentUser().id;
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get current user id: ", e);
+ mLogger.logException(e, "Failed to get current user id");
return;
}
if (userId != authUserId) {
- Log.d(TAG, "Fingerprint authenticated for wrong user: " + authUserId);
+ mLogger.logFingerprintAuthForWrongUser(authUserId);
return;
}
if (isFingerprintDisabled(userId)) {
- Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
+ mLogger.logFingerprintDisabledForUser(userId);
return;
}
onFingerprintAuthenticated(userId, isStrongBiometric);
@@ -789,8 +773,7 @@
private Runnable mRetryFingerprintAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG,
- "Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount);
+ mLogger.logRetryAfterFpHwUnavailable(mHardwareFingerprintUnavailableRetryCount);
if (mFpm.isHardwareDetected()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
@@ -800,6 +783,12 @@
}
};
+ private void onFingerprintCancelNotReceived() {
+ mLogger.e("Fp cancellation not received, transitioning to STOPPED");
+ mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+ }
+
private void handleFingerprintError(int msgId, String errString) {
Assert.isMainThread();
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
@@ -827,7 +816,7 @@
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
- Log.d(TAG, "Fingerprint locked out - requiring strong auth");
+ mLogger.d("Fingerprint locked out - requiring strong auth");
mLockPatternUtils.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
}
@@ -855,7 +844,7 @@
}
private void handleFingerprintLockoutReset(@LockoutMode int mode) {
- Log.d(TAG, "handleFingerprintLockoutReset: " + mode);
+ mLogger.logFingerprintLockoutReset(mode);
final boolean wasLockout = mFingerprintLockedOut;
final boolean wasLockoutPermanent = mFingerprintLockedOutPermanent;
mFingerprintLockedOut = (mode == BIOMETRIC_LOCKOUT_TIMED)
@@ -886,7 +875,7 @@
boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
- Log.d(TAG, "fingerprintRunningState: " + mFingerprintRunningState);
+ mLogger.logFingerprintRunningState(mFingerprintRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
// has changed.
@@ -953,7 +942,7 @@
private void handleFaceAcquired(int acquireInfo) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face acquired acquireInfo=" + acquireInfo);
+ mLogger.logFaceAcquired(acquireInfo);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -966,25 +955,25 @@
Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
try {
if (mGoingToSleep) {
- Log.d(TAG, "Aborted successful auth because device is going to sleep.");
+ mLogger.d("Aborted successful auth because device is going to sleep.");
return;
}
final int userId;
try {
userId = ActivityManager.getService().getCurrentUser().id;
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get current user id: ", e);
+ mLogger.logException(e, "Failed to get current user id");
return;
}
if (userId != authUserId) {
- Log.d(TAG, "Face authenticated for wrong user: " + authUserId);
+ mLogger.logFaceAuthForWrongUser(authUserId);
return;
}
if (isFaceDisabled(userId)) {
- Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);
+ mLogger.logFaceAuthDisabledForUser(userId);
return;
}
- if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);
+ mLogger.logFaceAuthSuccess(userId);
onFaceAuthenticated(userId, isStrongBiometric);
} finally {
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -994,7 +983,7 @@
private void handleFaceHelp(int msgId, String helpString) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face help received: " + helpString);
+ mLogger.logFaceAuthHelpMsg(msgId, helpString);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1006,15 +995,21 @@
private Runnable mRetryFaceAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG, "Retrying face after HW unavailable, attempt " +
- mHardwareFaceUnavailableRetryCount);
+ mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
}
};
- private void handleFaceError(int msgId, String errString) {
+ private void onFaceCancelNotReceived() {
+ mLogger.e("Face cancellation not received, transitioning to STOPPED");
+ mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+ }
+
+ private void handleFaceError(int msgId, final String originalErrMsg) {
Assert.isMainThread();
- if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString + " msgId=" + msgId);
+ String errString = originalErrMsg;
+ mLogger.logFaceAuthError(msgId, originalErrMsg);
if (mHandler.hasCallbacks(mFaceCancelNotReceived)) {
mHandler.removeCallbacks(mFaceCancelNotReceived);
}
@@ -1071,7 +1066,7 @@
}
private void handleFaceLockoutReset(@LockoutMode int mode) {
- Log.d(TAG, "handleFaceLockoutReset: " + mode);
+ mLogger.logFaceLockoutReset(mode);
final boolean wasLockoutPermanent = mFaceLockedOutPermanent;
mFaceLockedOutPermanent = (mode == BIOMETRIC_LOCKOUT_PERMANENT);
final boolean changed = (mFaceLockedOutPermanent != wasLockoutPermanent);
@@ -1089,7 +1084,7 @@
boolean wasRunning = mFaceRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = faceRunningState == BIOMETRIC_STATE_RUNNING;
mFaceRunningState = faceRunningState;
- Log.d(TAG, "faceRunningState: " + mFaceRunningState);
+ mLogger.logFaceRunningState(mFaceRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state or about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
// has changed.
@@ -1205,8 +1200,7 @@
mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
UserHandle.of(userId));
if (supervisorComponent == null) {
- Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User "
- + userId);
+ mLogger.logMissingSupervisorAppError(userId);
} else {
Intent intent =
new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
@@ -1338,7 +1332,7 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "received broadcast " + action);
+ mLogger.logBroadcastReceived(action);
if (Intent.ACTION_TIME_TICK.equals(action)
|| Intent.ACTION_TIME_CHANGED.equals(action)) {
@@ -1365,12 +1359,10 @@
}
return;
}
- if (DEBUG_SIM_STATES) {
- Log.v(TAG, "action " + action
- + " state: " + intent.getStringExtra(
- Intent.EXTRA_SIM_STATE)
- + " slotId: " + args.slotId + " subid: " + args.subId);
- }
+ mLogger.logSimStateFromIntent(action,
+ intent.getStringExtra(Intent.EXTRA_SIM_STATE),
+ args.slotId,
+ args.subId);
mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
.sendToTarget();
} else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
@@ -1382,10 +1374,7 @@
ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (DEBUG) {
- Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
- + subId);
- }
+ mLogger.logServiceStateIntent(action, serviceState, subId);
mHandler.sendMessage(
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
} else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
@@ -1505,7 +1494,7 @@
*/
@Override
public void onUdfpsPointerDown(int sensorId) {
- Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
+ mLogger.logUdfpsPointerDown(sensorId);
requestFaceAuth(true);
}
@@ -1514,7 +1503,7 @@
*/
@Override
public void onUdfpsPointerUp(int sensorId) {
- Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId);
+ mLogger.logUdfpsPointerUp(sensorId);
}
};
@@ -1810,7 +1799,9 @@
TelephonyListenerManager telephonyListenerManager,
InteractionJankMonitor interactionJankMonitor,
LatencyTracker latencyTracker,
- ActiveUnlockConfig activeUnlockConfiguration) {
+ ActiveUnlockConfig activeUnlockConfiguration,
+ KeyguardUpdateMonitorLogger logger) {
+ mLogger = logger;
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -2154,13 +2145,13 @@
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
if (runningOrRestarting && !shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_START) {
- Log.v(TAG, "Ignoring stopListeningForFingerprint()");
+ mLogger.v("Ignoring stopListeningForFingerprint()");
return;
}
stopListeningForFingerprint();
} else if (!runningOrRestarting && shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_STOP) {
- Log.v(TAG, "Ignoring startListeningForFingerprint()");
+ mLogger.v("Ignoring startListeningForFingerprint()");
return;
}
startListeningForFingerprint();
@@ -2186,7 +2177,7 @@
* @param active If the interrupt started or ended.
*/
public void onAuthInterruptDetected(boolean active) {
- if (DEBUG) Log.d(TAG, "onAuthInterruptDetected(" + active + ")");
+ mLogger.logAuthInterruptDetected(active);
if (mAuthInterruptActive == active) {
return;
}
@@ -2201,7 +2192,7 @@
* @param userInitiatedRequest true if the user explicitly requested face auth
*/
public void requestFaceAuth(boolean userInitiatedRequest) {
- if (DEBUG) Log.d(TAG, "requestFaceAuth() userInitiated=" + userInitiatedRequest);
+ mLogger.logFaceAuthRequested(userInitiatedRequest);
mIsFaceAuthUserRequested |= userInitiatedRequest;
updateFaceListeningState(BIOMETRIC_ACTION_START);
}
@@ -2227,14 +2218,14 @@
boolean shouldListenForFace = shouldListenForFace();
if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
if (action == BIOMETRIC_ACTION_START) {
- Log.v(TAG, "Ignoring stopListeningForFace()");
+ mLogger.v("Ignoring stopListeningForFace()");
return;
}
mIsFaceAuthUserRequested = false;
stopListeningForFace();
} else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) {
if (action == BIOMETRIC_ACTION_STOP) {
- Log.v(TAG, "Ignoring startListeningForFace()");
+ mLogger.v("Ignoring startListeningForFace()");
return;
}
startListeningForFace();
@@ -2251,9 +2242,7 @@
}
if (shouldTriggerActiveUnlock()) {
- if (DEBUG_ACTIVE_UNLOCK) {
- Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason);
- }
+ mLogger.logActiveUnlockTriggered(reason);
mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
}
}
@@ -2281,12 +2270,7 @@
}
if (allowRequest && shouldTriggerActiveUnlock()) {
- if (DEBUG_ACTIVE_UNLOCK) {
- Log.d("ActiveUnlock", "reportUserRequestedUnlock"
- + " origin=" + requestOrigin.name()
- + " reason=" + reason
- + " dismissKeyguard=" + dismissKeyguard);
- }
+ mLogger.logUserRequestedUnlock(requestOrigin, reason, dismissKeyguard);
mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser(),
dismissKeyguard);
}
@@ -2436,32 +2420,30 @@
final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
&& shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
- if (DEBUG_FINGERPRINT || DEBUG_SPEW) {
- maybeLogListenerModelData(
- new KeyguardFingerprintListenModel(
- System.currentTimeMillis(),
- user,
- shouldListen,
- biometricEnabledForUser,
- mBouncerIsOrWillBeShowing,
- userCanSkipBouncer,
- mCredentialAttempted,
- mDeviceInteractive,
- mIsDreaming,
- isEncryptedOrLockdownForUser,
- fingerprintDisabledForUser,
- mFingerprintLockedOut,
- mGoingToSleep,
- mKeyguardGoingAway,
- mKeyguardIsVisible,
- mKeyguardOccluded,
- mOccludingAppRequestingFp,
- mIsPrimaryUser,
- shouldListenForFingerprintAssistant,
- mSwitchingUser,
- isUdfps,
- userDoesNotHaveTrust));
- }
+ maybeLogListenerModelData(
+ new KeyguardFingerprintListenModel(
+ System.currentTimeMillis(),
+ user,
+ shouldListen,
+ biometricEnabledForUser,
+ mBouncerIsOrWillBeShowing,
+ userCanSkipBouncer,
+ mCredentialAttempted,
+ mDeviceInteractive,
+ mIsDreaming,
+ isEncryptedOrLockdownForUser,
+ fingerprintDisabledForUser,
+ mFingerprintLockedOut,
+ mGoingToSleep,
+ mKeyguardGoingAway,
+ mKeyguardIsVisible,
+ mKeyguardOccluded,
+ mOccludingAppRequestingFp,
+ mIsPrimaryUser,
+ shouldListenForFingerprintAssistant,
+ mSwitchingUser,
+ isUdfps,
+ userDoesNotHaveTrust));
return shouldListen;
}
@@ -2536,59 +2518,49 @@
&& !fpLockedout;
// Aggregate relevant fields for debug logging.
- if (DEBUG_FACE || DEBUG_SPEW) {
- maybeLogListenerModelData(
- new KeyguardFaceListenModel(
- System.currentTimeMillis(),
- user,
- shouldListen,
- mAuthInterruptActive,
- becauseCannotSkipBouncer,
- biometricEnabledForUser,
- mBouncerFullyShown,
- faceAuthenticated,
- faceDisabledForUser,
- mGoingToSleep,
- awakeKeyguard,
- mKeyguardGoingAway,
- shouldListenForFaceAssistant,
- mOccludingAppRequestingFace,
- mIsPrimaryUser,
- strongAuthAllowsScanning,
- mSecureCameraLaunched,
- mSwitchingUser,
- mUdfpsBouncerShowing));
- }
+ maybeLogListenerModelData(
+ new KeyguardFaceListenModel(
+ System.currentTimeMillis(),
+ user,
+ shouldListen,
+ mAuthInterruptActive,
+ becauseCannotSkipBouncer,
+ biometricEnabledForUser,
+ mBouncerFullyShown,
+ faceAuthenticated,
+ faceDisabledForUser,
+ mGoingToSleep,
+ awakeKeyguard,
+ mKeyguardGoingAway,
+ shouldListenForFaceAssistant,
+ mOccludingAppRequestingFace,
+ mIsPrimaryUser,
+ strongAuthAllowsScanning,
+ mSecureCameraLaunched,
+ mSwitchingUser,
+ mUdfpsBouncerShowing));
return shouldListen;
}
private void maybeLogListenerModelData(KeyguardListenModel model) {
- // Too chatty, but very useful when debugging issues.
- if (DEBUG_SPEW) {
- Log.v(TAG, model.toString());
- }
+ mLogger.logKeyguardListenerModel(model);
- if (DEBUG_ACTIVE_UNLOCK
- && model instanceof KeyguardActiveUnlockModel) {
+ if (model instanceof KeyguardActiveUnlockModel) {
mListenModels.add(model);
return;
}
// Add model data to the historical buffer.
final boolean notYetRunning =
- (DEBUG_FACE
- && model instanceof KeyguardFaceListenModel
- && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
- || (DEBUG_FINGERPRINT
- && model instanceof KeyguardFingerprintListenModel
- && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
+ (model instanceof KeyguardFaceListenModel
+ && mFaceRunningState != BIOMETRIC_STATE_RUNNING)
+ || (model instanceof KeyguardFingerprintListenModel
+ && mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
final boolean running =
- (DEBUG_FACE
- && model instanceof KeyguardFaceListenModel
+ (model instanceof KeyguardFaceListenModel
&& mFaceRunningState == BIOMETRIC_STATE_RUNNING)
- || (DEBUG_FINGERPRINT
- && model instanceof KeyguardFingerprintListenModel
+ || (model instanceof KeyguardFingerprintListenModel
&& mFingerprintRunningState == BIOMETRIC_STATE_RUNNING);
if (notYetRunning && model.getListening()
|| running && !model.getListening()) {
@@ -2600,9 +2572,9 @@
final int userId = getCurrentUser();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
- Log.e(TAG, "Cancellation signal is not null, high chance of bug in fp auth lifecycle"
- + " management. FP state: " + mFingerprintRunningState
- + ", unlockPossible: " + unlockPossible);
+ mLogger.logUnexpectedFpCancellationSignalState(
+ mFingerprintRunningState,
+ unlockPossible);
}
if (mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2613,7 +2585,7 @@
// Waiting for restart via handleFingerprintError().
return;
}
- if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
+ mLogger.v("startListeningForFingerprint()");
if (unlockPossible) {
mFingerprintCancelSignal = new CancellationSignal();
@@ -2634,9 +2606,7 @@
final int userId = getCurrentUser();
final boolean unlockPossible = isUnlockWithFacePossible(userId);
if (mFaceCancelSignal != null) {
- Log.e(TAG, "Cancellation signal is not null, high chance of bug in face auth lifecycle"
- + " management. Face state: " + mFaceRunningState
- + ", unlockPossible: " + unlockPossible);
+ mLogger.logUnexpectedFaceCancellationSignalState(mFaceRunningState, unlockPossible);
}
if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
@@ -2646,7 +2616,7 @@
// Waiting for ERROR_CANCELED before requesting auth again
return;
}
- if (DEBUG) Log.v(TAG, "startListeningForFace(): " + mFaceRunningState);
+ mLogger.logStartedListeningForFace(mFaceRunningState);
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
@@ -2712,7 +2682,7 @@
}
private void stopListeningForFingerprint() {
- if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
+ mLogger.v("stopListeningForFingerprint()");
if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
@@ -2728,7 +2698,7 @@
}
private void stopListeningForFace() {
- if (DEBUG) Log.v(TAG, "stopListeningForFace()");
+ mLogger.v("stopListeningForFace()");
if (mFaceRunningState == BIOMETRIC_STATE_RUNNING) {
if (mFaceCancelSignal != null) {
mFaceCancelSignal.cancel();
@@ -2757,7 +2727,7 @@
if (mDeviceProvisioned) {
mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
}
- if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
+ mLogger.logDeviceProvisionedState(mDeviceProvisioned);
}
};
@@ -2862,7 +2832,7 @@
*/
private void handlePhoneStateChanged(String newState) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
+ mLogger.logPhoneStateChanged(newState);
if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
mPhoneState = TelephonyManager.CALL_STATE_IDLE;
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) {
@@ -2883,7 +2853,7 @@
*/
private void handleTimeUpdate() {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeUpdate");
+ mLogger.d("handleTimeUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2897,7 +2867,7 @@
*/
private void handleTimeZoneUpdate(String timeZone) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeZoneUpdate");
+ mLogger.d("handleTimeZoneUpdate");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2915,7 +2885,7 @@
*/
private void handleTimeFormatUpdate(String timeFormat) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleTimeFormatUpdate timeFormat=" + timeFormat);
+ mLogger.logTimeFormatChanged(timeFormat);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2929,7 +2899,7 @@
*/
private void handleBatteryUpdate(BatteryStatus status) {
Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
+ mLogger.d("handleBatteryUpdate");
final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
mBatteryStatus = status;
if (batteryUpdateInteresting) {
@@ -2966,14 +2936,11 @@
@VisibleForTesting
void handleSimStateChange(int subId, int slotId, int state) {
Assert.isMainThread();
- if (DEBUG_SIM_STATES) {
- Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
- + slotId + ", state=" + state + ")");
- }
+ mLogger.logSimState(subId, slotId, state);
boolean becameAbsent = false;
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- Log.w(TAG, "invalid subId in handleSimStateChange()");
+ mLogger.w("invalid subId in handleSimStateChange()");
/* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to
* handleServiceStateChange() handle other case */
if (state == TelephonyManager.SIM_STATE_ABSENT) {
@@ -3022,13 +2989,10 @@
*/
@VisibleForTesting
void handleServiceStateChange(int subId, ServiceState serviceState) {
- if (DEBUG) {
- Log.d(TAG,
- "handleServiceStateChange(subId=" + subId + ", serviceState=" + serviceState);
- }
+ mLogger.logServiceStateChange(subId, serviceState);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- Log.w(TAG, "invalid subId in handleServiceStateChange()");
+ mLogger.w("invalid subId in handleServiceStateChange()");
return;
} else {
updateTelephonyCapable(true);
@@ -3050,7 +3014,7 @@
*/
public void onKeyguardVisibilityChanged(boolean showing) {
Assert.isMainThread();
- Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
+ mLogger.logKeyguardVisibilityChanged(showing);
mKeyguardIsVisible = showing;
if (showing) {
@@ -3070,7 +3034,7 @@
* Handle {@link #MSG_KEYGUARD_RESET}
*/
private void handleKeyguardReset() {
- if (DEBUG) Log.d(TAG, "handleKeyguardReset");
+ mLogger.d("handleKeyguardReset");
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
}
@@ -3084,8 +3048,8 @@
0 /* flags */, getCurrentUser());
if (resolveInfo == null) {
- Log.w(TAG, "resolveNeedsSlowUnlockTransition: returning false since activity "
- + "could not be resolved.");
+ mLogger.w("resolveNeedsSlowUnlockTransition: returning false since activity could "
+ + "not be resolved.");
return false;
}
@@ -3103,11 +3067,7 @@
final boolean wasBouncerFullyShown = mBouncerFullyShown;
mBouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing == 1;
mBouncerFullyShown = bouncerFullyShown == 1;
- if (DEBUG) {
- Log.d(TAG, "handleKeyguardBouncerChanged"
- + " bouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing
- + " bouncerFullyShowing=" + mBouncerFullyShown);
- }
+ mLogger.logKeyguardBouncerChanged(mBouncerIsOrWillBeShowing, mBouncerFullyShown);
if (mBouncerFullyShown) {
// If the bouncer is shown, always clear this flag. This can happen in the following
@@ -3227,9 +3187,7 @@
*/
public void removeCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
- if (DEBUG) {
- Log.v(TAG, "*** unregister callback for " + callback);
- }
+ mLogger.logUnregisterCallback(callback);
mCallbacks.removeIf(el -> el.get() == callback);
}
@@ -3242,15 +3200,14 @@
*/
public void registerCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
- if (DEBUG) Log.v(TAG, "*** register callback for " + callback);
+ mLogger.logRegisterCallback(callback);
// Prevent adding duplicate callbacks
for (int i = 0; i < mCallbacks.size(); i++) {
if (mCallbacks.get(i).get() == callback) {
- if (DEBUG) {
- Log.e(TAG, "Object tried to add another callback",
- new Exception("Called by"));
- }
+ mLogger.logException(
+ new Exception("Called by"),
+ "Object tried to add another callback");
return;
}
}
@@ -3300,11 +3257,7 @@
*/
public void sendKeyguardBouncerChanged(boolean bouncerIsOrWillBeShowing,
boolean bouncerFullyShown) {
- if (DEBUG) {
- Log.d(TAG, "sendKeyguardBouncerChanged"
- + " bouncerIsOrWillBeShowing=" + bouncerIsOrWillBeShowing
- + " bouncerFullyShown=" + bouncerFullyShown);
- }
+ mLogger.logSendKeyguardBouncerChanged(bouncerIsOrWillBeShowing, bouncerFullyShown);
Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
message.arg1 = bouncerIsOrWillBeShowing ? 1 : 0;
message.arg2 = bouncerFullyShown ? 1 : 0;
@@ -3321,7 +3274,7 @@
*/
@MainThread
public void reportSimUnlocked(int subId) {
- if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
+ mLogger.logSimUnlocked(subId);
handleSimStateChange(subId, getSlotId(subId), TelephonyManager.SIM_STATE_READY);
}
@@ -3598,7 +3551,9 @@
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
- Log.d(TAG, "RemoteException onDestroy. cannot unregister userSwitchObserver");
+ mLogger.logException(
+ e,
+ "RemoteException onDestroy. cannot unregister userSwitchObserver");
}
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
new file mode 100644
index 0000000..035b7f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -0,0 +1,326 @@
+/*
+ * 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.keyguard.logging
+
+import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.KeyguardListenModel
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "KeyguardUpdateMonitorLog"
+
+/**
+ * Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor]
+ */
+class KeyguardUpdateMonitorLogger @Inject constructor(
+ @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer
+) {
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+
+ fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun v(@CompileTimeConstant msg: String) = log(msg, ERROR)
+
+ fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+ fun logActiveUnlockTriggered(reason: String) {
+ logBuffer.log("ActiveUnlock", DEBUG,
+ { str1 = reason },
+ { "initiate active unlock triggerReason=$str1" })
+ }
+
+ fun logAuthInterruptDetected(active: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = active },
+ { "onAuthInterruptDetected($bool1)" })
+ }
+
+ fun logBroadcastReceived(action: String?) {
+ logBuffer.log(TAG, DEBUG, { str1 = action }, { "received broadcast $str1" })
+ }
+
+ fun logDeviceProvisionedState(deviceProvisioned: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = deviceProvisioned },
+ { "DEVICE_PROVISIONED state = $bool1" })
+ }
+
+ fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+ logBuffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
+ }
+
+ fun logFaceAcquired(acquireInfo: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = acquireInfo },
+ { "Face acquired acquireInfo=$int1" })
+ }
+
+ fun logFaceAuthDisabledForUser(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Face authentication disabled by DPM for userId: $int1" })
+ }
+ fun logFaceAuthError(msgId: Int, originalErrMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ str1 = originalErrMsg
+ int1 = msgId
+ }, { "Face error received: $str1 msgId= $int1" })
+ }
+
+ fun logFaceAuthForWrongUser(authUserId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = authUserId },
+ { "Face authenticated for wrong user: $int1" })
+ }
+
+ fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = msgId
+ str1 = helpMsg
+ }, { "Face help received, msgId: $int1 msg: $str1" })
+ }
+
+ fun logFaceAuthRequested(userInitiatedRequest: Boolean) {
+ logBuffer.log(TAG, DEBUG,
+ { bool1 = userInitiatedRequest },
+ { "requestFaceAuth() userInitiated=$bool1" })
+ }
+
+ fun logFaceAuthSuccess(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Face auth succeeded for user $int1" })
+ }
+
+ fun logFaceLockoutReset(@LockoutMode mode: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFaceLockoutReset: $int1" })
+ }
+
+ fun logFaceRunningState(faceRunningState: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = faceRunningState }, { "faceRunningState: $int1" })
+ }
+
+ fun logFingerprintAuthForWrongUser(authUserId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = authUserId },
+ { "Fingerprint authenticated for wrong user: $int1" })
+ }
+
+ fun logFingerprintDisabledForUser(userId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = userId },
+ { "Fingerprint disabled by DPM for userId: $int1" })
+ }
+
+ fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+ }
+
+ fun logFingerprintRunningState(fingerprintRunningState: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = fingerprintRunningState },
+ { "fingerprintRunningState: $int1" })
+ }
+
+ fun logInvalidSubId(subId: Int) {
+ logBuffer.log(TAG, INFO,
+ { int1 = subId },
+ { "Previously active sub id $int1 is now invalid, will remove" })
+ }
+
+ fun logKeyguardBouncerChanged(bouncerIsOrWillBeShowing: Boolean, bouncerFullyShown: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = bouncerIsOrWillBeShowing
+ bool2 = bouncerFullyShown
+ }, {
+ "handleKeyguardBouncerChanged " +
+ "bouncerIsOrWillBeShowing=$bool1 bouncerFullyShowing=$bool2"
+ })
+ }
+
+ fun logKeyguardListenerModel(model: KeyguardListenModel) {
+ logBuffer.log(TAG, VERBOSE, { str1 = "$model" }, { str1!! })
+ }
+
+ fun logKeyguardVisibilityChanged(showing: Boolean) {
+ logBuffer.log(TAG, DEBUG, { bool1 = showing }, { "onKeyguardVisibilityChanged($bool1)" })
+ }
+
+ fun logMissingSupervisorAppError(userId: Int) {
+ logBuffer.log(TAG, ERROR,
+ { int1 = userId },
+ { "No Profile Owner or Device Owner supervision app found for User $int1" })
+ }
+
+ fun logPhoneStateChanged(newState: String) {
+ logBuffer.log(TAG, DEBUG,
+ { str1 = newState },
+ { "handlePhoneStateChanged($str1)" })
+ }
+
+ fun logRegisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$callback" },
+ { "*** register callback for $str1" })
+ }
+
+ fun logRetryingAfterFaceHwUnavailable(retryCount: Int) {
+ logBuffer.log(TAG, WARNING,
+ { int1 = retryCount },
+ { "Retrying face after HW unavailable, attempt $int1" })
+ }
+
+ fun logRetryAfterFpHwUnavailable(retryCount: Int) {
+ logBuffer.log(TAG, WARNING,
+ { int1 = retryCount },
+ { "Retrying fingerprint attempt: $int1" })
+ }
+
+ fun logSendKeyguardBouncerChanged(
+ bouncerIsOrWillBeShowing: Boolean,
+ bouncerFullyShown: Boolean,
+ ) {
+ logBuffer.log(TAG, DEBUG, {
+ bool1 = bouncerIsOrWillBeShowing
+ bool2 = bouncerFullyShown
+ }, {
+ "sendKeyguardBouncerChanged bouncerIsOrWillBeShowing=$bool1 " +
+ "bouncerFullyShown=$bool2"
+ })
+ }
+
+ fun logServiceStateChange(subId: Int, serviceState: ServiceState?) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = subId
+ str1 = "$serviceState"
+ }, { "handleServiceStateChange(subId=$int1, serviceState=$str1)" })
+ }
+
+ fun logServiceStateIntent(action: String, serviceState: ServiceState?, subId: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = action
+ str2 = "$serviceState"
+ int1 = subId
+ }, { "action $str1 serviceState=$str2 subId=$int1" })
+ }
+
+ fun logSimState(subId: Int, slotId: Int, state: Int) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = subId
+ int2 = slotId
+ long1 = state.toLong()
+ }, { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" })
+ }
+
+ fun logSimStateFromIntent(action: String, extraSimState: String, slotId: Int, subId: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = action
+ str2 = extraSimState
+ int1 = slotId
+ int2 = subId
+ }, { "action $str1 state: $str2 slotId: $int1 subid: $int2" })
+ }
+
+ fun logSimUnlocked(subId: Int) {
+ logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
+ }
+
+ fun logStartedListeningForFace(faceRunningState: Int) {
+ logBuffer.log(TAG, VERBOSE,
+ { int1 = faceRunningState },
+ { "startListeningForFace(): $int1" })
+ }
+
+ fun logSubInfo(subInfo: SubscriptionInfo?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$subInfo" },
+ { "SubInfo:$str1" })
+ }
+
+ fun logTimeFormatChanged(newTimeFormat: String) {
+ logBuffer.log(TAG, DEBUG,
+ { str1 = newTimeFormat },
+ { "handleTimeFormatUpdate timeFormat=$str1" })
+ }
+
+ fun logUdfpsPointerDown(sensorId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = sensorId },
+ { "onUdfpsPointerDown, sensorId: $int1" })
+ }
+ fun logUdfpsPointerUp(sensorId: Int) {
+ logBuffer.log(TAG, DEBUG,
+ { int1 = sensorId },
+ { "onUdfpsPointerUp, sensorId: $int1" })
+ }
+
+ fun logUnexpectedFaceCancellationSignalState(faceRunningState: Int, unlockPossible: Boolean) {
+ logBuffer.log(TAG, ERROR, {
+ int1 = faceRunningState
+ bool1 = unlockPossible
+ }, {
+ "Cancellation signal is not null, high chance of bug in " +
+ "face auth lifecycle management. " +
+ "Face state: $int1, unlockPossible: $bool1"
+ })
+ }
+
+ fun logUnexpectedFpCancellationSignalState(
+ fingerprintRunningState: Int,
+ unlockPossible: Boolean
+ ) {
+ logBuffer.log(TAG, ERROR, {
+ int1 = fingerprintRunningState
+ bool1 = unlockPossible
+ }, {
+ "Cancellation signal is not null, high chance of bug in " +
+ "fp auth lifecycle management. FP state: $int1, unlockPossible: $bool1"
+ })
+ }
+
+ fun logUnregisterCallback(callback: KeyguardUpdateMonitorCallback?) {
+ logBuffer.log(TAG, VERBOSE,
+ { str1 = "$callback" },
+ { "*** unregister callback for $str1" })
+ }
+
+ fun logUserRequestedUnlock(
+ requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
+ reason: String,
+ dismissKeyguard: Boolean
+ ) {
+ logBuffer.log("ActiveUnlock", DEBUG, {
+ str1 = requestOrigin.name
+ str2 = reason
+ bool1 = dismissKeyguard
+ }, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 1270bd8..5c84ff3 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -70,6 +70,7 @@
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
@@ -80,7 +81,6 @@
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
@@ -104,7 +104,6 @@
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -323,7 +322,6 @@
@Inject Lazy<SmartReplyConstants> mSmartReplyConstants;
@Inject Lazy<NotificationListener> mNotificationListener;
@Inject Lazy<NotificationLogger> mNotificationLogger;
- @Inject Lazy<NotificationViewHierarchyManager> mNotificationViewHierarchyManager;
@Inject Lazy<NotificationFilter> mNotificationFilter;
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@@ -540,8 +538,6 @@
mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get);
mProviders.put(NotificationListener.class, mNotificationListener::get);
mProviders.put(NotificationLogger.class, mNotificationLogger::get);
- mProviders.put(NotificationViewHierarchyManager.class,
- mNotificationViewHierarchyManager::get);
mProviders.put(NotificationFilter.class, mNotificationFilter::get);
mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get);
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index e9ca0fd..5f586c9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -79,11 +79,6 @@
// Stand up WMComponent
setupWmComponent(mContext);
- if (initializeComponents) {
- // Only initialize when not starting from tests since this currently initializes some
- // components that shouldn't be run in the test environment
- mWMComponent.init();
- }
// And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
SysUIComponent.Builder builder = mRootComponent.getSysUIComponent();
@@ -102,6 +97,10 @@
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation());
+
+ // Only initialize when not starting from tests since this currently initializes some
+ // components that shouldn't be run in the test environment
+ mWMComponent.init();
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
// is separating this logic into newly creating SystemUITestsFactory.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 448b99b..a1288b5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -500,7 +500,7 @@
private void handleTakeScreenshot() {
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
index e30f2e4..7326ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
@@ -25,10 +25,10 @@
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.FrameworkStatsLog
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.assist.AssistantInvocationEvent.Companion.deviceStateFromLegacyDeviceState
import com.android.systemui.assist.AssistantInvocationEvent.Companion.eventFromLegacyInvocationType
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
/** Class for reporting events related to Assistant sessions. */
@@ -37,7 +37,8 @@
protected val context: Context,
protected val uiEventLogger: UiEventLogger,
private val assistUtils: AssistUtils,
- private val phoneStateMonitor: PhoneStateMonitor
+ private val phoneStateMonitor: PhoneStateMonitor,
+ private val userTracker: UserTracker,
) {
private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
@@ -78,7 +79,7 @@
FrameworkStatsLog.ASSISTANT_INVOCATION_REPORTED,
invocationEvent.id,
assistantUid,
- assistComponentFinal.flattenToString(),
+ assistComponentFinal?.flattenToString() ?: "",
getOrCreateInstanceId().id,
deviceStateFinal,
false)
@@ -91,7 +92,7 @@
uiEventLogger.logWithInstanceId(
sessionEvent,
assistantUid,
- assistantComponent.flattenToString(),
+ assistantComponent?.flattenToString(),
getOrCreateInstanceId())
if (SESSION_END_EVENTS.contains(sessionEvent)) {
@@ -112,11 +113,15 @@
currentInstanceId = null
}
- protected fun getAssistantComponentForCurrentUser(): ComponentName {
- return assistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser())
+ protected fun getAssistantComponentForCurrentUser(): ComponentName? {
+ return assistUtils.getAssistComponentForUser(userTracker.userId)
}
- protected fun getAssistantUid(assistantComponent: ComponentName): Int {
+ protected fun getAssistantUid(assistantComponent: ComponentName?): Int {
+ if (assistantComponent == null) {
+ return 0
+ }
+
var assistantUid = 0
try {
assistantUid = context.packageManager.getApplicationInfo(
@@ -138,4 +143,4 @@
AssistantSessionEvent.ASSISTANT_SESSION_INVOCATION_CANCELLED,
AssistantSessionEvent.ASSISTANT_SESSION_CLOSE)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 7c2673c..57ffdab 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -310,7 +310,8 @@
private void startVoiceInteractor(Bundle args) {
mAssistUtils.showSessionForActiveService(args,
- VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, null, null);
+ VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mContext.getAttributionTag(),
+ null, null);
}
public void launchVoiceAssistFromKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 0892612..5ed8986 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -16,12 +16,20 @@
package com.android.systemui.biometrics;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.WindowInsets.Type.ime;
+
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.Insets;
import android.os.UserHandle;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImeAwareEditText;
@@ -31,18 +39,24 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import java.io.PrintWriter;
+
/**
* Pin and Password UI
*/
public class AuthCredentialPasswordView extends AuthCredentialView
- implements TextView.OnEditorActionListener {
+ implements TextView.OnEditorActionListener, OnApplyWindowInsetsListener, Dumpable {
private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView";
private final InputMethodManager mImm;
private ImeAwareEditText mPasswordField;
+ private ViewGroup mAuthCredentialHeader;
+ private ViewGroup mAuthCredentialInput;
+ private int mBottomInset = 0;
public AuthCredentialPasswordView(Context context,
AttributeSet attrs) {
@@ -53,6 +67,9 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+
+ mAuthCredentialHeader = findViewById(R.id.auth_credential_header);
+ mAuthCredentialInput = findViewById(R.id.auth_credential_input);
mPasswordField = findViewById(R.id.lockPassword);
mPasswordField.setOnEditorActionListener(this);
// TODO: De-dupe the logic with AuthContainerView
@@ -66,6 +83,8 @@
}
return true;
});
+
+ setOnApplyWindowInsetsListener(this);
}
@Override
@@ -127,4 +146,65 @@
mPasswordField.setText("");
}
}
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mAuthCredentialInput == null || mAuthCredentialHeader == null
+ || mSubtitleView == null || mPasswordField == null || mErrorView == null) {
+ return;
+ }
+
+ // b/157910732 In AuthContainerView#getLayoutParams() we used to prevent jank risk when
+ // resizing by IME show or hide, we used to setFitInsetsTypes `~WindowInsets.Type.ime()` to
+ // LP. As a result this view needs to listen onApplyWindowInsets() and handle onLayout.
+ int inputLeftBound;
+ int inputTopBound;
+ int headerRightBound = right;
+ if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ inputTopBound = (bottom - (mPasswordField.getHeight() + mErrorView.getHeight())) / 2;
+ inputLeftBound = (right - left) / 2;
+ headerRightBound = inputLeftBound;
+ } else {
+ inputTopBound = mSubtitleView.getBottom() + (bottom - mSubtitleView.getBottom()) / 2;
+ inputLeftBound = (right - left - mAuthCredentialInput.getWidth()) / 2;
+ }
+
+ mAuthCredentialHeader.layout(left, top, headerRightBound, bottom);
+ mAuthCredentialInput.layout(inputLeftBound, inputTopBound, right, bottom);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int newHeight = MeasureSpec.getSize(heightMeasureSpec) - mBottomInset;
+
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), newHeight);
+
+ measureChildren(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.AT_MOST));
+ }
+
+ @NonNull
+ @Override
+ public WindowInsets onApplyWindowInsets(@NonNull View v, WindowInsets insets) {
+
+ final Insets bottomInset = insets.getInsets(ime());
+ if (v instanceof AuthCredentialPasswordView && mBottomInset != bottomInset.bottom) {
+ mBottomInset = bottomInset.bottom;
+ requestLayout();
+ }
+ return insets;
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println(TAG + "State:");
+ pw.println(" mBottomInset=" + mBottomInset);
+ pw.println(" mAuthCredentialHeader size=(" + mAuthCredentialHeader.getWidth() + ","
+ + mAuthCredentialHeader.getHeight());
+ pw.println(" mAuthCredentialInput size=(" + mAuthCredentialInput.getWidth() + ","
+ + mAuthCredentialInput.getHeight());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 4fa835e..d4176ac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -87,7 +87,7 @@
private boolean mShouldAnimateContents;
private TextView mTitleView;
- private TextView mSubtitleView;
+ protected TextView mSubtitleView;
private TextView mDescriptionView;
private ImageView mIconView;
protected TextView mErrorView;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index fef7383..1c57480 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -75,12 +75,12 @@
}
private var radius: Float = 0f
set(value) {
- rippleShader.radius = value
+ rippleShader.setMaxSize(value * 2f, value * 2f)
field = value
}
private var origin: PointF = PointF()
set(value) {
- rippleShader.origin = value
+ rippleShader.setCenter(value.x, value.y)
field = value
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index bbffb73..d03106b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -43,6 +43,8 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
@@ -130,7 +132,7 @@
fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
gravity = Gravity.TOP or Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+ privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 8292e52..da675de 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -19,7 +19,6 @@
import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
-import android.graphics.PointF
import android.os.SystemProperties
import android.util.DisplayMetrics
import android.view.View
@@ -85,7 +84,7 @@
private var debounceLevel = 0
@VisibleForTesting
- var rippleView: RippleView = RippleView(context, attrs = null)
+ var rippleView: RippleView = RippleView(context, attrs = null).also { it.setupShader() }
init {
pluggedIn = batteryController.isPluggedIn
@@ -177,20 +176,25 @@
context.display.getRealMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
- rippleView.radius = Integer.max(width, height).toFloat()
- rippleView.origin = when (RotationUtils.getExactRotation(context)) {
+ val maxDiameter = Integer.max(width, height) * 2f
+ rippleView.setMaxSize(maxDiameter, maxDiameter)
+ when (RotationUtils.getExactRotation(context)) {
RotationUtils.ROTATION_LANDSCAPE -> {
- PointF(width * normalizedPortPosY, height * (1 - normalizedPortPosX))
+ rippleView.setCenter(
+ width * normalizedPortPosY, height * (1 - normalizedPortPosX))
}
RotationUtils.ROTATION_UPSIDE_DOWN -> {
- PointF(width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
+ rippleView.setCenter(
+ width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
}
RotationUtils.ROTATION_SEASCAPE -> {
- PointF(width * (1 - normalizedPortPosY), height * normalizedPortPosX)
+ rippleView.setCenter(
+ width * (1 - normalizedPortPosY), height * normalizedPortPosX)
}
else -> {
// ROTATION_NONE
- PointF(width * normalizedPortPosX, height * normalizedPortPosY)
+ rippleView.setCenter(
+ width * normalizedPortPosX, height * normalizedPortPosY)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index f6368ee..65400c2 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -21,7 +21,6 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -34,6 +33,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.ripple.RippleShader;
import com.android.systemui.ripple.RippleView;
import java.text.NumberFormat;
@@ -138,6 +138,8 @@
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
+ // TODO: Make rounded box shape if the device is tablet.
+ mRippleView.setupShader(RippleShader.RippleShape.CIRCLE);
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
@@ -230,11 +232,11 @@
if (mRippleView != null) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
- mRippleView.setColor(
- Utils.getColorAttr(mRippleView.getContext(),
- android.R.attr.colorAccent).getDefaultColor());
- mRippleView.setOrigin(new PointF(width / 2, height / 2));
- mRippleView.setRadius(Math.max(width, height) * 0.5f);
+ mRippleView.setCenter(width * 0.5f, height * 0.5f);
+ 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/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
new file mode 100644
index 0000000..6f3beac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.common.coroutine
+
+import android.util.Log
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.onFailure
+
+object ChannelExt {
+
+ /**
+ * Convenience wrapper around [SendChannel.trySend] that also logs on failure. This is the
+ * equivalent of calling:
+ *
+ * ```
+ * sendChannel.trySend(element).onFailure {
+ * Log.e(
+ * loggingTag,
+ * "Failed to send $elementDescription" +
+ * " - downstream canceled or failed.",
+ * it,
+ * )
+ *}
+ * ```
+ */
+ fun <T> SendChannel<T>.trySendWithFailureLogging(
+ element: T,
+ loggingTag: String,
+ elementDescription: String = "updated state",
+ ) {
+ trySend(element).onFailure {
+ Log.e(
+ loggingTag,
+ "Failed to send $elementDescription - downstream canceled or failed.",
+ it,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
new file mode 100644
index 0000000..d4a1f74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.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.common.coroutine
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+object ConflatedCallbackFlow {
+
+ /**
+ * A [callbackFlow] that uses a buffer [Channel] that is "conflated" meaning that, if
+ * backpressure occurs (if the producer that emits new values into the flow is faster than the
+ * consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
+ * we drop the oldest values automatically instead of suspending the producer.
+ */
+ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+ @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
+ fun <T> conflatedCallbackFlow(
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+ ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
new file mode 100644
index 0000000..7c9df10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.common.data.model
+
+/** Models a two-dimensional position */
+data class Position(
+ val x: Int,
+ val y: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
new file mode 100644
index 0000000..f697c0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.common.domain.model
+
+import com.android.systemui.common.data.model.Position as DataLayerPosition
+
+/** Models a two-dimensional position */
+data class Position(
+ val x: Int,
+ val y: Int,
+) {
+ companion object {
+ fun DataLayerPosition.toDomainLayer(): Position {
+ return Position(
+ x = x,
+ y = y,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
new file mode 100644
index 0000000..d6a059d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/containeddrawable/ContainedDrawable.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.containeddrawable
+
+import android.graphics.drawable.Drawable
+import androidx.annotation.DrawableRes
+
+/** Convenience container for [Drawable] or a way to load it later. */
+sealed class ContainedDrawable {
+ data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
+ data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 2fd3731..9e4a364 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -21,20 +21,22 @@
import android.database.ContentObserver
import android.os.UserHandle
import android.provider.Settings
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
+import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
-import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
/**
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -59,7 +61,8 @@
private val contentResolver: ContentResolver
get() = context.contentResolver
- private var canShowWhileLockedSetting = false
+ private val _canShowWhileLockedSetting = MutableStateFlow(false)
+ val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
optionalControlsTileResourceConfiguration.orElse(
@@ -117,7 +120,7 @@
== STRONG_AUTH_REQUIRED_AFTER_BOOT) {
return Visibility.AVAILABLE_AFTER_UNLOCK
}
- if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
+ if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
return Visibility.AVAILABLE_AFTER_UNLOCK
}
@@ -125,7 +128,7 @@
}
private fun updateShowWhileLocked() {
- canShowWhileLockedSetting = secureSettings.getIntForUser(
+ _canShowWhileLockedSetting.value = secureSettings.getIntForUser(
Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 1268250..c1e99f5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -59,7 +59,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.phone.ShadeController
+import com.android.systemui.shade.ShadeController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -71,18 +71,18 @@
@SysUISingleton
class ControlsUiControllerImpl @Inject constructor (
- val controlsController: Lazy<ControlsController>,
- val context: Context,
- @Main val uiExecutor: DelayableExecutor,
- @Background val bgExecutor: DelayableExecutor,
- val controlsListingController: Lazy<ControlsListingController>,
- @Main val sharedPreferences: SharedPreferences,
- val controlActionCoordinator: ControlActionCoordinator,
- private val activityStarter: ActivityStarter,
- private val shadeController: ShadeController,
- private val iconCache: CustomIconCache,
- private val controlsMetricsLogger: ControlsMetricsLogger,
- private val keyguardStateController: KeyguardStateController
+ val controlsController: Lazy<ControlsController>,
+ val context: Context,
+ @Main val uiExecutor: DelayableExecutor,
+ @Background val bgExecutor: DelayableExecutor,
+ val controlsListingController: Lazy<ControlsListingController>,
+ @Main val sharedPreferences: SharedPreferences,
+ val controlActionCoordinator: ControlActionCoordinator,
+ private val activityStarter: ActivityStarter,
+ private val shadeController: ShadeController,
+ private val iconCache: CustomIconCache,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val keyguardStateController: KeyguardStateController
) : ControlsUiController {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index e9f518f..5dff4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -45,6 +45,9 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -57,9 +60,6 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.ShadeControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 718befa..029cabb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -31,7 +31,6 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
-import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
import com.android.systemui.unfold.FoldStateLoggingProvider;
@@ -131,7 +130,6 @@
getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
- getConnectivityInfoProcessor();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -214,9 +212,6 @@
/** */
Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
- /** */
- Optional<ConnectivityInfoProcessor> getConnectivityInfoProcessor();
-
/**
* Returns {@link CoreStartable}s that should be started with the application.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ceb702e..f32ea35 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -51,6 +51,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -68,7 +69,6 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
new file mode 100644
index 0000000..d853e04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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.doze.util
+
+import javax.inject.Inject
+
+/** Injectable wrapper around `BurnInHelper` functions */
+class BurnInHelperWrapper @Inject constructor() {
+
+ fun burnInOffset(amplitude: Int, xAxis: Boolean): Int {
+ return getBurnInOffset(amplitude, xAxis)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 51bd311..5457144 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -162,7 +162,8 @@
COMPLICATION_TYPE_DATE,
COMPLICATION_TYPE_WEATHER,
COMPLICATION_TYPE_AIR_QUALITY,
- COMPLICATION_TYPE_CAST_INFO
+ COMPLICATION_TYPE_CAST_INFO,
+ COMPLICATION_TYPE_HOME_CONTROLS
})
@Retention(RetentionPolicy.SOURCE)
@interface ComplicationType {}
@@ -173,6 +174,7 @@
int COMPLICATION_TYPE_WEATHER = 1 << 2;
int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3;
int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
+ int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5;
/**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
index a4a0075..dcab90f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -19,6 +19,7 @@
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
@@ -48,6 +49,8 @@
return COMPLICATION_TYPE_AIR_QUALITY;
case DreamBackend.COMPLICATION_TYPE_CAST_INFO:
return COMPLICATION_TYPE_CAST_INFO;
+ case DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS:
+ return COMPLICATION_TYPE_HOME_CONTROLS;
default:
return COMPLICATION_TYPE_NONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
deleted file mode 100644
index 1ca06b2..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS;
-import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-/**
- * Clock Date Complication that produce Clock Date view holder.
- */
-public class DreamClockDateComplication implements Complication {
- private final Provider<DreamClockDateViewHolder> mDreamClockDateViewHolderProvider;
-
- /**
- * Default constructor for {@link DreamClockDateComplication}.
- */
- @Inject
- public DreamClockDateComplication(
- Provider<DreamClockDateViewHolder> dreamClockDateViewHolderProvider) {
- mDreamClockDateViewHolderProvider = dreamClockDateViewHolderProvider;
- }
-
- @Override
- public int getRequiredTypeAvailability() {
- return COMPLICATION_TYPE_DATE;
- }
-
- /**
- * Create {@link DreamClockDateViewHolder}.
- */
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return mDreamClockDateViewHolderProvider.get();
- }
-
- /**
- * {@link CoreStartable} responsible for registering {@link DreamClockDateComplication} with
- * SystemUI.
- */
- public static class Registrant extends CoreStartable {
- private final DreamOverlayStateController mDreamOverlayStateController;
- private final DreamClockDateComplication mComplication;
-
- /**
- * Default constructor to register {@link DreamClockDateComplication}.
- */
- @Inject
- public Registrant(Context context,
- DreamOverlayStateController dreamOverlayStateController,
- DreamClockDateComplication dreamClockDateComplication) {
- super(context);
- mDreamOverlayStateController = dreamOverlayStateController;
- mComplication = dreamClockDateComplication;
- }
-
- @Override
- public void start() {
- mDreamOverlayStateController.addComplication(mComplication);
- }
- }
-
- /**
- * {@link ViewHolder} to contain value/logic associated with {@link DreamClockDateComplication}.
- */
- public static class DreamClockDateViewHolder implements ViewHolder {
- private final View mView;
- private final ComplicationLayoutParams mLayoutParams;
-
- @Inject
- DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view,
- @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
- ComplicationLayoutParams layoutParams) {
- mView = view;
- mLayoutParams = layoutParams;
- }
-
- @Override
- public View getView() {
- return mView;
- }
-
- @Override
- public ComplicationLayoutParams getLayoutParams() {
- return mLayoutParams;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
index 7f67ecd..675a2f4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -16,8 +16,8 @@
package com.android.systemui.dreams.complication;
-import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import android.content.Context;
import android.view.View;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
new file mode 100644
index 0000000..1c72e49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -0,0 +1,205 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
+import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.controls.dagger.ControlsComponent;
+import com.android.systemui.controls.management.ControlsListingController;
+import com.android.systemui.controls.ui.ControlsActivity;
+import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * A dream complication that shows a home controls chip to launch device controls (to control
+ * devices at home like lights and thermostats).
+ */
+public class DreamHomeControlsComplication implements Complication {
+ private final DreamHomeControlsComplicationComponent.Factory mComponentFactory;
+
+ @Inject
+ public DreamHomeControlsComplication(
+ DreamHomeControlsComplicationComponent.Factory componentFactory) {
+ mComponentFactory = componentFactory;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mComponentFactory.create().getViewHolder();
+ }
+
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_HOME_CONTROLS;
+ }
+
+ /**
+ * {@link CoreStartable} for registering the complication with SystemUI on startup.
+ */
+ public static class Registrant extends CoreStartable {
+ private final DreamHomeControlsComplication mComplication;
+ private final DreamOverlayStateController mDreamOverlayStateController;
+ private final ControlsComponent mControlsComponent;
+
+ private boolean mControlServicesAvailable = false;
+
+ // Callback for when the home controls service availability changes.
+ private final ControlsListingController.ControlsListingCallback mControlsCallback =
+ serviceInfos -> {
+ boolean available = !serviceInfos.isEmpty();
+
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateComplicationAvailability();
+ }
+ };
+
+ @Inject
+ public Registrant(Context context, DreamHomeControlsComplication complication,
+ DreamOverlayStateController dreamOverlayStateController,
+ ControlsComponent controlsComponent) {
+ super(context);
+
+ mComplication = complication;
+ mControlsComponent = controlsComponent;
+ mDreamOverlayStateController = dreamOverlayStateController;
+ }
+
+ @Override
+ public void start() {
+ mControlsComponent.getControlsListingController().ifPresent(
+ c -> c.addCallback(mControlsCallback));
+ }
+
+ private void updateComplicationAvailability() {
+ final boolean hasFavorites = mControlsComponent.getControlsController()
+ .map(c -> !c.getFavorites().isEmpty())
+ .orElse(false);
+ if (!hasFavorites || !mControlServicesAvailable
+ || mControlsComponent.getVisibility() == UNAVAILABLE) {
+ mDreamOverlayStateController.removeComplication(mComplication);
+ } else {
+ mDreamOverlayStateController.addComplication(mComplication);
+ }
+ }
+ }
+
+ /**
+ * Contains values/logic associated with the dream complication view.
+ */
+ public static class DreamHomeControlsChipViewHolder implements ViewHolder {
+ private final View mView;
+ private final ComplicationLayoutParams mLayoutParams;
+ private final DreamHomeControlsChipViewController mViewController;
+
+ @Inject
+ DreamHomeControlsChipViewHolder(
+ DreamHomeControlsChipViewController dreamHomeControlsChipViewController,
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+ @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
+ ) {
+ mView = view;
+ mLayoutParams = layoutParams;
+ mViewController = dreamHomeControlsChipViewController;
+ mViewController.init();
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public ComplicationLayoutParams getLayoutParams() {
+ return mLayoutParams;
+ }
+ }
+
+ /**
+ * Controls behavior of the dream complication.
+ */
+ static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "DreamHomeControlsCtrl";
+
+ private final ActivityStarter mActivityStarter;
+ private final Context mContext;
+ private final ControlsComponent mControlsComponent;
+
+ @Inject
+ DreamHomeControlsChipViewController(
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+ ActivityStarter activityStarter,
+ Context context,
+ ControlsComponent controlsComponent) {
+ super(view);
+
+ mActivityStarter = activityStarter;
+ mContext = context;
+ mControlsComponent = controlsComponent;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mView.setImageResource(mControlsComponent.getTileImageId());
+ mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+ mView.setOnClickListener(this::onClickHomeControls);
+ }
+
+ @Override
+ protected void onViewDetached() {}
+
+ private void onClickHomeControls(View v) {
+ if (DEBUG) Log.d(TAG, "home controls complication tapped");
+
+ final Intent intent = new Intent(mContext, ControlsActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
+
+ final ActivityLaunchAnimator.Controller controller =
+ v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
+ : null;
+ if (mControlsComponent.getVisibility() == AVAILABLE) {
+ // Controls can be made visible.
+ mActivityStarter.startActivity(intent, true /* dismissShade */, controller,
+ true /* showOverLockscreenWhenLocked */);
+ } else if (mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK) {
+ // Controls can be made visible only after device unlock.
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */,
+ controller);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
rename to packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index be94e50..ac6edba 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_SMARTSPACE_LAYOUT_PARAMS;
import android.content.Context;
import android.os.Parcelable;
@@ -23,21 +25,33 @@
import android.widget.FrameLayout;
import com.android.systemui.CoreStartable;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import java.util.List;
import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
/**
* {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a
* {@link Complication}
*/
public class SmartSpaceComplication implements Complication {
+ private final Provider<SmartSpaceComplicationViewHolder> mViewHolderProvider;
+
+ @Inject
+ public SmartSpaceComplication(Provider<SmartSpaceComplicationViewHolder> viewHolderProvider) {
+ mViewHolderProvider = viewHolderProvider;
+ }
+
+ @Override
+ public ViewHolder createView(ComplicationViewModel model) {
+ return mViewHolderProvider.get();
+ }
+
/**
* {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
* SystemUI.
@@ -89,17 +103,20 @@
}
}
- private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+ static class SmartSpaceComplicationViewHolder implements ViewHolder {
private View mView = null;
- private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
private final DreamSmartspaceController mSmartSpaceController;
private final Context mContext;
+ private final ComplicationLayoutParams mLayoutParams;
+ @Inject
protected SmartSpaceComplicationViewHolder(
Context context,
- DreamSmartspaceController smartSpaceController) {
+ DreamSmartspaceController smartSpaceController,
+ @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) {
mSmartSpaceController = smartSpaceController;
mContext = context;
+ mLayoutParams = layoutParams;
}
@Override
@@ -119,25 +136,7 @@
@Override
public ComplicationLayoutParams getLayoutParams() {
- return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- SMARTSPACE_COMPLICATION_WEIGHT, true);
+ return mLayoutParams;
}
}
-
- private final DreamSmartspaceController mSmartSpaceController;
- private final Context mContext;
-
- @Inject
- public SmartSpaceComplication(Context context,
- DreamSmartspaceController smartSpaceController) {
- mContext = context;
- mSmartSpaceController = smartSpaceController;
- }
-
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
deleted file mode 100644
index 3ab26ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
+++ /dev/null
@@ -1,71 +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.dreams.complication.dagger;
-
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.DreamClockDateComplication;
-
-import javax.inject.Named;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Module for providing {@link DreamClockDateComplication}.
- */
-@Module
-public interface DreamClockDateComplicationModule {
- String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view";
- String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS =
- "clock_date_complication_layout_params";
- // Order weight of insert into parent container
- //TODO(b/217199227): move to a single location.
- int INSERT_ORDER_WEIGHT = 3;
-
- /**
- * Provides the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW)
- static View provideComplicationView(LayoutInflater layoutInflater) {
- return Preconditions.checkNotNull(
- layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date,
- null, false),
- "R.layout.dream_overlay_complication_clock_date did not properly inflated");
- }
-
- /**
- * Provides the layout parameters for the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams() {
- return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
- }
-}
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 3ad7d3d..5250d44 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
@@ -19,12 +19,10 @@
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.TextClock;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.DreamClockTimeComplication;
import javax.inject.Named;
@@ -38,11 +36,6 @@
@Module
public interface DreamClockTimeComplicationModule {
String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
- String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS =
- "clock_time_complication_layout_params";
- // Order weight of insert into parent container
- //TODO(b/217199227): move to a single location.
- int INSERT_ORDER_WEIGHT = 0;
String TAG_WEIGHT = "'wght' ";
int WEIGHT = 200;
@@ -59,18 +52,4 @@
view.setFontVariationSettings(TAG_WEIGHT + WEIGHT);
return view;
}
-
- /**
- * Provides the layout parameters for the complication view.
- */
- @Provides
- @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams() {
- return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_UP,
- INSERT_ORDER_WEIGHT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
new file mode 100644
index 0000000..cf05d2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -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.systemui.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * Responsible for generating dependencies for the {@link DreamHomeControlsComplication}.
+ */
+@Subcomponent(modules = DreamHomeControlsComplicationComponent.DreamHomeControlsModule.class)
+@DreamHomeControlsComplicationComponent.DreamHomeControlsComplicationScope
+public interface DreamHomeControlsComplicationComponent {
+ /**
+ * Creates a view holder for the home controls complication.
+ */
+ DreamHomeControlsComplication.DreamHomeControlsChipViewHolder getViewHolder();
+
+ /**
+ * Scope of the home controls complication.
+ */
+ @Documented
+ @Retention(RUNTIME)
+ @Scope
+ @interface DreamHomeControlsComplicationScope {}
+
+ /**
+ * Factory that generates a {@link DreamHomeControlsComplicationComponent}.
+ */
+ @Subcomponent.Factory
+ interface Factory {
+ DreamHomeControlsComplicationComponent create();
+ }
+
+ /**
+ * Scoped injected values for the {@link DreamHomeControlsComplicationComponent}.
+ */
+ @Module
+ interface DreamHomeControlsModule {
+ String DREAM_HOME_CONTROLS_CHIP_VIEW = "dream_home_controls_chip_view";
+
+ /**
+ * Provides the dream home controls chip view.
+ */
+ @Provides
+ @DreamHomeControlsComplicationScope
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
+ static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
+ return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+ null, false);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index e45437d..eb07238 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -16,17 +16,79 @@
package com.android.systemui.dreams.complication.dagger;
+import android.content.res.Resources;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
import com.android.systemui.dagger.SystemUIBinder;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationLayoutParams;
+
+import javax.inject.Named;
import dagger.Module;
+import dagger.Provides;
/**
* Module for all components with corresponding dream layer complications registered in
* {@link SystemUIBinder}.
*/
@Module(includes = {
- DreamClockDateComplicationModule.class,
DreamClockTimeComplicationModule.class,
+ },
+ subcomponents = {
+ DreamHomeControlsComplicationComponent.class,
})
public interface RegisteredComplicationsModule {
+ String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS = "time_complication_layout_params";
+ String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params";
+ String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
+
+ int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
+ int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0;
+ int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1;
+
+ /**
+ * Provides layout parameters for the clock time complication.
+ */
+ @Provides
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideClockTimeLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ }
+
+ /**
+ * Provides layout parameters for the home controls complication.
+ */
+ @Provides
+ @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
+ return new ComplicationLayoutParams(
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
+ }
+
+ /**
+ * Provides layout parameters for the smartspace complication.
+ */
+ @Provides
+ @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
+ static ComplicationLayoutParams provideSmartspaceLayoutParams() {
+ return new ComplicationLayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
+ true /*snapToGuide*/);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 73bc92d2..c92cf54 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -61,7 +61,7 @@
new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS =
- new BooleanFlag(109, false);
+ new BooleanFlag(109, false, true);
public static final BooleanFlag FSI_REQUIRES_KEYGUARD =
new BooleanFlag(110, false, true);
@@ -92,10 +92,19 @@
* Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
* one.
*/
- public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(206, false);
+ public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(
+ 206,
+ /* default= */ false,
+ /* teamfood= */ true);
public static final BooleanFlag LOCKSCREEN_CUSTOM_CLOCKS = new BooleanFlag(207, false);
+ /**
+ * Flag to enable the usage of the new bouncer data source. This is a refactor of and
+ * eventual replacement of KeyguardBouncer.java.
+ */
+ public static final BooleanFlag MODERN_BOUNCER = new BooleanFlag(208, true);
+
/***************************************/
// 300 - power menu
public static final BooleanFlag POWER_MENU_LITE =
@@ -142,9 +151,6 @@
/***************************************/
// 600- status bar
- public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS =
- new BooleanFlag(601, false);
-
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
@@ -204,6 +210,10 @@
new DeviceConfigBooleanFlag(1102, "record_task_content",
NAMESPACE_WINDOW_MANAGER, false, true);
+ @Keep
+ public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW =
+ new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false);
+
// 1200 - predictive back
@Keep
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
@@ -216,7 +226,11 @@
new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
public static final BooleanFlag NEW_BACK_AFFORDANCE =
- new BooleanFlag(1203, false /* default */, true /* teamfood */);
+ new BooleanFlag(1203, false /* default */, false /* teamfood */);
+
+ // 1300 - screenshots
+
+ public static final BooleanFlag SCREENSHOT_REQUEST_PROCESSOR = new BooleanFlag(1300, false);
// 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 ab30db2..ca65d12 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -947,7 +947,7 @@
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 382323f..c944e50 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -370,7 +370,6 @@
// the animations since they won't be visible.
!notificationShadeWindowController.isLaunchingActivity &&
launcherUnlockController != null &&
- !keyguardStateController.isDismissingFromSwipe &&
// Temporarily disable for foldables since foldable launcher has two first pages,
// which breaks the in-window animation.
!isFoldable(context)
@@ -475,16 +474,34 @@
// If we specifically requested that the surface behind be made visible (vs. it being made
// visible because we're unlocking), then we're in the middle of a swipe-to-unlock touch
- // gesture and the surface behind the keyguard should be made visible.
+ // gesture and the surface behind the keyguard should be made visible so that we can animate
+ // it in.
if (requestedShowSurfaceBehindKeyguard) {
- // Fade in the surface, as long as we're not now flinging. The touch gesture ending in
- // a fling during the time it takes the keyguard exit animation to start is an edge
- // case race condition, and we'll handle it by playing a canned animation on the
- // now-visible surface to finish unlocking.
- if (!keyguardStateController.isFlingingToDismissKeyguard) {
- fadeInSurfaceBehind()
- } else {
+
+ // If we're flinging to dismiss here, it means the touch gesture ended in a fling during
+ // the time it takes the keyguard exit animation to start. This is an edge case race
+ // condition, which we handle by just playing a canned animation on the now-visible
+ // surface behind the keyguard to finish unlocking.
+ if (keyguardStateController.isFlingingToDismissKeyguard) {
playCannedUnlockAnimation()
+ } else if (keyguardStateController.isDismissingFromSwipe
+ && willUnlockWithInWindowLauncherAnimations) {
+ // If we're swiping to unlock to the Launcher, and can play in-window animations,
+ // make the launcher surface fully visible and play the in-window unlock animation
+ // on the launcher icons. System UI will remain locked, using the swipe-to-unlock
+ // translation logic on the launcher window, until the swipe gesture ends (either in
+ // a successful unlock, or an aborted unlock).
+ surfaceBehindAlpha = 1f
+ setSurfaceBehindAppearAmount(1f)
+
+ launcherUnlockController?.playUnlockAnimation(
+ true,
+ UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY,
+ 0 /* startDelay */)
+ } else {
+ // Otherwise, we're swiping in an app and should just fade it in. The swipe gesture
+ // will translate it until the end of the swipe gesture.
+ fadeInSurfaceBehind()
}
} else {
// The surface was made visible since we're unlocking not from a swipe (fingerprint,
@@ -703,10 +720,18 @@
// Otherwise, animate in the surface's scale/transltion.
val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height()
- val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+
+ var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
(1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
MathUtils.clamp(amount, 0f, 1f))
+ // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations, so
+ // don't also scale the window.
+ if (keyguardStateController.isDismissingFromSwipe
+ && willUnlockWithInWindowLauncherAnimations) {
+ scaleFactor = 1f
+ }
+
// Scale up from a point at the center-bottom of the surface.
surfaceBehindMatrix.setScale(
scaleFactor,
@@ -871,6 +896,12 @@
return false
}
+ // If we're swiping to dismiss, the smartspace will be swiped off the top of the screen
+ // so we can't shared element animate it.
+ if (keyguardStateController.isDismissingFromSwipe) {
+ return false
+ }
+
// We don't do the shared element on tablets because they're large and the smartspace has to
// fly across large distances, which is distracting.
if (Utilities.isTablet(context)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7ddce62..b4f40e2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -627,13 +627,13 @@
case TelephonyManager.SIM_STATE_PUK_REQUIRED:
synchronized (KeyguardViewMediator.this) {
mSimWasLocked.append(slotId, true);
+ mPendingPinLock = true;
if (!mShowing) {
if (DEBUG_SIM_STATES) Log.d(TAG,
"INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
+ "showing; need to show keyguard so user can enter sim pin");
doKeyguardLocked(null);
} else {
- mPendingPinLock = true;
resetStateLocked();
}
}
@@ -2985,6 +2985,7 @@
pw.print(" mPendingReset: "); pw.println(mPendingReset);
pw.print(" mPendingLock: "); pw.println(mPendingLock);
pw.print(" wakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
+ pw.print(" mPendingPinLock: "); pw.println(mPendingPinLock);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 165af13..4ff008f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,8 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -66,7 +68,11 @@
KeyguardStatusBarViewComponent.class,
KeyguardStatusViewComponent.class,
KeyguardUserSwitcherComponent.class},
- includes = {FalsingModule.class})
+ includes = {
+ FalsingModule.class,
+ KeyguardRepositoryModule.class,
+ KeyguardUseCaseModule.class,
+ })
public class KeyguardModule {
/**
* Provides our instance of KeyguardViewMediator which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..3202ecb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.DrawableRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.kotlin.getOrNull
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Home controls quick affordance data source. */
+@SysUISingleton
+class HomeControlsKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ @Application context: Context,
+ private val component: ControlsComponent,
+) : KeyguardQuickAffordanceConfig {
+
+ private val appContext = context.applicationContext
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+ stateInternal(component.getControlsListingController().getOrNull())
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent =
+ Intent(appContext, ControlsActivity::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(
+ ControlsUiController.EXTRA_ANIMATE,
+ true,
+ ),
+ canShowWhileLocked = component.canShowWhileLockedSetting.value,
+ )
+ }
+
+ private fun stateInternal(
+ listingController: ControlsListingController?,
+ ): Flow<KeyguardQuickAffordanceConfig.State> {
+ if (listingController == null) {
+ return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ }
+
+ return conflatedCallbackFlow {
+ val callback =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ val favorites: List<StructureInfo>? =
+ component.getControlsController().getOrNull()?.getFavorites()
+
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = component.isEnabled(),
+ hasFavorites = favorites?.isNotEmpty() == true,
+ hasServiceInfos = serviceInfos.isNotEmpty(),
+ iconResourceId = component.getTileImageId(),
+ ),
+ TAG,
+ )
+ }
+ }
+
+ listingController.addCallback(callback)
+
+ awaitClose { listingController.removeCallback(callback) }
+ }
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasFavorites: Boolean,
+ hasServiceInfos: Boolean,
+ @DrawableRes iconResourceId: Int?,
+ ): KeyguardQuickAffordanceConfig.State {
+ return if (isFeatureEnabled && hasFavorites && hasServiceInfos && iconResourceId != null) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithResource(iconResourceId),
+ contentDescriptionResourceId = component.getTileTitleId(),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..67a776e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface that can act as data source for a single quick affordance model. */
+interface KeyguardQuickAffordanceConfig {
+
+ val state: Flow<State>
+
+ fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?
+ ): OnClickedResult
+
+ /**
+ * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
+ * button on the lock-screen).
+ */
+ sealed class State {
+
+ /** No affordance should show up. */
+ object Hidden : State()
+
+ /** An affordance is visible. */
+ data class Visible(
+ /** An icon for the affordance. */
+ val icon: ContainedDrawable,
+ /**
+ * Resource ID for a string to use for the accessibility content description text of the
+ * affordance.
+ */
+ @StringRes val contentDescriptionResourceId: Int,
+ ) : State()
+ }
+
+ sealed class OnClickedResult {
+ /**
+ * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+ * implementation has taken care of the click, the system will do nothing.
+ */
+ object Handled : OnClickedResult()
+
+ /**
+ * Returning this as a result from the [onQuickAffordanceClicked] method means that the
+ * implementation has _not_ taken care of the click and the system should start an activity
+ * using the given [Intent].
+ */
+ data class StartActivity(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ ) : OnClickedResult()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..ea6497e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** QR code scanner quick affordance data source. */
+@SysUISingleton
+class QrCodeScannerKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val controller: QRCodeScannerController,
+) : KeyguardQuickAffordanceConfig {
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ override fun onQRCodeScannerPreferenceChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ // Registering does not push an initial update.
+ trySendWithFailureLogging(state(), "initial state", TAG)
+
+ awaitClose {
+ controller.unregisterQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ controller.removeCallback(callback)
+ }
+ }
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = controller.intent,
+ canShowWhileLocked = true,
+ )
+ }
+
+ private fun state(): KeyguardQuickAffordanceConfig.State {
+ return if (controller.isEnabledForLockScreenButton) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
+ contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..cc5a997
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Quick access wallet quick affordance data source. */
+@SysUISingleton
+class QuickAccessWalletKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val walletController: QuickAccessWalletController,
+ private val activityStarter: ActivityStarter,
+) : KeyguardQuickAffordanceConfig {
+
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = walletController.isWalletEnabled,
+ hasCard = response?.walletCards?.isNotEmpty() == true,
+ tileIcon = walletController.walletClient.tileIcon,
+ ),
+ TAG,
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+ trySendWithFailureLogging(
+ KeyguardQuickAffordanceConfig.State.Hidden,
+ TAG,
+ )
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): KeyguardQuickAffordanceConfig.OnClickedResult {
+ walletController.startQuickAccessUiIntent(
+ activityStarter,
+ animationController,
+ /* hasCard= */ true,
+ )
+ return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasCard: Boolean,
+ tileIcon: Drawable?,
+ ): KeyguardQuickAffordanceConfig.State {
+ return if (isFeatureEnabled && hasCard && tileIcon != null) {
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = ContainedDrawable.WithDrawable(tileIcon),
+ contentDescriptionResourceId = R.string.accessibility_wallet_button,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 0000000..7164215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceConfigs.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.config
+
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Injectable provider of the positioning of the known quick affordance configs. */
+interface KeyguardQuickAffordanceConfigs {
+ fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
+ fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+}
+
+class KeyguardQuickAffordanceConfigsImpl
+@Inject
+constructor(
+ homeControls: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+) : KeyguardQuickAffordanceConfigs {
+ private val configsByPosition =
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWallet,
+ qrCodeScanner,
+ ),
+ )
+ private val configByClass =
+ configsByPosition.values.flatten().associateBy { config -> config::class }
+
+ override fun getAll(
+ position: KeyguardQuickAffordancePosition,
+ ): List<KeyguardQuickAffordanceConfig> {
+ return configsByPosition.getValue(position)
+ }
+
+ override fun get(
+ configClass: KClass<out KeyguardQuickAffordanceConfig>
+ ): KeyguardQuickAffordanceConfig {
+ return configByClass.getValue(configClass)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..43c4fa0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
+interface KeyguardQuickAffordanceRepository {
+ fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
+}
+
+/** Real implementation of [KeyguardQuickAffordanceRepository] */
+@SysUISingleton
+class KeyguardQuickAffordanceRepositoryImpl
+@Inject
+constructor(
+ private val configs: KeyguardQuickAffordanceConfigs,
+) : KeyguardQuickAffordanceRepository {
+
+ /** Returns an observable for the quick affordance model in the given position. */
+ override fun affordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ val configs = configs.getAll(position)
+ return combine(configs.map { config -> config.state }) { states ->
+ val index = states.indexOfFirst { state -> state is State.Visible }
+ val visibleState =
+ if (index != -1) {
+ states[index] as State.Visible
+ } else {
+ null
+ }
+ if (visibleState != null) {
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configs[index]::class,
+ icon = visibleState.icon,
+ contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
new file mode 100644
index 0000000..62cf1a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Defines interface for classes that encapsulate application state for the keyguard. */
+interface KeyguardRepository {
+ /**
+ * Observable for whether the bottom area UI should animate the transition out of doze state.
+ *
+ * To learn more about doze state, please see [isDozing].
+ */
+ val animateBottomAreaDozingTransitions: StateFlow<Boolean>
+
+ /**
+ * Observable for the current amount of alpha that should be used for rendering the bottom area.
+ * UI.
+ */
+ val bottomAreaAlpha: StateFlow<Float>
+
+ /**
+ * Observable of the relative offset of the lock-screen clock from its natural position on the
+ * screen.
+ */
+ val clockPosition: StateFlow<Position>
+
+ /**
+ * Observable for whether the keyguard is showing.
+ *
+ * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
+ * the z-order (which is not really above the system UI window, but rather - the lock-screen
+ * becomes invisible to reveal the "occluding activity").
+ */
+ val isKeyguardShowing: Flow<Boolean>
+
+ /**
+ * Observable for whether we are in doze state.
+ *
+ * Doze state is the same as "Always on Display" or "AOD". It is the state that the device can
+ * enter to conserve battery when the device is locked and inactive.
+ *
+ * Note that it is possible for the system to be transitioning into doze while this flow still
+ * returns `false`. In order to account for that, observers should also use the [dozeAmount]
+ * flow to check if it's greater than `0`
+ */
+ val isDozing: Flow<Boolean>
+
+ /**
+ * Observable for the amount of doze we are currently in.
+ *
+ * While in doze state, this amount can change - driving a cycle of animations designed to avoid
+ * pixel burn-in, etc.
+ *
+ * Also note that the value here may be greater than `0` while [isDozing] is still `false`, this
+ * happens during an animation/transition into doze mode. An observer would be wise to account
+ * for both flows if needed.
+ */
+ val dozeAmount: Flow<Float>
+
+ /** Sets whether the bottom area UI should animate the transition out of doze state. */
+ fun setAnimateDozingTransitions(animate: Boolean)
+
+ /** Sets the current amount of alpha that should be used for rendering the bottom area. */
+ fun setBottomAreaAlpha(alpha: Float)
+
+ /**
+ * Sets the relative offset of the lock-screen clock from its natural position on the screen.
+ */
+ fun setClockPosition(x: Int, y: Int)
+}
+
+/** Encapsulates application state for the keyguard. */
+@SysUISingleton
+class KeyguardRepositoryImpl
+@Inject
+constructor(
+ statusBarStateController: StatusBarStateController,
+ keyguardStateController: KeyguardStateController,
+) : KeyguardRepository {
+ private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+ override val animateBottomAreaDozingTransitions =
+ _animateBottomAreaDozingTransitions.asStateFlow()
+
+ private val _bottomAreaAlpha = MutableStateFlow(1f)
+ override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
+
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition = _clockPosition.asStateFlow()
+
+ override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onKeyguardShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isShowing,
+ TAG,
+ "updated isKeyguardShowing"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isShowing,
+ TAG,
+ "initial isKeyguardShowing"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
+ override val isDozing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozingChanged(isDozing: Boolean) {
+ trySendWithFailureLogging(isDozing, TAG, "updated isDozing")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(statusBarStateController.isDozing, TAG, "initial isDozing")
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+ override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+
+ override fun setAnimateDozingTransitions(animate: Boolean) {
+ _animateBottomAreaDozingTransitions.value = animate
+ }
+
+ override fun setBottomAreaAlpha(alpha: Float) {
+ _bottomAreaAlpha.value = alpha
+ }
+
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
+ companion object {
+ private const val TAG = "KeyguardRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
new file mode 100644
index 0000000..1a5670c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.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.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardRepositoryModule {
+ @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
+
+ @Binds
+ fun keyguardQuickAffordanceRepository(
+ impl: KeyguardQuickAffordanceRepositoryImpl
+ ): KeyguardQuickAffordanceRepository
+
+ @Binds
+ fun keyguardQuickAffordanceConfigs(
+ impl: KeyguardQuickAffordanceConfigsImpl
+ ): KeyguardQuickAffordanceConfigs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
new file mode 100644
index 0000000..c44c2c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.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.keyguard.domain.usecase
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface KeyguardUseCaseModule {
+
+ @Binds
+ fun launchQuickAffordance(
+ impl: LaunchKeyguardQuickAffordanceUseCaseImpl
+ ): LaunchKeyguardQuickAffordanceUseCase
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..3d60399
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+
+/** Defines interface for classes that can launch a quick affordance. */
+interface LaunchKeyguardQuickAffordanceUseCase {
+ operator fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ )
+}
+
+/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
+class LaunchKeyguardQuickAffordanceUseCaseImpl
+@Inject
+constructor(
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+) : LaunchKeyguardQuickAffordanceUseCase {
+ override operator fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @StrongAuthFlags
+ val strongAuthFlags =
+ lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+ val needsToUnlockFirst =
+ when {
+ strongAuthFlags ==
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+ !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+ else -> false
+ }
+ if (needsToUnlockFirst) {
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0 /* delay */,
+ animationController
+ )
+ } else {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
new file mode 100644
index 0000000..ca37727
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether doze state transitions should animate the bottom area */
+class ObserveAnimateBottomAreaTransitionsUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.animateBottomAreaDozingTransitions
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
new file mode 100644
index 0000000..151b704
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the alpha of the bottom area */
+class ObserveBottomAreaAlphaUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Float> {
+ return repository.bottomAreaAlpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
new file mode 100644
index 0000000..02c5737
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
@@ -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 com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.common.domain.model.Position
+import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Use-case for observing the position of the clock. */
+class ObserveClockPositionUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Position> {
+ return repository.clockPosition.map { it.toDomainLayer() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
new file mode 100644
index 0000000..56d6182
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing the amount of doze the system is in. */
+class ObserveDozeAmountUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Float> {
+ return repository.dozeAmount
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
new file mode 100644
index 0000000..1d241d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Use-case for observing whether we are dozing. */
+class ObserveIsDozingUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.isDozing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
new file mode 100644
index 0000000..11af123
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Use-case for observing whether the keyguard is currently being shown.
+ *
+ * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
+ * z-order (which is not really above the system UI window, but rather - the lock-screen becomes
+ * invisible to reveal the "occluding activity").
+ */
+class ObserveIsKeyguardShowingUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(): Flow<Boolean> {
+ return repository.isKeyguardShowing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..eef8ec3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.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.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Use-case for observing the model of a quick affordance in the keyguard. */
+class ObserveKeyguardQuickAffordanceUseCase
+@Inject
+constructor(
+ private val repository: KeyguardQuickAffordanceRepository,
+ private val isDozingUseCase: ObserveIsDozingUseCase,
+ private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
+) {
+ operator fun invoke(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return combine(
+ repository.affordance(position),
+ isDozingUseCase(),
+ isKeyguardShowingUseCase(),
+ ) { affordance, isDozing, isKeyguardShowing ->
+ if (!isDozing && isKeyguardShowing) {
+ affordance
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
new file mode 100644
index 0000000..f8db90f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.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.keyguard.domain.usecase
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
+class OnKeyguardQuickAffordanceClickedUseCase
+@Inject
+constructor(
+ private val configs: KeyguardQuickAffordanceConfigs,
+ private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
+) {
+ operator fun invoke(
+ configKey: KClass<*>,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @Suppress("UNCHECKED_CAST")
+ val config = configs.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
+ when (val result = config.onQuickAffordanceClicked(animationController)) {
+ is OnClickedResult.StartActivity ->
+ launchAffordanceUseCase(
+ intent = result.intent,
+ canShowWhileLocked = result.canShowWhileLocked,
+ animationController = animationController
+ )
+ is OnClickedResult.Handled -> Unit
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
new file mode 100644
index 0000000..8f746e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the updated clock position. */
+class SetClockPositionUseCase
+@Inject
+constructor(
+ private val keyguardRepository: KeyguardRepository,
+) {
+ operator fun invoke(x: Int, y: Int) {
+ keyguardRepository.setClockPosition(x, y)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
new file mode 100644
index 0000000..90be1ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/** Use-case for setting the alpha that the keyguard bottom area should use */
+class SetKeyguardBottomAreaAlphaUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(alpha: Float) {
+ repository.setBottomAreaAlpha(alpha)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
new file mode 100644
index 0000000..007780a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
@@ -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.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+/**
+ * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
+ */
+class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ operator fun invoke(animate: Boolean) {
+ repository.setAnimateDozingTransitions(animate)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.kt
new file mode 100644
index 0000000..09785df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordanceModel.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.keyguard.shared.model
+
+import androidx.annotation.StringRes
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import kotlin.reflect.KClass
+
+/**
+ * Models a "quick affordance" in the keyguard bottom area (for example, a button on the
+ * lock-screen).
+ */
+sealed class KeyguardQuickAffordanceModel {
+
+ /** No affordance should show up. */
+ object Hidden : KeyguardQuickAffordanceModel()
+
+ /** A affordance is visible. */
+ data class Visible(
+ /** Identifier for the affordance this is modeling. */
+ val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ /** An icon for the affordance. */
+ val icon: ContainedDrawable,
+ /**
+ * Resource ID for a string to use for the accessibility content description text of the
+ * affordance.
+ */
+ @StringRes val contentDescriptionResourceId: Int,
+ ) : KeyguardQuickAffordanceModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
new file mode 100644
index 0000000..b71e15d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePosition.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
+enum class KeyguardQuickAffordancePosition {
+ BOTTOM_START,
+ BOTTOM_END,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
new file mode 100644
index 0000000..04d30bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.util.Size
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a keyguard bottom area view to its view-model.
+ *
+ * To use this properly, users should maintain a one-to-one relationship between the [View] and the
+ * view-binding, binding each view only once. It is okay and expected for the same instance of the
+ * view-model to be reused for multiple view/view-binder bindings.
+ */
+object KeyguardBottomAreaViewBinder {
+
+ private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+
+ /**
+ * Defines interface for an object that acts as the binding between the view and its view-model.
+ *
+ * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+ * it is bound.
+ */
+ interface Binding {
+ /**
+ * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
+ * indication areas.
+ */
+ fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
+
+ /** Notifies that device configuration has changed. */
+ fun onConfigurationChanged()
+ }
+
+ /** Binds the view to the view-model, continuing to update the former based on the latter. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: KeyguardBottomAreaViewModel,
+ falsingManager: FalsingManager,
+ ): Binding {
+ val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
+ val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
+ val startButton: ImageView = view.requireViewById(R.id.start_button)
+ val endButton: ImageView = view.requireViewById(R.id.end_button)
+ val overlayContainer: View = view.requireViewById(R.id.overlay_container)
+ val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
+ val indicationTextBottom: TextView =
+ view.requireViewById(R.id.keyguard_indication_text_bottom)
+
+ view.clipChildren = false
+ view.clipToPadding = false
+
+ val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ combine(viewModel.startButton, viewModel.animateButtonReveal) {
+ buttonModel,
+ animateReveal ->
+ Pair(buttonModel, animateReveal)
+ }
+ .collect { (buttonModel, animateReveal) ->
+ updateButton(
+ view = startButton,
+ viewModel = buttonModel,
+ animateReveal = animateReveal,
+ falsingManager = falsingManager,
+ )
+ }
+ }
+
+ launch {
+ combine(viewModel.endButton, viewModel.animateButtonReveal) {
+ buttonModel,
+ animateReveal ->
+ Pair(buttonModel, animateReveal)
+ }
+ .collect { (buttonModel, animateReveal) ->
+ updateButton(
+ view = endButton,
+ viewModel = buttonModel,
+ animateReveal = animateReveal,
+ falsingManager = falsingManager,
+ )
+ }
+ }
+
+ launch {
+ viewModel.isOverlayContainerVisible.collect { isVisible ->
+ overlayContainer.visibility =
+ if (isVisible) {
+ View.VISIBLE
+ } else {
+ View.INVISIBLE
+ }
+ }
+ }
+
+ launch {
+ viewModel.alpha.collect { alpha ->
+ view.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+
+ ambientIndicationArea?.alpha = alpha
+ indicationArea.alpha = alpha
+ startButton.alpha = alpha
+ endButton.alpha = alpha
+ }
+ }
+
+ launch {
+ viewModel.indicationAreaTranslationX.collect { translationX ->
+ indicationArea.translationX = translationX
+ ambientIndicationArea?.translationX = translationX
+ }
+ }
+
+ launch {
+ combine(
+ viewModel.isIndicationAreaPadded,
+ configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+ ) { isPadded, paddingIfPaddedPx ->
+ if (isPadded) {
+ paddingIfPaddedPx
+ } else {
+ 0
+ }
+ }
+ .collect { paddingPx ->
+ indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
+ }
+ }
+
+ launch {
+ configurationBasedDimensions
+ .map { it.defaultBurnInPreventionYOffsetPx }
+ .flatMapLatest { defaultBurnInOffsetY ->
+ viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+ }
+ .collect { translationY ->
+ indicationArea.translationY = translationY
+ ambientIndicationArea?.translationY = translationY
+ }
+ }
+
+ launch {
+ configurationBasedDimensions.collect { dimensions ->
+ indicationText.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+ indicationTextBottom.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+
+ startButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
+ }
+ endButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
+ }
+ }
+ }
+ }
+ }
+
+ return object : Binding {
+ override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
+ return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() }
+ }
+
+ override fun onConfigurationChanged() {
+ configurationBasedDimensions.value = loadFromResources(view)
+ }
+ }
+ }
+
+ private fun updateButton(
+ view: ImageView,
+ viewModel: KeyguardQuickAffordanceViewModel,
+ animateReveal: Boolean,
+ falsingManager: FalsingManager,
+ ) {
+ if (!viewModel.isVisible) {
+ view.isVisible = false
+ return
+ }
+
+ if (!view.isVisible) {
+ view.isVisible = true
+ if (animateReveal) {
+ view.alpha = 0f
+ view.translationY = view.height / 2f
+ view
+ .animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
+ .start()
+ }
+ }
+
+ when (viewModel.icon) {
+ is ContainedDrawable.WithDrawable -> view.setImageDrawable(viewModel.icon.drawable)
+ is ContainedDrawable.WithResource -> view.setImageResource(viewModel.icon.resourceId)
+ }
+
+ view.drawable.setTint(
+ Utils.getColorAttrDefaultColor(
+ view.context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ )
+ view.backgroundTintList =
+ Utils.getColorAttr(view.context, com.android.internal.R.attr.colorSurface)
+
+ view.contentDescription = view.context.getString(viewModel.contentDescriptionResourceId)
+ view.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return@setOnClickListener
+ }
+
+ if (viewModel.configKey != null) {
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = viewModel.configKey,
+ animationController = ActivityLaunchAnimator.Controller.fromView(view),
+ )
+ )
+ }
+ }
+ }
+
+ private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+ return ConfigurationBasedDimensions(
+ defaultBurnInPreventionYOffsetPx =
+ view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+ indicationAreaPaddingPx =
+ view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
+ indicationTextSizePx =
+ view.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.text_size_small_material,
+ ),
+ buttonSizePx =
+ Size(
+ view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+ view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ),
+ )
+ }
+
+ private data class ConfigurationBasedDimensions(
+ val defaultBurnInPreventionYOffsetPx: Int,
+ val indicationAreaPaddingPx: Int,
+ val indicationTextSizePx: Int,
+ val buttonSizePx: Size,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
new file mode 100644
index 0000000..4b69a81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the keyguard bottom area view */
+class KeyguardBottomAreaViewModel
+@Inject
+constructor(
+ private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
+ private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
+ observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
+ observeIsDozingUseCase: ObserveIsDozingUseCase,
+ observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
+ private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
+ observeClockPositionUseCase: ObserveClockPositionUseCase,
+ private val burnInHelperWrapper: BurnInHelperWrapper,
+) {
+ /** An observable for the view-model of the "start button" quick affordance. */
+ val startButton: Flow<KeyguardQuickAffordanceViewModel> =
+ button(KeyguardQuickAffordancePosition.BOTTOM_START)
+ /** An observable for the view-model of the "end button" quick affordance. */
+ val endButton: Flow<KeyguardQuickAffordanceViewModel> =
+ button(KeyguardQuickAffordancePosition.BOTTOM_END)
+ /**
+ * An observable for whether the next time a quick action button becomes visible, it should
+ * animate.
+ */
+ val animateButtonReveal: Flow<Boolean> =
+ observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
+ /** An observable for whether the overlay container should be visible. */
+ val isOverlayContainerVisible: Flow<Boolean> =
+ observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+ /** An observable for the alpha level for the entire bottom area. */
+ val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+ /** An observable for whether the indication area should be padded. */
+ val isIndicationAreaPadded: Flow<Boolean> =
+ combine(startButton, endButton) { startButtonModel, endButtonModel ->
+ startButtonModel.isVisible || endButtonModel.isVisible
+ }
+ .distinctUntilChanged()
+ /** An observable for the x-offset by which the indication area should be translated. */
+ val indicationAreaTranslationX: Flow<Float> =
+ observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+
+ /** Returns an observable for the y-offset by which the indication area should be translated. */
+ fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+ return observeDozeAmountUseCase()
+ .map { dozeAmount ->
+ dozeAmount *
+ (burnInHelperWrapper.burnInOffset(
+ /* amplitude = */ defaultBurnInOffset * 2,
+ /* xAxis= */ false,
+ ) - defaultBurnInOffset)
+ }
+ .distinctUntilChanged()
+ }
+
+ private fun button(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceViewModel> {
+ return observeQuickAffordanceUseCase(position)
+ .map { model -> model.toViewModel() }
+ .distinctUntilChanged()
+ }
+
+ private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+ return when (this) {
+ is KeyguardQuickAffordanceModel.Visible ->
+ KeyguardQuickAffordanceViewModel(
+ configKey = configKey,
+ isVisible = true,
+ icon = icon,
+ contentDescriptionResourceId = contentDescriptionResourceId,
+ onClicked = { parameters ->
+ onQuickAffordanceClickedUseCase(
+ configKey = parameters.configKey,
+ animationController = parameters.animationController,
+ )
+ },
+ )
+ is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
new file mode 100644
index 0000000..2417998
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.annotation.StringRes
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import kotlin.reflect.KClass
+
+/** Models the UI state of a keyguard quick affordance button. */
+data class KeyguardQuickAffordanceViewModel(
+ val configKey: KClass<*>? = null,
+ val isVisible: Boolean = false,
+ val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
+ @StringRes val contentDescriptionResourceId: Int = 0,
+ val onClicked: (OnClickedParameters) -> Unit = {},
+) {
+ data class OnClickedParameters(
+ val configKey: KClass<*>,
+ val animationController: ActivityLaunchAnimator.Controller?,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
new file mode 100644
index 0000000..e364918
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -0,0 +1,183 @@
+/*
+ * 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.lifecycle
+
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.annotation.MainThread
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.util.Assert
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/**
+ * Runs the given [block] every time the [View] becomes attached (or immediately after calling this
+ * function, if the view was already attached), automatically canceling the work when the `View`
+ * becomes detached.
+ *
+ * Only use from the main thread.
+ *
+ * When [block] is run, it is run in the context of a [ViewLifecycleOwner] which the caller can use
+ * to launch jobs, with confidence that the jobs will be properly canceled when the view is
+ * detached.
+ *
+ * The [block] may be run multiple times, running once per every time the view is attached. Each
+ * time the block is run for a new attachment event, the [ViewLifecycleOwner] provided will be a
+ * fresh one.
+ *
+ * @param coroutineContext An optional [CoroutineContext] to replace the dispatcher [block] is
+ * invoked on.
+ * @param block The block of code that should be run when the view becomes attached. It can end up
+ * being invoked multiple times if the view is reattached after being detached.
+ * @return A [DisposableHandle] to invoke when the caller of the function destroys its [View] and is
+ * no longer interested in the [block] being run the next time its attached. Calling this is an
+ * optional optimization as the logic will be properly cleaned up and destroyed each time the view
+ * is detached. Using this is not *thread-safe* and should only be used on the main thread.
+ */
+@MainThread
+fun View.repeatWhenAttached(
+ coroutineContext: CoroutineContext = EmptyCoroutineContext,
+ block: suspend LifecycleOwner.(View) -> Unit,
+): DisposableHandle {
+ Assert.isMainThread()
+ val view = this
+ // The suspend block will run on the app's main thread unless the caller supplies a different
+ // dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
+ // default behavior. Instead, we want it to run on the view's UI thread since the user will
+ // presumably want to call view methods that require being called from said UI thread.
+ val lifecycleCoroutineContext = Dispatchers.Main + coroutineContext
+ var lifecycleOwner: ViewLifecycleOwner? = null
+ val onAttachListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View?) {
+ Assert.isMainThread()
+ lifecycleOwner?.onDestroy()
+ lifecycleOwner =
+ createLifecycleOwnerAndRun(
+ view,
+ lifecycleCoroutineContext,
+ block,
+ )
+ }
+
+ override fun onViewDetachedFromWindow(v: View?) {
+ lifecycleOwner?.onDestroy()
+ lifecycleOwner = null
+ }
+ }
+
+ addOnAttachStateChangeListener(onAttachListener)
+ if (view.isAttachedToWindow) {
+ lifecycleOwner =
+ createLifecycleOwnerAndRun(
+ view,
+ lifecycleCoroutineContext,
+ block,
+ )
+ }
+
+ return object : DisposableHandle {
+ override fun dispose() {
+ Assert.isMainThread()
+
+ lifecycleOwner?.onDestroy()
+ lifecycleOwner = null
+ view.removeOnAttachStateChangeListener(onAttachListener)
+ }
+ }
+}
+
+private fun createLifecycleOwnerAndRun(
+ view: View,
+ coroutineContext: CoroutineContext,
+ block: suspend LifecycleOwner.(View) -> Unit,
+): ViewLifecycleOwner {
+ return ViewLifecycleOwner(view).apply {
+ onCreate()
+ lifecycleScope.launch(coroutineContext) { block(view) }
+ }
+}
+
+/**
+ * A [LifecycleOwner] for a [View] for exclusive use by the [repeatWhenAttached] extension function.
+ *
+ * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
+ * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is called,
+ * the implementation monitors window state in the following way
+ *
+ * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
+ * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
+ * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
+ *
+ * Or in table format:
+ * ```
+ * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
+ * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
+ * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
+ * │ Not attached │ Any │ N/A │
+ * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
+ * │ │ Not visible │ Any │ CREATED │
+ * │ ├───────────────────┼──────────────┼─────────────────┤
+ * │ Attached │ │ No focus │ STARTED │
+ * │ │ Visible ├──────────────┼─────────────────┤
+ * │ │ │ Has focus │ RESUMED │
+ * └───────────────┴───────────────────┴──────────────┴─────────────────┘
+ * ```
+ */
+private class ViewLifecycleOwner(
+ private val view: View,
+) : LifecycleOwner {
+
+ private val windowVisibleListener =
+ ViewTreeObserver.OnWindowVisibilityChangeListener { updateState() }
+ private val windowFocusListener = ViewTreeObserver.OnWindowFocusChangeListener { updateState() }
+
+ private val registry = LifecycleRegistry(this)
+
+ fun onCreate() {
+ registry.currentState = Lifecycle.State.CREATED
+ view.viewTreeObserver.addOnWindowVisibilityChangeListener(windowVisibleListener)
+ view.viewTreeObserver.addOnWindowFocusChangeListener(windowFocusListener)
+ updateState()
+ }
+
+ fun onDestroy() {
+ view.viewTreeObserver.removeOnWindowVisibilityChangeListener(windowVisibleListener)
+ view.viewTreeObserver.removeOnWindowFocusChangeListener(windowFocusListener)
+ registry.currentState = Lifecycle.State.DESTROYED
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return registry
+ }
+
+ private fun updateState() {
+ registry.currentState =
+ when {
+ view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
+ !view.hasWindowFocus() -> Lifecycle.State.STARTED
+ else -> Lifecycle.State.RESUMED
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
deleted file mode 100644
index 55c7ac9..0000000
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.android.systemui.lifecycle
-
-import android.view.View
-import android.view.ViewTreeObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-
-/**
- * [LifecycleOwner] for Window-added Views.
- *
- * These are [View] instances that are added to a `Window` using the `WindowManager` API.
- *
- * This implementation goes to:
- * * The <b>CREATED</b> `Lifecycle.State` when the view gets attached to the window but the window
- * is not yet visible
- * * The <b>STARTED</b> `Lifecycle.State` when the view is attached to the window and the window is
- * visible
- * * The <b>RESUMED</b> `Lifecycle.State` when the view is attached to the window and the window is
- * visible and the window receives focus
- *
- * In table format:
- * ```
- * | ----------------------------------------------------------------------------- |
- * | View attached to window | Window visible | Window has focus | Lifecycle state |
- * | ----------------------------------------------------------------------------- |
- * | not attached | Any | INITIALIZED |
- * | ----------------------------------------------------------------------------- |
- * | | not visible | Any | CREATED |
- * | ----------------------------------------------------- |
- * | attached | | not focused | STARTED |
- * | | is visible |----------------------------------- |
- * | | | has focus | RESUMED |
- * | ----------------------------------------------------------------------------- |
- * ```
- * ### Notes
- * * [dispose] must be invoked when the [LifecycleOwner] is done and won't be reused
- * * It is always better for [LifecycleOwner] implementations to be more explicit than just
- * listening to the state of the `Window`. E.g. if the code that added the `View` to the `Window`
- * already has access to the correct state to know when that `View` should become visible and when
- * it is ready to receive interaction from the user then it already knows when to move to `STARTED`
- * and `RESUMED`, respectively. In that case, it's better to implement your own `LifecycleOwner`
- * instead of relying on the `Window` callbacks.
- */
-class WindowAddedViewLifecycleOwner
-@JvmOverloads
-constructor(
- private val view: View,
- registryFactory: (LifecycleOwner) -> LifecycleRegistry = { LifecycleRegistry(it) },
-) : LifecycleOwner {
-
- private val windowAttachListener =
- object : ViewTreeObserver.OnWindowAttachListener {
- override fun onWindowAttached() {
- updateCurrentState()
- }
-
- override fun onWindowDetached() {
- updateCurrentState()
- }
- }
- private val windowFocusListener =
- ViewTreeObserver.OnWindowFocusChangeListener { updateCurrentState() }
- private val windowVisibilityListener =
- ViewTreeObserver.OnWindowVisibilityChangeListener { updateCurrentState() }
-
- private val registry = registryFactory(this)
-
- init {
- setCurrentState(Lifecycle.State.INITIALIZED)
-
- with(view.viewTreeObserver) {
- addOnWindowAttachListener(windowAttachListener)
- addOnWindowVisibilityChangeListener(windowVisibilityListener)
- addOnWindowFocusChangeListener(windowFocusListener)
- }
-
- updateCurrentState()
- }
-
- override fun getLifecycle(): Lifecycle {
- return registry
- }
-
- /**
- * Disposes of this [LifecycleOwner], performing proper clean-up.
- *
- * <p>Invoke this when the instance is finished and won't be reused.
- */
- fun dispose() {
- with(view.viewTreeObserver) {
- removeOnWindowAttachListener(windowAttachListener)
- removeOnWindowVisibilityChangeListener(windowVisibilityListener)
- removeOnWindowFocusChangeListener(windowFocusListener)
- }
- }
-
- private fun updateCurrentState() {
- val state =
- when {
- !view.isAttachedToWindow -> Lifecycle.State.INITIALIZED
- view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
- !view.hasWindowFocus() -> Lifecycle.State.STARTED
- else -> Lifecycle.State.RESUMED
- }
- setCurrentState(state)
- }
-
- private fun setCurrentState(state: Lifecycle.State) {
- if (registry.currentState != state) {
- registry.currentState = state
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index db446c3..dc23684d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -20,14 +20,19 @@
import android.util.Log
import com.android.systemui.log.dagger.LogModule
import com.android.systemui.util.collection.RingBuffer
+import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.text.SimpleDateFormat
+import java.util.Arrays.stream
import java.util.Locale
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.BlockingQueue
import kotlin.concurrent.thread
import kotlin.math.max
+const val UNBOUNDED_STACK_TRACE = -1
+const val NESTED_TRACE_DEPTH = 10
+
/**
* A simple ring buffer of recyclable log messages
*
@@ -69,12 +74,18 @@
* @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
* out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
* the maximum, it behaves like a ring buffer.
+ * @param rootStackTraceDepth The number of stack trace elements to be logged for an exception when
+ * the logBuffer is dumped. Defaulted to -1 [UNBOUNDED_STACK_TRACE] to print the entire stack trace.
+ * @param nestedStackTraceDepth The number of stack trace elements to be logged for any nested
+ * exceptions present in [Throwable.cause] or [Throwable.suppressedExceptions].
*/
class LogBuffer @JvmOverloads constructor(
private val name: String,
private val maxSize: Int,
private val logcatEchoTracker: LogcatEchoTracker,
- private val systrace: Boolean = true
+ private val systrace: Boolean = true,
+ private val rootStackTraceDepth: Int = UNBOUNDED_STACK_TRACE,
+ private val nestedStackTraceDepth: Int = NESTED_TRACE_DEPTH,
) {
private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
@@ -107,11 +118,11 @@
* May also log the message to logcat if echoing is enabled for this buffer or tag.
*
* The actual string of the log message is not constructed until it is needed. To accomplish
- * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
- * obtained and is passed to the [initializer]. The initializer stores any relevant data on the
- * message's fields. The message is then inserted into the buffer where it waits until it is
- * either pushed out by newer messages or it needs to printed. If and when this latter moment
- * occurs, the [printer] function is called on the message. It reads whatever data the
+ * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
+ * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
+ * on the message's fields. The message is then inserted into the buffer where it waits until it
+ * is either pushed out by newer messages or it needs to printed. If and when this latter moment
+ * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
* initializer stored and converts it to a human-readable log message.
*
* @param tag A string of at most 23 characters, used for grouping logs into categories or
@@ -120,27 +131,49 @@
* echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
* INFO level should be reserved for information that other parts of the system might care
* about, leaving the specifics of code's day-to-day operations to DEBUG.
- * @param initializer A function that will be called immediately to store relevant data on the
- * log message. The value of `this` will be the LogMessage to be initialized.
- * @param printer A function that will be called if and when the message needs to be dumped to
- * logcat or a bug report. It should read the data stored by the initializer and convert it to
- * a human-readable string. The value of `this` will be the LogMessage to be printed.
- * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
- * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
- * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ * @param messageInitializer A function that will be called immediately to store relevant data
+ * on the log message. The value of `this` will be the LogMessage to be initialized.
+ * @param messagePrinter A function that will be called if and when the message needs to be
+ * dumped to logcat or a bug report. It should read the data stored by the initializer and
+ * convert it to a human-readable string. The value of `this` will be the LogMessage to be
+ * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and
+ * NEVER any variables in its enclosing scope. Otherwise, the runtime will need to allocate a
+ * new instance of the printer for each call, thwarting our attempts at avoiding any sort of
+ * allocation.
+ * @param exception Provide any exception that need to be logged. This is saved as
+ * [LogMessage.exception]
*/
+ @JvmOverloads
inline fun log(
- tag: String,
- level: LogLevel,
- initializer: LogMessage.() -> Unit,
- noinline printer: LogMessage.() -> String
+ tag: String,
+ level: LogLevel,
+ messageInitializer: MessageInitializer,
+ noinline messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
) {
- val message = obtain(tag, level, printer)
- initializer(message)
+ val message = obtain(tag, level, messagePrinter, exception)
+ messageInitializer(message)
commit(message)
}
/**
+ * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
+ *
+ * May also log the message to logcat if echoing is enabled for this buffer or tag. This is for
+ * simpler use-cases where [message] is a compile time string constant. For use-cases where the
+ * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in
+ * an initializer and a message printer.
+ *
+ * Log buffers are limited by the number of entries, so logging more frequently
+ * will limit the time window that the LogBuffer covers in a bug report. Richer logs, on the
+ * other hand, make a bug report more actionable, so using the [log] with a messagePrinter to
+ * add more detail to every log may do more to improve overall logging than adding more logs
+ * with this method.
+ */
+ fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) =
+ log(tag, level, {str1 = message}, { str1!! })
+
+ /**
* You should call [log] instead of this method.
*
* Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size,
@@ -151,15 +184,16 @@
*/
@Synchronized
fun obtain(
- tag: String,
- level: LogLevel,
- printer: (LogMessage) -> String
- ): LogMessageImpl {
+ tag: String,
+ level: LogLevel,
+ messagePrinter: MessagePrinter,
+ exception: Throwable? = null,
+ ): LogMessage {
if (!mutable) {
return FROZEN_MESSAGE
}
val message = buffer.advance()
- message.reset(tag, level, System.currentTimeMillis(), printer)
+ message.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
return message
}
@@ -230,19 +264,79 @@
}
}
- private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
- pw.print(DATE_FORMAT.format(message.timestamp))
+ private fun dumpMessage(
+ message: LogMessage,
+ pw: PrintWriter
+ ) {
+ val formattedTimestamp = DATE_FORMAT.format(message.timestamp)
+ val shortLevel = message.level.shortString
+ val messageToPrint = message.messagePrinter(message)
+ val tag = message.tag
+ printLikeLogcat(pw, formattedTimestamp, shortLevel, tag, messageToPrint)
+ message.exception?.let { ex ->
+ printException(
+ pw,
+ formattedTimestamp,
+ shortLevel,
+ ex,
+ tag,
+ stackTraceDepth = rootStackTraceDepth)
+ }
+ }
+
+ private fun printException(
+ pw: PrintWriter,
+ timestamp: String,
+ level: String,
+ exception: Throwable,
+ tag: String,
+ exceptionMessagePrefix: String = "",
+ stackTraceDepth: Int = UNBOUNDED_STACK_TRACE
+ ) {
+ val message = "$exceptionMessagePrefix$exception"
+ printLikeLogcat(pw, timestamp, level, tag, message)
+ var stacktraceStream = stream(exception.stackTrace)
+ if (stackTraceDepth != UNBOUNDED_STACK_TRACE) {
+ stacktraceStream = stacktraceStream.limit(stackTraceDepth.toLong())
+ }
+ stacktraceStream.forEach { line ->
+ printLikeLogcat(pw, timestamp, level, tag, "\tat $line")
+ }
+ exception.cause?.let { cause ->
+ printException(pw, timestamp, level, cause, tag, "Caused by: ", nestedStackTraceDepth)
+ }
+ exception.suppressedExceptions.forEach { suppressed ->
+ printException(
+ pw,
+ timestamp,
+ level,
+ suppressed,
+ tag,
+ "Suppressed: ",
+ nestedStackTraceDepth
+ )
+ }
+ }
+
+ private fun printLikeLogcat(
+ pw: PrintWriter,
+ formattedTimestamp: String,
+ shortLogLevel: String,
+ tag: String,
+ message: String
+ ) {
+ pw.print(formattedTimestamp)
pw.print(" ")
- pw.print(message.level.shortString)
+ pw.print(shortLogLevel)
pw.print(" ")
- pw.print(message.tag)
+ pw.print(tag)
pw.print(": ")
- pw.println(message.printer(message))
+ pw.println(message)
}
private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
if (toLogcat || toSystrace) {
- val strMessage = message.printer(message)
+ val strMessage = message.messagePrinter(message)
if (toSystrace) {
echoToSystrace(message, strMessage)
}
@@ -259,16 +353,22 @@
private fun echoToLogcat(message: LogMessage, strMessage: String) {
when (message.level) {
- LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
- LogLevel.DEBUG -> Log.d(message.tag, strMessage)
- LogLevel.INFO -> Log.i(message.tag, strMessage)
- LogLevel.WARNING -> Log.w(message.tag, strMessage)
- LogLevel.ERROR -> Log.e(message.tag, strMessage)
- LogLevel.WTF -> Log.wtf(message.tag, strMessage)
+ LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+ LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+ LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+ LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+ LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+ LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
}
}
}
+/**
+ * A function that will be called immediately to store relevant data on the log message. The value
+ * of `this` will be the LogMessage to be initialized.
+ */
+typealias MessageInitializer = LogMessage.() -> Unit
+
private const val TAG = "LogBuffer"
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
-private val FROZEN_MESSAGE = LogMessageImpl.create()
\ No newline at end of file
+private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
index 2a0a2aa6..987aea8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt
@@ -25,7 +25,7 @@
*
* When a message is logged, the code doing the logging stores data in one or more of the generic
* fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the
- * [printer] function reads the data stored in the generic fields and converts that to a human-
+ * [messagePrinter] function reads the data stored in the generic fields and converts that to a human-
* readable string. Thus, for every log type there must be a specialized initializer function that
* stores data specific to that log type and a specialized printer function that prints that data.
*
@@ -35,7 +35,8 @@
val level: LogLevel
val tag: String
val timestamp: Long
- val printer: LogMessage.() -> String
+ val messagePrinter: MessagePrinter
+ val exception: Throwable?
var str1: String?
var str2: String?
@@ -50,3 +51,13 @@
var bool3: Boolean
var bool4: Boolean
}
+
+/**
+ * A function that will be called if and when the message needs to be dumped to
+ * logcat or a bug report. It should read the data stored by the initializer and convert it to
+ * a human-readable string. The value of `this` will be the LogMessage to be printed.
+ * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any
+ * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance
+ * of the printer for each call, thwarting our attempts at avoiding any sort of allocation.
+ */
+typealias MessagePrinter = LogMessage.() -> String
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
index d33ac4b..4dd6f65 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt
@@ -23,7 +23,8 @@
override var level: LogLevel,
override var tag: String,
override var timestamp: Long,
- override var printer: LogMessage.() -> String,
+ override var messagePrinter: MessagePrinter,
+ override var exception: Throwable?,
override var str1: String?,
override var str2: String?,
override var str3: String?,
@@ -35,19 +36,21 @@
override var bool1: Boolean,
override var bool2: Boolean,
override var bool3: Boolean,
- override var bool4: Boolean
+ override var bool4: Boolean,
) : LogMessage {
fun reset(
tag: String,
level: LogLevel,
timestamp: Long,
- renderer: LogMessage.() -> String
+ renderer: MessagePrinter,
+ exception: Throwable? = null,
) {
this.level = level
this.tag = tag
this.timestamp = timestamp
- this.printer = renderer
+ this.messagePrinter = renderer
+ this.exception = exception
str1 = null
str2 = null
str3 = null
@@ -68,7 +71,8 @@
LogLevel.DEBUG,
DEFAULT_TAG,
0,
- DEFAULT_RENDERER,
+ DEFAULT_PRINTER,
+ null,
null,
null,
null,
@@ -86,4 +90,4 @@
}
private const val DEFAULT_TAG = "UnknownTag"
-private val DEFAULT_RENDERER: LogMessage.() -> String = { "Unknown message: $this" }
+private val DEFAULT_PRINTER: MessagePrinter = { "Unknown message: $this" }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
new file mode 100644
index 0000000..323ee21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardUpdateMonitorLog.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.log.dagger
+
+/** A [com.android.systemui.log.LogBuffer] for KeyguardUpdateMonitor. */
+annotation class KeyguardUpdateMonitorLog
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 d0da18a..c858bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -255,6 +255,16 @@
return factory.create("MediaCarouselCtlrLog", 20);
}
+ /**
+ * Provides a {@link LogBuffer} for use in the status bar connectivity pipeline
+ */
+ @Provides
+ @SysUISingleton
+ @StatusBarConnectivityLog
+ public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
+ return factory.create("StatusBarConnectivityLog", 64);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
@@ -277,4 +287,14 @@
public static LogBuffer provideStatusBarNetworkControllerBuffer(LogBufferFactory factory) {
return factory.create("StatusBarNetworkControllerLog", 20);
}
+
+ /**
+ * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardUpdateMonitorLog
+ public static LogBuffer provideKeyguardUpdateMonitorLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyguardUpdateMonitorLog", 200);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
new file mode 100644
index 0000000..f03fbcb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarConnectivityLog.java
@@ -0,0 +1,36 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for events processed by {@link ConnectivityInfoProcessor}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface StatusBarConnectivityLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 6802da3..53abd99 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -22,6 +22,7 @@
import android.os.Binder
import android.os.Bundle
import android.os.IBinder
+import android.os.ResultReceiver
import android.view.View
import com.android.internal.app.ChooserActivity
import com.android.internal.app.chooser.NotSelectableTargetInfo
@@ -103,17 +104,42 @@
}
private fun onTargetActivityLaunched(launchToken: IBinder) {
- val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
- val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
+ if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
+ // The client requested to return the result in the result receiver instead of
+ // activity result, let's send the media projection to the result receiver
+ val resultReceiver = intent
+ .getParcelableExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+ ResultReceiver::class.java) as ResultReceiver
+ val captureRegion = MediaProjectionCaptureTarget(launchToken)
+ val data = Bundle().apply {
+ putParcelable(KEY_CAPTURE_TARGET, captureRegion)
+ }
+ resultReceiver.send(RESULT_OK, data)
+ } else {
+ // Return the media projection instance as activity result
+ val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
+ val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
- projection.launchCookie = launchToken
+ projection.launchCookie = launchToken
- val intent = Intent()
- intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
- setResult(RESULT_OK, intent)
- setForceSendResultForMediaProjection()
+ val intent = Intent()
+ intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
+ setResult(RESULT_OK, intent)
+ setForceSendResultForMediaProjection()
+ }
+
finish()
}
override fun shouldGetOnlyDefaultActivities() = false
+
+ companion object {
+ /**
+ * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra
+ * the activity will send the [CaptureRegion] to the result receiver
+ * instead of returning media projection instance through activity result.
+ */
+ const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
+ const val KEY_CAPTURE_TARGET = "capture_region"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt
new file mode 100644
index 0000000..fbf9294
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.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.media
+
+import android.os.IBinder
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * Class that represents an area that should be captured.
+ * Currently it has only a launch cookie that represents a task but
+ * we potentially could add more identifiers e.g. for a pair of tasks.
+ */
+data class MediaProjectionCaptureTarget(
+ val launchCookie: IBinder?
+): Parcelable {
+
+ constructor(parcel: Parcel) : this(parcel.readStrongBinder())
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeStrongBinder(launchCookie)
+ }
+
+ override fun describeContents(): Int = 0
+
+ companion object CREATOR : Parcelable.Creator<MediaProjectionCaptureTarget> {
+ override fun createFromParcel(parcel: Parcel): MediaProjectionCaptureTarget {
+ return MediaProjectionCaptureTarget(parcel)
+ }
+
+ override fun newArray(size: Int): Array<MediaProjectionCaptureTarget?> {
+ return arrayOfNulls(size)
+ }
+ }
+}
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 1d773a1..7e263d8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -101,6 +101,7 @@
private int mListMaxHeight;
private WallpaperColors mWallpaperColors;
private Executor mExecutor;
+ private boolean mShouldLaunchLeBroadcastDialog;
MediaOutputBaseAdapter mAdapter;
@@ -399,7 +400,9 @@
}
public void handleLeBroadcastStarted() {
- startLeBroadcastDialog();
+ // Waiting for the onBroadcastMetadataChanged. The UI launchs the broadcast dialog when
+ // the metadata is ready.
+ mShouldLaunchLeBroadcastDialog = true;
}
public void handleLeBroadcastStartFailed() {
@@ -409,10 +412,15 @@
}
public void handleLeBroadcastMetadataChanged() {
+ if (mShouldLaunchLeBroadcastDialog) {
+ startLeBroadcastDialog();
+ mShouldLaunchLeBroadcastDialog = false;
+ }
refresh();
}
public void handleLeBroadcastStopped() {
+ mShouldLaunchLeBroadcastDialog = false;
refresh();
}
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 7e58a83..27095b3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -62,6 +62,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
import com.android.settingslib.media.LocalMediaManager;
@@ -816,7 +817,9 @@
Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null");
return "";
}
- return broadcast.getLocalBluetoothLeBroadcastMetaData().convertToQrCodeString();
+ final LocalBluetoothLeBroadcastMetadata metadata =
+ broadcast.getLocalBluetoothLeBroadcastMetaData();
+ return metadata != null ? metadata.convertToQrCodeString() : "";
}
boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
index e95976f..a29c588 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
@@ -27,4 +27,4 @@
fun getTimeoutMs(): Long
}
-const val DEFAULT_TIMEOUT_MILLIS = 4000L
+const val DEFAULT_TIMEOUT_MILLIS = 10000L
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 c9fce79..5f478ce 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
@@ -26,18 +26,18 @@
import android.os.SystemClock
import android.util.Log
import android.view.LayoutInflater
-import android.view.MotionEvent
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import androidx.annotation.CallSuper
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
@@ -52,17 +52,16 @@
* display the chip in a certain state, since they receive <T> in [updateChipView].
*/
abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
- internal val context: Context,
- internal val logger: MediaTttLogger,
- internal val windowManager: WindowManager,
- private val viewUtil: ViewUtil,
- @Main private val mainExecutor: DelayableExecutor,
- private val accessibilityManager: AccessibilityManager,
- private val tapGestureDetector: TapGestureDetector,
- private val powerManager: PowerManager,
- @LayoutRes private val chipLayoutRes: Int
+ internal val context: Context,
+ internal val logger: MediaTttLogger,
+ internal val windowManager: WindowManager,
+ private val viewUtil: ViewUtil,
+ @Main private val mainExecutor: DelayableExecutor,
+ private val accessibilityManager: AccessibilityManager,
+ private val configurationController: ConfigurationController,
+ private val powerManager: PowerManager,
+ @LayoutRes private val chipLayoutRes: Int,
) {
-
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
@@ -89,42 +88,43 @@
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
+ /** The chip info currently being displayed. Null if the chip is not being displayed. */
+ internal var chipInfo: T? = null
+
/** A [Runnable] that, when run, will cancel the pending timeout of the chip. */
private var cancelChipViewTimeout: Runnable? = null
/**
- * Displays the chip with the current state.
+ * Displays the chip with the provided [newChipInfo].
*
* This method handles inflating and attaching the view, then delegates to [updateChipView] to
* display the correct information in the chip.
*/
- fun displayChip(chipInfo: T) {
- val oldChipView = chipView
- if (chipView == null) {
- chipView = LayoutInflater
- .from(context)
- .inflate(chipLayoutRes, null) as ViewGroup
- }
- val currentChipView = chipView!!
+ fun displayChip(newChipInfo: T) {
+ val currentChipView = chipView
- updateChipView(chipInfo, currentChipView)
+ if (currentChipView != null) {
+ updateChipView(newChipInfo, currentChipView)
+ } else {
+ // The chip is new, so set up all our callbacks and inflate the view
+ configurationController.addCallback(displayScaleListener)
+ // Wake the screen if necessary so the user will see the chip. (Per b/239426653, we want
+ // the chip to show over the dream state, so we should only wake up if the screen is
+ // completely off.)
+ if (!powerManager.isScreenOn) {
+ powerManager.wakeUp(
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "com.android.systemui:media_tap_to_transfer_activated"
+ )
+ }
- // Add view if necessary
- if (oldChipView == null) {
- tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped)
- windowManager.addView(chipView, windowLayoutParams)
- // Wake the screen so the user will see the chip
- powerManager.wakeUp(
- SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:media_tap_to_transfer_activated"
- )
- animateChipIn(currentChipView)
+ inflateAndUpdateChip(newChipInfo)
}
// Cancel and re-set the chip timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
- chipInfo.getTimeoutMs().toInt(),
+ newChipInfo.getTimeoutMs().toInt(),
// Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -136,6 +136,32 @@
)
}
+ /** Inflates a new chip view, updates it with [newChipInfo], and adds the view to the window. */
+ private fun inflateAndUpdateChip(newChipInfo: T) {
+ val newChipView = LayoutInflater
+ .from(context)
+ .inflate(chipLayoutRes, null) as ViewGroup
+ chipView = newChipView
+ updateChipView(newChipInfo, newChipView)
+ windowManager.addView(newChipView, windowLayoutParams)
+ animateChipIn(newChipView)
+ }
+
+ /** Removes then re-inflates the chip. */
+ private fun reinflateChip() {
+ val currentChipInfo = chipInfo
+ if (chipView == null || currentChipInfo == null) { return }
+
+ windowManager.removeView(chipView)
+ inflateAndUpdateChip(currentChipInfo)
+ }
+
+ private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ reinflateChip()
+ }
+ }
+
/**
* Hides the chip.
*
@@ -145,17 +171,21 @@
open fun removeChip(removalReason: String) {
if (chipView == null) { return }
logger.logChipRemoval(removalReason)
- tapGestureDetector.removeOnGestureDetectedCallback(TAG)
+ configurationController.removeCallback(displayScaleListener)
windowManager.removeView(chipView)
chipView = null
+ chipInfo = null
// No need to time the chip out since it's already gone
cancelChipViewTimeout?.run()
}
/**
- * A method implemented by subclasses to update [currentChipView] based on [chipInfo].
+ * A method implemented by subclasses to update [currentChipView] based on [newChipInfo].
*/
- abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup)
+ @CallSuper
+ open fun updateChipView(newChipInfo: T, currentChipView: ViewGroup) {
+ chipInfo = newChipInfo
+ }
/**
* A method that can be implemented by subclcasses to do custom animations for when the chip
@@ -226,15 +256,6 @@
isAppIcon = false
)
}
-
- private fun onScreenTapped(e: MotionEvent) {
- val view = chipView ?: return
- // If the tap is within the chip bounds, we shouldn't hide the chip (in case users think the
- // chip is tappable).
- if (!viewUtil.touchIsWithinView(view, e.x, e.y)) {
- removeChip(MediaTttRemovalReason.REASON_SCREEN_TAP)
- }
- }
}
// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 99a5b8b..0f1ae00 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -19,7 +19,6 @@
import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
-import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
@@ -41,7 +40,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
@@ -54,27 +53,27 @@
*/
@SysUISingleton
class MediaTttChipControllerReceiver @Inject constructor(
- commandQueue: CommandQueue,
- context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- viewUtil: ViewUtil,
- mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager,
- @Main private val mainHandler: Handler,
- private val uiEventLogger: MediaTttReceiverUiEventLogger,
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttReceiverLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ viewUtil: ViewUtil,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ @Main private val mainHandler: Handler,
+ private val uiEventLogger: MediaTttReceiverUiEventLogger,
) : MediaTttChipControllerCommon<ChipReceiverInfo>(
- context,
- logger,
- windowManager,
- viewUtil,
- mainExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager,
- R.layout.media_ttt_chip_receiver
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ R.layout.media_ttt_chip_receiver,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -140,12 +139,13 @@
)
}
- override fun updateChipView(chipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+ override fun updateChipView(newChipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
+ super.updateChipView(newChipInfo, currentChipView)
setIcon(
currentChipView,
- chipInfo.routeInfo.packageName,
- chipInfo.appIconDrawableOverride,
- chipInfo.appNameOverride
+ newChipInfo.routeInfo.packageName,
+ newChipInfo.appIconDrawableOverride,
+ newChipInfo.appNameOverride
)
}
@@ -201,10 +201,10 @@
val height = windowBounds.height()
val width = windowBounds.width()
- rippleView.radius = height / 5f
+ val maxDiameter = height / 2.5f
+ rippleView.setMaxSize(maxDiameter, maxDiameter)
// Center the ripple on the bottom of the screen in the middle.
- rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
-
+ rippleView.setCenter(width * 0.5f, height.toFloat())
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
rippleView.setColor(colorWithAlpha)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index fed546bf..6a505f0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -23,10 +23,10 @@
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
*/
-class ReceiverChipRippleView(
- context: Context?, attrs: AttributeSet?
-) : RippleView(context, attrs) {
+class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
init {
+ // TODO: use RippleShape#ELLIPSE when calling setupShader.
+ setupShader()
setRippleFill(true)
duration = 3000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index cd86fff..a153cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -243,6 +243,6 @@
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 15000L
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
private const val TAG = "ChipStateSender"
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 797a770..3ea11b8 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
@@ -38,7 +38,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttRemovalReason
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -49,33 +49,31 @@
*/
@SysUISingleton
class MediaTttChipControllerSender @Inject constructor(
- commandQueue: CommandQueue,
- context: Context,
- @MediaTttSenderLogger logger: MediaTttLogger,
- windowManager: WindowManager,
- viewUtil: ViewUtil,
- @Main mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager,
- private val uiEventLogger: MediaTttSenderUiEventLogger
+ commandQueue: CommandQueue,
+ context: Context,
+ @MediaTttSenderLogger logger: MediaTttLogger,
+ windowManager: WindowManager,
+ viewUtil: ViewUtil,
+ @Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ private val uiEventLogger: MediaTttSenderUiEventLogger
) : MediaTttChipControllerCommon<ChipSenderInfo>(
- context,
- logger,
- windowManager,
- viewUtil,
- mainExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager,
- R.layout.media_ttt_chip
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ R.layout.media_ttt_chip,
) {
override val windowLayoutParams = commonWindowLayoutParams.apply {
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
- private var currentlyDisplayedChipState: ChipStateSender? = null
-
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
@@ -116,16 +114,18 @@
/** Displays the chip view for the given state. */
override fun updateChipView(
- chipInfo: ChipSenderInfo,
- currentChipView: ViewGroup) {
- val chipState = chipInfo.state
- currentlyDisplayedChipState = chipState
+ newChipInfo: ChipSenderInfo,
+ currentChipView: ViewGroup
+ ) {
+ super.updateChipView(newChipInfo, currentChipView)
+
+ val chipState = newChipInfo.state
// App icon
- setIcon(currentChipView, chipInfo.routeInfo.packageName)
+ setIcon(currentChipView, newChipInfo.routeInfo.packageName)
// Text
- val otherDeviceName = chipInfo.routeInfo.name.toString()
+ val otherDeviceName = newChipInfo.routeInfo.name.toString()
currentChipView.requireViewById<TextView>(R.id.text).apply {
text = chipState.getChipTextString(context, otherDeviceName)
}
@@ -137,7 +137,7 @@
// Undo
val undoView = currentChipView.requireViewById<View>(R.id.undo)
val undoClickListener = chipState.undoClickListener(
- this, chipInfo.routeInfo, chipInfo.undoCallback, uiEventLogger
+ this, newChipInfo.routeInfo, newChipInfo.undoCallback, uiEventLogger
)
undoView.setOnClickListener(undoClickListener)
undoView.visibility = (undoClickListener != null).visibleIfTrue()
@@ -161,12 +161,11 @@
override fun removeChip(removalReason: String) {
// Don't remove the chip if we're mid-transfer since the user should still be able to
// see the status of the transfer. (But do remove it if it's finally timed out.)
- if (currentlyDisplayedChipState?.isMidTransfer == true
- && removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
+ if (chipInfo?.state?.isMidTransfer == true &&
+ removalReason != MediaTttRemovalReason.REASON_TIMEOUT) {
return
}
super.removeChip(removalReason)
- currentlyDisplayedChipState = null
}
private fun Boolean.visibleIfTrue(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 6bc50a6..da9fefa 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -48,6 +48,7 @@
import androidx.annotation.NonNull;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -89,6 +90,7 @@
private final AccessibilityManager mAccessibilityManager;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+ private final KeyguardViewController mKeyguardViewController;
private final UserTracker mUserTracker;
private final SystemActions mSystemActions;
private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
@@ -123,6 +125,7 @@
OverviewProxyService overviewProxyService,
Lazy<AssistManager> assistManagerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+ KeyguardViewController keyguardViewController,
NavigationModeController navigationModeController,
UserTracker userTracker,
DumpManager dumpManager) {
@@ -131,6 +134,7 @@
mAccessibilityManager = accessibilityManager;
mAssistManagerLazy = assistManagerLazy;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ mKeyguardViewController = keyguardViewController;
mUserTracker = userTracker;
mSystemActions = systemActions;
accessibilityManager.addAccessibilityServicesStateChangeListener(this);
@@ -317,8 +321,12 @@
* {@link InputMethodService} and the keyguard states.
*/
public boolean isImeShown(int vis) {
- View shadeWindowView = mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
- boolean isKeyguardShowing = mCentralSurfacesOptionalLazy.get().get().isKeyguardShowing();
+ View shadeWindowView = null;
+ if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+ shadeWindowView =
+ mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
+ }
+ boolean isKeyguardShowing = mKeyguardViewController.isShowing();
boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
&& shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
return imeVisibleOnShade
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 281ef94..75e48d2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -128,6 +128,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
@@ -146,7 +147,6 @@
import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.DeviceConfigProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 2a7c871..2d7a809 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -51,6 +51,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.QuickStepContract;
@@ -82,6 +84,7 @@
private final Context mContext;
private final Handler mHandler;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
+ private FeatureFlags mFeatureFlags;
private final DisplayManager mDisplayManager;
private final TaskbarDelegate mTaskbarDelegate;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -113,10 +116,12 @@
AutoHideController autoHideController,
LightBarController lightBarController,
Optional<Pip> pipOptional,
- Optional<BackAnimation> backAnimation) {
+ Optional<BackAnimation> backAnimation,
+ FeatureFlags featureFlags) {
mContext = context;
mHandler = mainHandler;
mNavigationBarComponentFactory = navigationBarComponentFactory;
+ mFeatureFlags = featureFlags;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
commandQueue.addCallback(this);
configurationController.addCallback(this);
@@ -217,7 +222,10 @@
/** @return {@code true} if taskbar is enabled, false otherwise */
private boolean initializeTaskbarIfNecessary() {
- if (mIsTablet) {
+ // Enable for tablet or (phone AND flag is set); assuming phone = !mIsTablet
+ boolean taskbarEnabled = mIsTablet || mFeatureFlags.isEnabled(Flags.HIDE_NAVBAR_WINDOW);
+
+ if (taskbarEnabled) {
Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
// Remove navigation bar when taskbar is showing
removeNavigationBar(mContext.getDisplayId());
@@ -226,7 +234,7 @@
} else {
mTaskbarDelegate.destroy();
}
- return mIsTablet;
+ return taskbarEnabled;
}
@Override
@@ -294,6 +302,10 @@
*/
@VisibleForTesting
void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
+ if (initializeTaskbarIfNecessary()) {
+ return;
+ }
+
if (display == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index b05e75e..b44b4de 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -510,7 +510,6 @@
private fun playCommitBackAnimation() {
// Check if we should vibrate again
if (previousState != GestureState.FLUNG) {
- backCallback.triggerBack()
velocityTracker!!.computeCurrentVelocity(1000)
val isSlow = abs(velocityTracker!!.xVelocity) < 500
val hasNotVibratedRecently =
@@ -519,6 +518,10 @@
vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
}
}
+ // Dispatch the actual back trigger
+ if (DEBUG) Log.d(TAG, "playCommitBackAnimation() invoked triggerBack() on backCallback")
+ backCallback.triggerBack()
+
playAnimation(setGoneEndListener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 057ed24..fc6dcd3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -60,6 +60,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -82,6 +83,7 @@
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
+import com.android.systemui.util.Assert;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -191,6 +193,7 @@
private final int mDisplayId;
private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
private final Rect mPipExcludedBounds = new Rect();
private final Rect mNavBarOverlayExcludedBounds = new Rect();
@@ -251,6 +254,7 @@
private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
private Map<String, Integer> mVocab;
private boolean mUseMLModel;
+ private boolean mMLModelIsLoading;
// minimum width below which we do not run the model
private int mMLEnableWidth;
private float mMLModelThreshold;
@@ -318,6 +322,7 @@
SysUiState sysUiState,
PluginManager pluginManager,
@Main Executor executor,
+ @Background Executor backgroundExecutor,
BroadcastDispatcher broadcastDispatcher,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
@@ -334,6 +339,7 @@
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = executor;
+ mBackgroundExecutor = backgroundExecutor;
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -631,28 +637,63 @@
return;
}
- if (newState) {
- mBackGestureTfClassifierProvider = mBackGestureTfClassifierProviderProvider.get();
- mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
- if (mBackGestureTfClassifierProvider.isActive()) {
- Trace.beginSection("EdgeBackGestureHandler#loadVocab");
- mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets());
- Trace.endSection();
- mUseMLModel = true;
+ mUseMLModel = newState;
+
+ if (mUseMLModel) {
+ Assert.isMainThread();
+ if (mMLModelIsLoading) {
+ Log.d(TAG, "Model tried to load while already loading.");
return;
}
- }
-
- mUseMLModel = false;
- if (mBackGestureTfClassifierProvider != null) {
+ mMLModelIsLoading = true;
+ mBackgroundExecutor.execute(() -> loadMLModel());
+ } else if (mBackGestureTfClassifierProvider != null) {
mBackGestureTfClassifierProvider.release();
mBackGestureTfClassifierProvider = null;
+ mVocab = null;
}
}
+ private void loadMLModel() {
+ BackGestureTfClassifierProvider provider = mBackGestureTfClassifierProviderProvider.get();
+ float threshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
+ Map<String, Integer> vocab = null;
+ if (provider != null && !provider.isActive()) {
+ provider.release();
+ provider = null;
+ Log.w(TAG, "Cannot load model because it isn't active");
+ }
+ if (provider != null) {
+ Trace.beginSection("EdgeBackGestureHandler#loadVocab");
+ vocab = provider.loadVocab(mContext.getAssets());
+ Trace.endSection();
+ }
+ BackGestureTfClassifierProvider finalProvider = provider;
+ Map<String, Integer> finalVocab = vocab;
+ mMainExecutor.execute(() -> onMLModelLoadFinished(finalProvider, finalVocab, threshold));
+ }
+
+ private void onMLModelLoadFinished(BackGestureTfClassifierProvider provider,
+ Map<String, Integer> vocab, float threshold) {
+ Assert.isMainThread();
+ mMLModelIsLoading = false;
+ if (!mUseMLModel) {
+ // This can happen if the user disables Gesture Nav while the model is loading.
+ if (provider != null) {
+ provider.release();
+ }
+ Log.d(TAG, "Model finished loading but isn't needed.");
+ return;
+ }
+ mBackGestureTfClassifierProvider = provider;
+ mVocab = vocab;
+ mMLModelThreshold = threshold;
+ }
+
private int getBackGesturePredictionsCategory(int x, int y, int app) {
- if (app == -1) {
+ BackGestureTfClassifierProvider provider = mBackGestureTfClassifierProvider;
+ if (provider == null || app == -1) {
return -1;
}
int distanceFromEdge;
@@ -673,7 +714,7 @@
new long[]{(long) y},
};
- mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector);
+ mMLResults = provider.predict(featuresVector);
if (mMLResults == -1) {
return -1;
}
@@ -1031,6 +1072,7 @@
private final SysUiState mSysUiState;
private final PluginManager mPluginManager;
private final Executor mExecutor;
+ private final Executor mBackgroundExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
@@ -1050,6 +1092,7 @@
SysUiState sysUiState,
PluginManager pluginManager,
@Main Executor executor,
+ @Background Executor backgroundExecutor,
BroadcastDispatcher broadcastDispatcher,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
@@ -1067,6 +1110,7 @@
mSysUiState = sysUiState;
mPluginManager = pluginManager;
mExecutor = executor;
+ mBackgroundExecutor = backgroundExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
@@ -1089,6 +1133,7 @@
mSysUiState,
mPluginManager,
mExecutor,
+ mBackgroundExecutor,
mBroadcastDispatcher,
mProtoTracer,
mNavigationModeController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index cb76ee2..6a8bf75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -151,7 +151,7 @@
mActivityStarter.postStartActivityDismissingKeyguard(
new Intent(Settings.ACTION_WIRELESS_SETTINGS), 0);
};
- view.setOnClickListener(onClickListener);
+
mNoSimTextView = view.getNoSimTextView();
mNoSimTextView.setOnClickListener(onClickListener);
mMainHandler = new H(mainLooper, this::handleUpdateCarrierInfo, this::handleUpdateState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 02d30c52..75fb393 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -34,6 +34,7 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
@@ -61,6 +62,7 @@
private final KeyguardStateController mKeyguardStateController;
private final Callback mCallback = new Callback();
private final DialogLaunchAnimator mDialogLaunchAnimator;
+ private final FeatureFlags mFlags;
private long mMillisUntilFinished = 0;
@@ -71,6 +73,7 @@
@Main Handler mainHandler,
FalsingManager falsingManager,
MetricsLogger metricsLogger,
+ FeatureFlags flags,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
@@ -83,6 +86,7 @@
statusBarStateController, activityStarter, qsLogger);
mController = controller;
mController.observe(this, mCallback);
+ mFlags = flags;
mKeyguardDismissUtil = keyguardDismissUtil;
mKeyguardStateController = keyguardStateController;
mDialogLaunchAnimator = dialogLaunchAnimator;
@@ -164,8 +168,8 @@
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
getHost().collapsePanels();
};
- ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext,
- onStartRecordingClicked);
+ ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
+ mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
ActivityStarter.OnDismissAction dismissAction = () -> {
if (shouldAnimateFromView) {
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 93a2efc..0a8e6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -20,92 +20,107 @@
import android.util.MathUtils
/**
- * Shader class that renders an expanding charging ripple effect. A charging ripple contains
- * three elements:
- * 1. an expanding filled circle that appears in the beginning and quickly fades away
+ * Shader class that renders an expanding ripple effect. The ripple contains three elements:
+ *
+ * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away
* 2. an expanding ring that appears throughout the effect
* 3. an expanding ring-shaped area that reveals noise over #2.
*
+ * The ripple shader will be default to the circle shape if not specified.
+ *
* Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
*/
-class RippleShader internal constructor() : RuntimeShader(SHADER) {
+class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
+ RuntimeShader(buildShader(rippleShape)) {
+
+ /** Shapes that the [RippleShader] supports. */
+ enum class RippleShape {
+ CIRCLE,
+ ROUNDED_BOX,
+ ELLIPSE
+ }
+
companion object {
- private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
+ private const val SHADER_UNIFORMS = """uniform vec2 in_center;
+ uniform vec2 in_size;
uniform float in_progress;
- uniform float in_maxRadius;
+ uniform float in_cornerRadius;
+ uniform float in_thickness;
uniform float in_time;
uniform float in_distort_radial;
uniform float in_distort_xy;
- uniform float in_radius;
uniform float in_fadeSparkle;
- uniform float in_fadeCircle;
+ uniform float in_fadeFill;
uniform float in_fadeRing;
uniform float in_blur;
uniform float in_pixelDensity;
layout(color) uniform vec4 in_color;
uniform float in_sparkle_strength;"""
- private const val SHADER_LIB = """float triangleNoise(vec2 n) {
- n = fract(n * vec2(5.3987, 5.4421));
- n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
- float xy = n.x * n.y;
- return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+
+ private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
+ vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+ float radius = in_size.x * 0.5;
+ float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
+ float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+ * (1.-sparkleRing) * in_fadeSparkle;
+
+ float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ vec4 ripple = in_color * rippleAlpha;
+ return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+ }
+ """
+
+ private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
+ float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
+ in_thickness), in_blur);
+ float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
+ in_blur);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+ * (1.-sparkleRing) * in_fadeSparkle;
+
+ float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ vec4 ripple = in_color * rippleAlpha;
+ return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+ }
+ """
+
+ private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
+ vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
+
+ float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
+ float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+ * (1.-sparkleRing) * in_fadeSparkle;
+
+ float rippleInsideAlpha = (1.-inside) * in_fadeFill;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ vec4 ripple = in_color * rippleAlpha;
+ return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
+ }
+ """
+
+ private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
+ SHADER_CIRCLE_MAIN
+ private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
+ RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
+ private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
+ SHADER_ELLIPSE_MAIN
+
+ private fun buildShader(rippleShape: RippleShape): String =
+ when (rippleShape) {
+ RippleShape.CIRCLE -> CIRCLE_SHADER
+ RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+ RippleShape.ELLIPSE -> ELLIPSE_SHADER
}
- const float PI = 3.1415926535897932384626;
-
- float threshold(float v, float l, float h) {
- return step(l, v) * (1.0 - step(h, v));
- }
-
- float sparkles(vec2 uv, float t) {
- float n = triangleNoise(uv);
- float s = 0.0;
- for (float i = 0; i < 4; i += 1) {
- float l = i * 0.01;
- float h = l + 0.1;
- float o = smoothstep(n - l, h, n);
- o *= abs(sin(PI * o * (t + 0.55 * i)));
- s += o;
- }
- return s;
- }
-
- float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
- float blurHalf = blur * 0.5;
- float d = distance(uv, xy);
- return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);
- }
-
- float softRing(vec2 uv, vec2 xy, float radius, float blur) {
- float thickness_half = radius * 0.25;
- float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
- float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
- return circle_outer - circle_inner;
- }
-
- vec2 distort(vec2 p, vec2 origin, float time,
- float distort_amount_radial, float distort_amount_xy) {
- float2 distance = origin - p;
- float angle = atan(distance.y, distance.x);
- return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
- cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
- + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
- cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
- }"""
- private const val SHADER_MAIN = """vec4 main(vec2 p) {
- vec2 p_distorted = distort(p, in_origin, in_time, in_distort_radial,
- in_distort_xy);
-
- // Draw shapes
- float sparkleRing = softRing(p_distorted, in_origin, in_radius, in_blur);
- float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
- * sparkleRing * in_fadeSparkle;
- float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
- float rippleAlpha = max(circle * in_fadeCircle,
- softRing(p_distorted, in_origin, in_radius, in_blur) * in_fadeRing) * 0.45;
- vec4 ripple = in_color * rippleAlpha;
- return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
- }"""
- private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
private fun subProgress(start: Float, end: Float, progress: Float): Float {
val min = Math.min(start, end)
@@ -116,22 +131,18 @@
}
/**
- * Maximum radius of the ripple.
+ * Sets the center position of the ripple.
*/
- var radius: Float = 0.0f
- set(value) {
- field = value
- setFloatUniform("in_maxRadius", value)
- }
+ fun setCenter(x: Float, y: Float) {
+ setFloatUniform("in_center", x, y)
+ }
- /**
- * Origin coordinate of the ripple.
- */
- var origin: PointF = PointF()
- set(value) {
- field = value
- setFloatUniform("in_origin", value.x, value.y)
- }
+ /** Max width of the ripple. */
+ private var maxSize: PointF = PointF()
+ fun setMaxSize(width: Float, height: Float) {
+ maxSize.x = width
+ maxSize.y = height
+ }
/**
* Progress of the ripple. Float value between [0, 1].
@@ -140,20 +151,27 @@
set(value) {
field = value
setFloatUniform("in_progress", value)
- setFloatUniform("in_radius",
- (1 - (1 - value) * (1 - value) * (1 - value))* radius)
+ val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
+
+ setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
+ /* height= */ maxSize.y * curvedProg)
+ setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
+ // radius should not exceed width and height values.
+ setFloatUniform("in_cornerRadius",
+ Math.min(maxSize.x, maxSize.y) * curvedProg)
+
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
val fadeIn = subProgress(0f, 0.1f, value)
val fadeOutNoise = subProgress(0.4f, 1f, value)
var fadeOutRipple = 0f
- var fadeCircle = 0f
+ var fadeFill = 0f
if (!rippleFill) {
- fadeCircle = subProgress(0f, 0.2f, value)
+ fadeFill = subProgress(0f, 0.6f, value)
fadeOutRipple = subProgress(0.3f, 1f, value)
}
setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
- setFloatUniform("in_fadeCircle", 1 - fadeCircle)
+ setFloatUniform("in_fadeFill", 1 - fadeFill)
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
}
@@ -169,7 +187,7 @@
/**
* A hex value representing the ripple color, in the format of ARGB
*/
- var color: Int = 0xffffff.toInt()
+ var color: Int = 0xffffff
set(value) {
field = value
setColorUniform("in_color", value)
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
new file mode 100644
index 0000000..0cacbc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.ripple
+
+/** A common utility functions that are used for computing [RippleShader]. */
+class RippleShaderUtilLibrary {
+ companion object {
+ const val SHADER_LIB = """
+ float triangleNoise(vec2 n) {
+ n = fract(n * vec2(5.3987, 5.4421));
+ n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+ float xy = n.x * n.y;
+ return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+ }
+ const float PI = 3.1415926535897932384626;
+
+ float sparkles(vec2 uv, float t) {
+ float n = triangleNoise(uv);
+ float s = 0.0;
+ for (float i = 0; i < 4; i += 1) {
+ float l = i * 0.01;
+ float h = l + 0.1;
+ float o = smoothstep(n - l, h, n);
+ o *= abs(sin(PI * o * (t + 0.55 * i)));
+ s += o;
+ }
+ return s;
+ }
+
+ vec2 distort(vec2 p, float time, float distort_amount_radial,
+ float distort_amount_xy) {
+ float angle = atan(p.y, p.x);
+ return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+ cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+ cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+ }"""
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index fc52464..83d9f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -23,39 +23,42 @@
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Paint
-import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
+import com.android.systemui.ripple.RippleShader.RippleShape
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
+private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
/**
- * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
- * then call [startRipple].
+ * A generic expanding ripple effect.
+ *
+ * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter],
+ * then call [startRipple] to trigger the ripple expansion.
*/
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- private val rippleShader = RippleShader()
- private val defaultColor: Int = 0xffffffff.toInt()
+
+ private lateinit var rippleShader: RippleShader
+ private lateinit var rippleShape: RippleShape
private val ripplePaint = Paint()
var rippleInProgress: Boolean = false
- var radius: Float = 0.0f
- set(value) {
- rippleShader.radius = value
- field = value
- }
- var origin: PointF = PointF()
- set(value) {
- rippleShader.origin = value
- field = value
- }
var duration: Long = 1750
- init {
- rippleShader.color = defaultColor
- rippleShader.progress = 0f
- rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
- ripplePaint.shader = rippleShader
+ private var maxWidth: Float = 0.0f
+ private var maxHeight: Float = 0.0f
+ fun setMaxSize(maxWidth: Float, maxHeight: Float) {
+ this.maxWidth = maxWidth
+ this.maxHeight = maxHeight
+ rippleShader.setMaxSize(maxWidth, maxHeight)
+ }
+
+ private var centerX: Float = 0.0f
+ private var centerY: Float = 0.0f
+ fun setCenter(x: Float, y: Float) {
+ this.centerX = x
+ this.centerY = y
+ rippleShader.setCenter(x, y)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -68,6 +71,18 @@
super.onAttachedToWindow()
}
+ /** Initializes the shader. Must be called before [startRipple]. */
+ fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) {
+ this.rippleShape = rippleShape
+ rippleShader = RippleShader(rippleShape)
+
+ rippleShader.color = RIPPLE_DEFAULT_COLOR
+ rippleShader.progress = 0f
+ rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+
+ ripplePaint.shader = rippleShader
+ }
+
@JvmOverloads
fun startRipple(onAnimationEnd: Runnable? = null) {
if (rippleInProgress) {
@@ -113,11 +128,24 @@
// if it's unsupported.
return
}
- // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
- // the active effect area. Values here should be kept in sync with the
- // animation implementation in the ripple shader.
- val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 2
- canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+ // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
+ // active effect area. Values here should be kept in sync with the animation implementation
+ // in the ripple shader.
+ if (rippleShape == RippleShape.CIRCLE) {
+ val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth
+ canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
+ } else {
+ val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth * 2
+ val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxHeight * 2
+ canvas.drawRect(
+ /* left= */ centerX - maskWidth,
+ /* top= */ centerY - maskHeight,
+ /* right= */ centerX + maskWidth,
+ /* bottom= */ centerY + maskHeight,
+ ripplePaint)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
new file mode 100644
index 0000000..7f26146
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.ripple
+
+/** Library class that contains 2D signed distance functions. */
+class SdfShaderLibrary {
+ companion object {
+ const val CIRCLE_SDF = """
+ float sdCircle(vec2 p, float r) {
+ return (length(p)-r) / r;
+ }
+
+ float circleRing(vec2 p, float radius) {
+ float thicknessHalf = radius * 0.25;
+
+ float outerCircle = sdCircle(p, radius + thicknessHalf);
+ float innerCircle = sdCircle(p, radius);
+
+ return subtract(outerCircle, innerCircle);
+ }
+ """
+
+ const val ROUNDED_BOX_SDF = """
+ float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
+ size *= 0.5;
+ cornerRadius *= 0.5;
+ vec2 d = abs(p)-size+cornerRadius;
+
+ float outside = length(max(d, 0.0));
+ float inside = min(max(d.x, d.y), 0.0);
+
+ return (outside+inside-cornerRadius)/size.y;
+ }
+
+ float roundedBoxRing(vec2 p, vec2 size, float cornerRadius,
+ float borderThickness) {
+ float outerRoundBox = sdRoundedBox(p, size, cornerRadius);
+ float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness),
+ cornerRadius - borderThickness);
+ return subtract(outerRoundBox, innerRoundBox);
+ }
+ """
+
+ // Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
+ // This is more expensive than the regular circle SDF, recommend to use the circle SDF if
+ // possible.
+ const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
+ wh *= 0.5;
+
+ // symmetry
+ (wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p);
+
+ vec2 u = wh*p, v = wh*wh;
+
+ float U1 = u.y/2.0; float U5 = 4.0*U1;
+ float U2 = v.y-v.x; float U6 = 6.0*U1;
+ float U3 = u.x-U2; float U7 = 3.0*U3;
+ float U4 = u.x+U2;
+
+ float t = 0.5;
+ for (int i = 0; i < 3; i ++) {
+ float F1 = t*(t*t*(U1*t+U3)+U4)-U1;
+ float F2 = t*t*(U5*t+U7)+U4;
+ float F3 = t*(U6*t+U7);
+
+ t += (F1*F2)/(F1*F3-F2*F2);
+ }
+
+ t = clamp(t, 0.0, 1.0);
+
+ float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0));
+ d /= wh.y;
+
+ return (dot(p/wh,p/wh)>1.0) ? d : -d;
+ }
+
+ float ellipseRing(vec2 p, vec2 wh) {
+ vec2 thicknessHalf = wh * 0.25;
+
+ float outerEllipse = sdEllipse(p, wh + thicknessHalf);
+ float innerEllipse = sdEllipse(p, wh);
+
+ return subtract(outerEllipse, innerEllipse);
+ }
+ """
+
+ const val SHADER_SDF_OPERATION_LIB = """
+ float soften(float d, float blur) {
+ float blurHalf = blur * 0.5;
+ return smoothstep(-blurHalf, blurHalf, d);
+ }
+
+ float subtract(float outer, float inner) {
+ return max(outer, -inner);
+ }
+ """
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 1a08878..1083f22 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -29,8 +29,11 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.policy.CallbackController;
@@ -94,9 +97,11 @@
}
/** Create a dialog to show screen recording options to the user. */
- public ScreenRecordDialog createScreenRecordDialog(Context context,
+ public ScreenRecordDialog createScreenRecordDialog(Context context, FeatureFlags flags,
+ DialogLaunchAnimator dialogLaunchAnimator, ActivityStarter activityStarter,
@Nullable Runnable onStartRecordingClicked) {
- return new ScreenRecordDialog(context, this, mUserContextProvider, onStartRecordingClicked);
+ return new ScreenRecordDialog(context, this, activityStarter, mUserContextProvider,
+ flags, dialogLaunchAnimator, onStartRecordingClicked);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index a837cbb..0477626 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -42,6 +43,7 @@
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaProjectionCaptureTarget;
import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -67,6 +69,7 @@
private static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
+ private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
private static final String ACTION_START = "com.android.systemui.screenrecord.START";
private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
@@ -110,14 +113,18 @@
* @param audioSource The ordinal value of the audio source
* {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource}
* @param showTaps True to make touches visible while recording
+ * @param captureTarget pass this parameter to capture a specific part instead
+ * of the full screen
*/
public static Intent getStartIntent(Context context, int resultCode,
- int audioSource, boolean showTaps) {
+ int audioSource, boolean showTaps,
+ @Nullable MediaProjectionCaptureTarget captureTarget) {
return new Intent(context, RecordingService.class)
.setAction(ACTION_START)
.putExtra(EXTRA_RESULT_CODE, resultCode)
.putExtra(EXTRA_AUDIO_SOURCE, audioSource)
- .putExtra(EXTRA_SHOW_TAPS, showTaps);
+ .putExtra(EXTRA_SHOW_TAPS, showTaps)
+ .putExtra(EXTRA_CAPTURE_TARGET, captureTarget);
}
@Override
@@ -136,6 +143,9 @@
.values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
Log.d(TAG, "recording with audio source" + mAudioSource);
mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
+ MediaProjectionCaptureTarget captureTarget =
+ intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
+ MediaProjectionCaptureTarget.class);
mOriginalShowTaps = Settings.System.getInt(
getApplicationContext().getContentResolver(),
@@ -148,6 +158,7 @@
mMainHandler,
currentUserId,
mAudioSource,
+ captureTarget,
this
);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index d098b4b..b8d96f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -51,6 +51,7 @@
import android.view.Surface;
import android.view.WindowManager;
+import com.android.systemui.media.MediaProjectionCaptureTarget;
import java.io.File;
import java.io.Closeable;
import java.io.IOException;
@@ -85,6 +86,7 @@
private ScreenRecordingMuxer mMuxer;
private ScreenInternalAudioRecorder mAudio;
private ScreenRecordingAudioSource mAudioSource;
+ private final MediaProjectionCaptureTarget mCaptureRegion;
private final Handler mHandler;
private Context mContext;
@@ -92,10 +94,12 @@
public ScreenMediaRecorder(Context context, Handler handler,
int user, ScreenRecordingAudioSource audioSource,
+ MediaProjectionCaptureTarget captureRegion,
ScreenMediaRecorderListener listener) {
mContext = context;
mHandler = handler;
mUser = user;
+ mCaptureRegion = captureRegion;
mListener = listener;
mAudioSource = audioSource;
}
@@ -108,9 +112,11 @@
IMediaProjection proj = null;
proj = mediaService.createProjection(mUser, mContext.getPackageName(),
MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
- IBinder projection = proj.asBinder();
- mMediaProjection = new MediaProjection(mContext,
- IMediaProjection.Stub.asInterface(projection));
+ IMediaProjection projection = IMediaProjection.Stub.asInterface(proj.asBinder());
+ if (mCaptureRegion != null) {
+ projection.setLaunchCookie(mCaptureRegion.getLaunchCookie());
+ }
+ mMediaProjection = new MediaProjection(mContext, projection);
mMediaProjection.registerCallback(this, mHandler);
File cacheDir = mContext.getCacheDir();
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 1fb88df..efa45a4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -16,6 +16,10 @@
package com.android.systemui.screenrecord;
+import static android.app.Activity.RESULT_OK;
+
+import static com.android.systemui.media.MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER;
+import static com.android.systemui.media.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
@@ -24,8 +28,13 @@
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
import android.view.Gravity;
+import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
@@ -36,6 +45,13 @@
import androidx.annotation.Nullable;
import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.media.MediaProjectionAppSelectorActivity;
+import com.android.systemui.media.MediaProjectionCaptureTarget;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -55,15 +71,23 @@
private final UserContextProvider mUserContextProvider;
@Nullable
private final Runnable mOnStartRecordingClicked;
+ private final ActivityStarter mActivityStarter;
+ private final FeatureFlags mFlags;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
private Switch mTapsSwitch;
private Switch mAudioSwitch;
private Spinner mOptions;
public ScreenRecordDialog(Context context, RecordingController controller,
- UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) {
+ ActivityStarter activityStarter, UserContextProvider userContextProvider,
+ FeatureFlags flags, DialogLaunchAnimator dialogLaunchAnimator,
+ @Nullable Runnable onStartRecordingClicked) {
super(context);
mController = controller;
mUserContextProvider = userContextProvider;
+ mActivityStarter = activityStarter;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
+ mFlags = flags;
mOnStartRecordingClicked = onStartRecordingClicked;
}
@@ -91,10 +115,36 @@
mOnStartRecordingClicked.run();
}
- requestScreenCapture();
+ // Start full-screen recording
+ requestScreenCapture(/* captureTarget= */ null);
dismiss();
});
+ if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+ TextView appBtn = findViewById(R.id.button_app);
+
+ appBtn.setVisibility(View.VISIBLE);
+ appBtn.setOnClickListener(v -> {
+ Intent intent = new Intent(getContext(), MediaProjectionAppSelectorActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // We can't start activity for result here so we use result receiver to get
+ // the selected target to capture
+ intent.putExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+ new CaptureTargetResultReceiver());
+
+ ActivityLaunchAnimator.Controller animationController =
+ mDialogLaunchAnimator.createActivityLaunchController(appBtn);
+
+ if (animationController == null) {
+ dismiss();
+ }
+
+ mActivityStarter.startActivity(intent, /* dismissShade= */ true,
+ animationController);
+ });
+ }
+
mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
mOptions = findViewById(R.id.screen_recording_options);
@@ -108,7 +158,12 @@
});
}
- private void requestScreenCapture() {
+ /**
+ * Starts screen capture after some countdown
+ * @param captureTarget target to capture (could be e.g. a task) or
+ * null to record the whole screen
+ */
+ private void requestScreenCapture(@Nullable MediaProjectionCaptureTarget captureTarget) {
Context userContext = mUserContextProvider.getUserContext();
boolean showTaps = mTapsSwitch.isChecked();
ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
@@ -118,7 +173,7 @@
RecordingService.REQUEST_CODE,
RecordingService.getStartIntent(
userContext, Activity.RESULT_OK,
- audioMode.ordinal(), showTaps),
+ audioMode.ordinal(), showTaps, captureTarget),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
PendingIntent stopIntent = PendingIntent.getService(userContext,
RecordingService.REQUEST_CODE,
@@ -126,4 +181,22 @@
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
}
+
+ private class CaptureTargetResultReceiver extends ResultReceiver {
+
+ CaptureTargetResultReceiver() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == RESULT_OK) {
+ MediaProjectionCaptureTarget captureTarget = resultData
+ .getParcelable(KEY_CAPTURE_TARGET, MediaProjectionCaptureTarget.class);
+
+ // Start recording of the selected target
+ requestScreenCapture(captureTarget);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
new file mode 100644
index 0000000..39f35a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCapture.kt
@@ -0,0 +1,26 @@
+/*
+ * 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
+
+interface ImageCapture {
+
+ fun captureDisplay(displayId: Int, crop: Rect? = null): Bitmap?
+
+ 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
new file mode 100644
index 0000000..258c436
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.IActivityTaskManager
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.hardware.display.DisplayManager
+import android.os.IBinder
+import android.util.Log
+import android.view.DisplayAddress
+import android.view.SurfaceControl
+import android.view.SurfaceControl.DisplayCaptureArgs
+import android.view.SurfaceControl.ScreenshotHardwareBuffer
+import androidx.annotation.VisibleForTesting
+import javax.inject.Inject
+
+private const val TAG = "ImageCaptureImpl"
+
+open class ImageCaptureImpl @Inject constructor(
+ private val displayManager: DisplayManager,
+ private val atmService: IActivityTaskManager
+) : ImageCapture {
+
+ override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
+ val width = crop?.width() ?: 0
+ val height = crop?.height() ?: 0
+ val sourceCrop = crop ?: Rect()
+ val displayToken = physicalDisplayToken(displayId) ?: return null
+ val buffer = captureDisplay(displayToken, width, height, sourceCrop)
+
+ return buffer?.asBitmap()
+ }
+
+ override fun captureTask(taskId: Int): Bitmap? {
+ val snapshot = atmService.takeTaskSnapshot(taskId)
+ return Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace)
+ }
+
+ @VisibleForTesting
+ open fun physicalDisplayToken(displayId: Int): IBinder? {
+ val display = displayManager.getDisplay(displayId)
+ if (display == null) {
+ Log.e(TAG, "No display with id: $displayId")
+ return null
+ }
+ val address = display.address
+ if (address !is DisplayAddress.Physical) {
+ Log.e(TAG, "Display does not have a physical address: $display")
+ return null
+ }
+ return SurfaceControl.getPhysicalDisplayToken(address.physicalDisplayId)
+ }
+
+ @VisibleForTesting
+ 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
new file mode 100644
index 0000000..beb54c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.net.Uri
+import android.util.Log
+import android.view.WindowManager.ScreenshotType
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+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 java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * Processes a screenshot request sent from {@link ScreenshotHelper}.
+ */
+@SysUISingleton
+internal class RequestProcessor @Inject constructor(
+ private val controller: ScreenshotController,
+) {
+ fun processRequest(
+ @ScreenshotType type: Int,
+ onSavedListener: Consumer<Uri>,
+ request: ScreenshotRequest,
+ callback: RequestCallback
+ ) {
+
+ if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ val image = HardwareBitmapBundler.bundleToHardwareBitmap(request.bitmapBundle)
+
+ controller.handleImageAsScreenshot(
+ image, request.boundsInScreen, request.insets,
+ request.taskId, request.userId, request.topComponent, onSavedListener, callback
+ )
+ return
+ }
+
+ when (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: $type")
+ }
+ }
+
+ companion object {
+ const val TAG: String = "RequestProcessor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 82de389..69ee8e8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -57,14 +57,12 @@
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
-import android.view.DisplayAddress;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent;
@@ -72,7 +70,6 @@
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
-import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
@@ -249,6 +246,7 @@
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
private final ImageExporter mImageExporter;
+ private final ImageCapture mImageCapture;
private final Executor mMainExecutor;
private final ExecutorService mBgExecutor;
private final BroadcastSender mBroadcastSender;
@@ -295,6 +293,7 @@
ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
+ ImageCapture imageCapture,
@Main Executor mainExecutor,
ScrollCaptureController scrollCaptureController,
LongScreenshotData longScreenshotHolder,
@@ -308,6 +307,7 @@
mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
+ mImageCapture = imageCapture;
mMainExecutor = mainExecutor;
mScrollCaptureController = scrollCaptureController;
mLongScreenshotHolder = longScreenshotHolder;
@@ -531,7 +531,7 @@
// copy the input Rect, since SurfaceControl.screenshot can mutate it
Rect screenRect = new Rect(crop);
- Bitmap screenshot = captureScreenshot(crop);
+ Bitmap screenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY, crop);
if (screenshot == null) {
Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
@@ -549,32 +549,6 @@
ClipboardOverlayController.SELF_PERMISSION);
}
- private Bitmap captureScreenshot(Rect crop) {
- int width = crop.width();
- int height = crop.height();
- Bitmap screenshot = null;
- final Display display = getDefaultDisplay();
- final DisplayAddress address = display.getAddress();
- if (!(address instanceof DisplayAddress.Physical)) {
- Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "
- + display);
- } else {
- final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
-
- final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(
- physicalAddress.getPhysicalDisplayId());
- final SurfaceControl.DisplayCaptureArgs captureArgs =
- new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
- .setSourceCrop(crop)
- .setSize(width, height)
- .build();
- final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
- SurfaceControl.captureDisplay(captureArgs);
- screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
- }
- return screenshot;
- }
-
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, ComponentName topComponent, boolean showFlash) {
withWindowAttached(() ->
@@ -720,7 +694,7 @@
mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDefaultDisplay().getRealMetrics(displayMetrics);
- Bitmap newScreenshot = captureScreenshot(
+ Bitmap newScreenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 32d8203..f1f0223 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -21,6 +21,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.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
@@ -57,6 +58,8 @@
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FlagListenable.FlagEvent;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -75,6 +78,8 @@
private final Handler mHandler;
private final Context mContext;
private final @Background Executor mBgExecutor;
+ private final RequestProcessor mProcessor;
+ private final FeatureFlags mFeatureFlags;
private final BroadcastReceiver mCloseSystemDialogs = new BroadcastReceiver() {
@Override
@@ -104,7 +109,8 @@
public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
DevicePolicyManager devicePolicyManager, UiEventLogger uiEventLogger,
ScreenshotNotificationsController notificationsController, Context context,
- @Background Executor bgExecutor) {
+ @Background Executor bgExecutor, FeatureFlags featureFlags,
+ RequestProcessor processor) {
if (DEBUG_SERVICE) {
Log.d(TAG, "new " + this);
}
@@ -116,6 +122,9 @@
mNotificationsController = notificationsController;
mContext = context;
mBgExecutor = bgExecutor;
+ mFeatureFlags = featureFlags;
+ mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart);
+ mProcessor = processor;
}
@Override
@@ -218,6 +227,12 @@
mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,
topComponent == null ? "" : topComponent.getPackageName());
+ if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) {
+ Log.d(TAG, "handleMessage: Using request processor");
+ mProcessor.processRequest(msg.what, uriConsumer, screenshotRequest, requestCallback);
+ return true;
+ }
+
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
if (DEBUG_SERVICE) {
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 91ef3c3..3e442587 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -18,6 +18,8 @@
import android.app.Service;
+import com.android.systemui.screenshot.ImageCapture;
+import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.TakeScreenshotService;
import dagger.Binds;
@@ -37,4 +39,6 @@
@ClassKey(TakeScreenshotService.class)
public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+ @Binds
+ public abstract ImageCapture bindImageCapture(ImageCaptureImpl capture);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 5793105..0f9ac36 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -264,14 +264,8 @@
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
)
- carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
- listOf(
- header.context.getString(com.android.internal.R.string.status_bar_no_calling),
- header.context.getString(com.android.internal.R.string.status_bar_call_strength)
- )
- } else {
+ carrierIconSlots =
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
- }
qsCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(qsCarrierGroup)
.build()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 24448bb..7f0e76b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -44,7 +44,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
@@ -128,6 +127,10 @@
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -284,7 +287,6 @@
private final NotificationPanelView mView;
private final VibratorHelper mVibratorHelper;
private final MetricsLogger mMetricsLogger;
- private final ActivityManager mActivityManager;
private final ConfigurationController mConfigurationController;
private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@@ -337,14 +339,14 @@
private final RecordingController mRecordingController;
private final PanelEventsEmitter mPanelEventsEmitter;
private boolean mSplitShadeEnabled;
- // The bottom padding reserved for elements of the keyguard measuring notifications
+ /** The bottom padding reserved for elements of the keyguard measuring notifications. */
private float mKeyguardNotificationBottomPadding;
/**
* The top padding from where notification should start in lockscreen.
* Should be static also during animations and should match the Y of the first notification.
*/
private float mKeyguardNotificationTopPadding;
- // Current max allowed keyguard notifications determined by measuring the panel
+ /** Current max allowed keyguard notifications determined by measuring the panel. */
private int mMaxAllowedKeyguardNotifications;
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
@@ -697,6 +699,12 @@
};
private final CameraGestureHelper mCameraGestureHelper;
+ private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
+ private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
+ private final Provider<SetKeyguardBottomAreaAlphaUseCase>
+ mSetKeyguardBottomAreaAlphaUseCaseProvider;
+ private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -718,7 +726,6 @@
AccessibilityManager accessibilityManager, @DisplayId int displayId,
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
- ActivityManager activityManager,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -767,7 +774,12 @@
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
SystemClock systemClock,
- CameraGestureHelper cameraGestureHelper) {
+ CameraGestureHelper cameraGestureHelper,
+ Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
+ Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
+ Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
+ Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
+ setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
super(view,
falsingManager,
dozeLog,
@@ -783,7 +795,6 @@
panelExpansionStateManager,
ambientState,
interactionJankMonitor,
- keyguardUnlockAnimationController,
systemClock);
mView = view;
mVibratorHelper = vibratorHelper;
@@ -793,7 +804,6 @@
mQRCodeScannerController = qrCodeScannerController;
mControlsComponent = controlsComponent;
mMetricsLogger = metricsLogger;
- mActivityManager = activityManager;
mConfigurationController = configurationController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mMediaHierarchyManager = mediaHierarchyManager;
@@ -897,6 +907,7 @@
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
+ mKeyguardBottomAreaViewModelProvider = keyguardBottomAreaViewModelProvider;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -950,6 +961,10 @@
}
});
mCameraGestureHelper = cameraGestureHelper;
+ mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
+ mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
+ setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
}
@VisibleForTesting
@@ -1274,11 +1289,17 @@
}
private void initBottomArea() {
- mKeyguardBottomArea.init(
- mFalsingManager,
- mQuickAccessWalletController,
- mControlsComponent,
- mQRCodeScannerController);
+ if (mFeatureFlags.isEnabled(Flags.MODERN_BOTTOM_AREA)) {
+ mKeyguardBottomArea.init(mKeyguardBottomAreaViewModelProvider.get(), mFalsingManager);
+ } else {
+ // TODO(b/235403546): remove this method call when the new implementation is complete
+ // and these are not needed.
+ mKeyguardBottomArea.init(
+ mFalsingManager,
+ mQuickAccessWalletController,
+ mControlsComponent,
+ mQRCodeScannerController);
+ }
}
@VisibleForTesting
@@ -1454,6 +1475,8 @@
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
+ mSetClockPositionUseCaseProvider.get().invoke(
+ mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
mKeyguardStatusViewController.updatePosition(
@@ -1476,10 +1499,7 @@
}
private void updateKeyguardStatusViewAlignment(boolean animate) {
- boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
- .getVisibleNotificationCount() != 0
- || mMediaDataManager.hasActiveMediaOrRecommendation();
- boolean shouldBeCentered = !mSplitShadeEnabled || !hasVisibleNotifications || mDozing;
+ boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
if (mStatusViewCentered != shouldBeCentered) {
mStatusViewCentered = shouldBeCentered;
ConstraintSet constraintSet = new ConstraintSet();
@@ -1503,6 +1523,15 @@
mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered));
}
+ private boolean shouldKeyguardStatusViewBeCentered() {
+ boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
+ .getVisibleNotificationCount() != 0
+ || mMediaDataManager.hasActiveMediaOrRecommendation();
+ boolean isOnAod = mDozing && mDozeParameters.getAlwaysOn();
+ return !mSplitShadeEnabled || !hasVisibleNotifications || isOnAod
+ || hasPulsingNotifications();
+ }
+
/**
* @return the padding of the stackscroller when unlocked
*/
@@ -3220,6 +3249,7 @@
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomArea.setComponentAlphas(alpha);
+ mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
mLockIconViewController.setAlpha(alpha);
}
@@ -3419,6 +3449,7 @@
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
@@ -3721,6 +3752,7 @@
mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate);
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) {
@@ -3850,29 +3882,37 @@
}
/**
- * Starts fold to AOD animation
+ * Starts fold to AOD animation.
+ *
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param cancelAction invoked when the animation is cancelled, before endAction.
*/
- public void startFoldToAodAnimation(Runnable endAction) {
+ public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
+ Runnable cancelAction) {
mView.animate()
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- endAction.run();
- }
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startAction.run();
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
- }
- })
- .setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
- })
- .start();
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAction.run();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ }).setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ }).start();
}
/**
@@ -3930,7 +3970,7 @@
* notification data being displayed. In the new notification pipeline, this is handled in
* {@link ShadeViewManager}.
*/
- public void updateNotificationViews(String reason) {
+ public void updateNotificationViews() {
mNotificationStackScrollLayoutController.updateFooter();
mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
@@ -4387,7 +4427,7 @@
NotificationStackScrollLayout.OnEmptySpaceClickListener {
@Override
public void onEmptySpaceClicked(float x, float y) {
- onEmptySpaceClick(x);
+ onEmptySpaceClick();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
rename to packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 905a5f9..1d92105 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -44,8 +44,6 @@
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import androidx.lifecycle.ViewTreeLifecycleOwner;
-
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -54,12 +52,17 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.lifecycle.WindowAddedViewLifecycleOwner;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -86,7 +89,6 @@
Dumpable, ConfigurationListener {
private static final String TAG = "NotificationShadeWindowController";
- private static final boolean DEBUG = false;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -187,7 +189,7 @@
return;
}
}
- mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
+ mCallbacks.add(new WeakReference<>(callback));
}
@Override
@@ -245,15 +247,6 @@
mWindowManager.addView(mNotificationShadeView, mLp);
- // Set up and "inject" a LifecycleOwner bound to the Window-View relationship such that all
- // views in the sub-tree rooted under this view can access the LifecycleOwner using
- // ViewTreeLifecycleOwner.get(...).
- if (ViewTreeLifecycleOwner.get(mNotificationShadeView) == null) {
- ViewTreeLifecycleOwner.set(
- mNotificationShadeView,
- new WindowAddedViewLifecycleOwner(mNotificationShadeView));
- }
-
mLpChanged.copyFrom(mLp);
onThemeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 121d69d..e52170e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -18,6 +18,8 @@
import static android.view.WindowInsets.Type.systemBars;
+import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
+
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
@@ -52,14 +54,12 @@
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
/**
* Combined keyguard and notification panel view. Also holding backdrop and scrims.
*/
public class NotificationShadeWindowView extends FrameLayout {
public static final String TAG = "NotificationShadeWindowView";
- public static final boolean DEBUG = CentralSurfaces.DEBUG;
private int mRightInset = 0;
private int mLeftInset = 0;
@@ -221,7 +221,7 @@
}
}
- class LayoutParams extends FrameLayout.LayoutParams {
+ private static class LayoutParams extends FrameLayout.LayoutParams {
public boolean ignoreRightInset;
@@ -243,7 +243,7 @@
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
int type) {
if (type == ActionMode.TYPE_FLOATING) {
- return startActionMode(originalView, callback, type);
+ return startActionMode(originalView, callback);
}
return super.startActionModeForChild(originalView, callback, type);
}
@@ -258,14 +258,10 @@
final FloatingActionMode mode =
new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar);
mFloatingActionModeOriginatingView = originatingView;
- mFloatingToolbarPreDrawListener =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mode.updateViewLocationInWindow();
- return true;
- }
- };
+ mFloatingToolbarPreDrawListener = () -> {
+ mode.updateViewLocationInWindow();
+ return true;
+ };
return mode;
}
@@ -292,10 +288,10 @@
}
private ActionMode startActionMode(
- View originatingView, ActionMode.Callback callback, int type) {
+ View originatingView, ActionMode.Callback callback) {
ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback);
- if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+ if (wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
setHandledFloatingActionMode(mode);
} else {
mode = null;
@@ -382,7 +378,7 @@
/**
* Minimal window to satisfy FloatingToolbar.
*/
- private Window mFakeWindow = new Window(mContext) {
+ private final Window mFakeWindow = new Window(mContext) {
@Override
public void takeSurface(SurfaceHolder.Callback2 callback) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 587e0e6d..02316b7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -48,13 +48,12 @@
private View mStackScroller;
private View mKeyguardStatusBar;
- private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
- private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
+ private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>();
+ private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>();
private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild);
private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
private QS mQs;
- private View mQSScrollView;
private View mQSContainer;
@Nullable
@@ -76,7 +75,6 @@
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
mQSFragmentAttachedListener.accept(mQs);
- mQSScrollView = mQs.getView().findViewById(R.id.expanded_qs_scroll_view);
mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
index 1082967..efff0db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
@@ -49,10 +49,6 @@
super(context, attrs, defStyleAttr);
}
- public PanelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
public void setOnTouchListener(PanelViewController.TouchHandler touchHandler) {
super.setOnTouchListener(touchHandler);
mTouchHandler = touchHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 229acf4..4aad245 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -19,11 +19,11 @@
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.shade.PanelView.DEBUG;
import static java.lang.Float.isNaN;
@@ -53,7 +53,6 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.doze.DozeLog;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
@@ -77,7 +76,6 @@
import java.util.List;
public abstract class PanelViewController {
- public static final boolean DEBUG = PanelView.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
public static final float FLING_SPEED_UP_FACTOR = 0.6f;
@@ -97,7 +95,7 @@
protected boolean mTouchSlopExceededBeforeDown;
private float mMinExpandHeight;
private boolean mPanelUpdateWhenAnimatorEnds;
- private boolean mVibrateOnOpening;
+ private final boolean mVibrateOnOpening;
protected boolean mIsLaunchAnimationRunning;
private int mFixedDuration = NO_FIXED_DURATION;
protected float mOverExpansion;
@@ -144,7 +142,6 @@
private int mTouchSlop;
private float mSlopMultiplier;
protected boolean mHintAnimationRunning;
- private boolean mOverExpandedBeforeFling;
private boolean mTouchAboveFalsingThreshold;
private int mUnlockFalsingThreshold;
private boolean mTouchStartedInEmptyArea;
@@ -155,9 +152,9 @@
private ValueAnimator mHeightAnimator;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
- private FlingAnimationUtils mFlingAnimationUtils;
- private FlingAnimationUtils mFlingAnimationUtilsClosing;
- private FlingAnimationUtils mFlingAnimationUtilsDismissing;
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+ private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
private final LatencyTracker mLatencyTracker;
private final FalsingManager mFalsingManager;
private final DozeLog mDozeLog;
@@ -174,13 +171,14 @@
private float mInitialTouchY;
private float mInitialTouchX;
private boolean mTouchDisabled;
+ private boolean mInitialTouchFromKeyguard;
/**
* Whether or not the PanelView can be expanded or collapsed with a drag.
*/
- private boolean mNotificationsDragEnabled;
+ private final boolean mNotificationsDragEnabled;
- private Interpolator mBounceInterpolator;
+ private final Interpolator mBounceInterpolator;
protected KeyguardBottomAreaView mKeyguardBottomArea;
/**
@@ -201,7 +199,6 @@
protected final AmbientState mAmbientState;
protected final LockscreenGestureLogger mLockscreenGestureLogger;
private final PanelExpansionStateManager mPanelExpansionStateManager;
- private final TouchHandler mTouchHandler;
private final InteractionJankMonitor mInteractionJankMonitor;
protected final SystemClock mSystemClock;
@@ -229,8 +226,6 @@
return mAmbientState;
}
- private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-
public PanelViewController(
PanelView view,
FalsingManager falsingManager,
@@ -247,9 +242,7 @@
PanelExpansionStateManager panelExpansionStateManager,
AmbientState ambientState,
InteractionJankMonitor interactionJankMonitor,
- KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SystemClock systemClock) {
- mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
@@ -261,7 +254,7 @@
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
mPanelExpansionStateManager = panelExpansionStateManager;
- mTouchHandler = createTouchHandler();
+ TouchHandler touchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -274,7 +267,7 @@
});
mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(mTouchHandler);
+ mView.setOnTouchListener(touchHandler);
mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
mResources = mView.getResources();
@@ -398,11 +391,12 @@
public void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
- beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ beginJankMonitoring();
}
mInitialOffsetOnTouch = expandedHeight;
mInitialTouchY = newY;
mInitialTouchX = newX;
+ mInitialTouchFromKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
if (startTracking) {
mTouchSlopExceeded = true;
setExpandedHeight(mInitialOffsetOnTouch);
@@ -425,20 +419,14 @@
mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
final boolean expand;
- if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- // If the keyguard is fading away, don't expand it again. This can happen if you're
- // swiping to unlock, the app below the keyguard is in landscape, and the screen
- // rotates while your finger is still down after the swipe to unlock.
- if (mKeyguardStateController.isKeyguardFadingAway()) {
- expand = false;
- } else if (onKeyguard) {
+ if (mKeyguardStateController.isKeyguardFadingAway()
+ || (mInitialTouchFromKeyguard && !onKeyguard)) {
+ // Don't expand for any touches that started from the keyguard and ended after the
+ // keyguard is gone.
+ expand = false;
+ } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ if (onKeyguard) {
expand = true;
- } else if (mKeyguardStateController.isKeyguardFadingAway()) {
- // If we're in the middle of dismissing the keyguard, don't expand due to the
- // cancelled gesture. Gesture cancellation during an unlock is expected in some
- // situations, such keeping your finger down while swiping to unlock to an app
- // that is locked in landscape (the rotation will cancel the touch event).
- expand = false;
} else if (mCentralSurfaces.isBouncerShowingOverDream()) {
expand = false;
} else {
@@ -475,7 +463,7 @@
} else if (!mCentralSurfaces.isBouncerShowing()
&& !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
- boolean expands = onEmptySpaceClick(mInitialTouchX);
+ boolean expands = onEmptySpaceClick();
onTrackingStopped(expands);
}
mVelocityTracker.clear();
@@ -670,7 +658,7 @@
@Override
public void onAnimationStart(Animator animation) {
if (!mStatusBarStateController.isDozing()) {
- beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ beginJankMonitoring();
}
}
@@ -702,10 +690,8 @@
mIsSpringBackAnimation = true;
ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
animator.addUpdateListener(
- animation -> {
- setOverExpansionInternal((float) animation.getAnimatedValue(),
- false /* isFromGesture */);
- });
+ animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
+ false /* isFromGesture */));
animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addListener(new AnimatorListenerAdapter() {
@@ -731,10 +717,10 @@
setAnimator(null);
mKeyguardStateController.notifyPanelFlingEnd();
if (!cancelled) {
- endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ endJankMonitoring();
notifyExpandingFinished();
} else {
- cancelJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ cancelJankMonitoring();
}
updatePanelExpansionAndVisibility();
}
@@ -773,13 +759,6 @@
setExpandedHeight(currentMaxPanelHeight);
}
- private float getStackHeightFraction(float height) {
- final float gestureFraction = height / getMaxPanelHeight();
- final float stackHeightFraction = Interpolators.ACCELERATE_DECELERATE
- .getInterpolation(gestureFraction);
- return stackHeightFraction;
- }
-
public void setExpandedHeightInternal(float h) {
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
@@ -911,13 +890,8 @@
return !isFullyCollapsed() && !mTracking && !mClosing;
}
- private final Runnable mFlingCollapseRunnable = new Runnable() {
- @Override
- public void run() {
- fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
- false /* expandBecauseOfFalsing */);
- }
- };
+ private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
+ mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
public void expand(final boolean animate) {
if (!isFullyCollapsed() && !isCollapsing()) {
@@ -950,7 +924,7 @@
mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (mAnimateAfterExpanding) {
notifyExpandingStarted();
- beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ beginJankMonitoring();
fling(0, true /* expand */);
} else {
setExpandedFraction(1f);
@@ -1150,7 +1124,7 @@
*
* @return whether the panel will be expanded after the action performed by this method
*/
- protected boolean onEmptySpaceClick(float x) {
+ protected boolean onEmptySpaceClick() {
if (mHintAnimationRunning) {
return true;
}
@@ -1432,9 +1406,9 @@
// mHeightAnimator is null, there is no remaining frame, ends instrumenting.
if (mHeightAnimator == null) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- endJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ endJankMonitoring();
} else {
- cancelJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ cancelJankMonitoring();
}
}
break;
@@ -1465,28 +1439,32 @@
}
}
- private void beginJankMonitoring(int cuj) {
+ private void beginJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
}
InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(cuj, mView)
+ InteractionJankMonitor.Configuration.Builder.withView(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ mView)
.setTag(isFullyCollapsed() ? "Expand" : "Collapse");
mInteractionJankMonitor.begin(builder);
}
- private void endJankMonitoring(int cuj) {
+ private void endJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
}
- InteractionJankMonitor.getInstance().end(cuj);
+ InteractionJankMonitor.getInstance().end(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
- private void cancelJankMonitoring(int cuj) {
+ private void cancelJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
}
- InteractionJankMonitor.getInstance().cancel(cuj);
+ InteractionJankMonitor.getInstance().cancel(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
protected float getExpansionFraction() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 83ee125..aa610bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -1,20 +1,24 @@
/*
- * Copyright (C) 2018 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. You may obtain a copy of the License at
+ * 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.
+ * 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.phone;
+package com.android.systemui.shade;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
/**
* {@link ShadeController} is an abstraction of the work that used to be hard-coded in
@@ -86,10 +90,10 @@
boolean collapsePanel();
/**
- * If {@param animate}, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
+ * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
* the panel. Post collapse runnables will be executed
*
- * @param animate
+ * @param animate true to animate the collapse, false for instantaneous collapse
*/
void collapsePanel(boolean animate);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d37ecbc..f389dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import android.util.Log;
import android.view.ViewTreeObserver;
@@ -23,12 +23,12 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import java.util.ArrayList;
import java.util.Optional;
@@ -37,7 +37,7 @@
import dagger.Lazy;
-/** An implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
+/** An implementation of {@link ShadeController}. */
@SysUISingleton
public class ShadeControllerImpl implements ShadeController {
@@ -157,7 +157,8 @@
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- if (getCentralSurfaces().getNotificationShadeWindowView().isVisibleToUser()) {
+ if (getCentralSurfaces().getNotificationShadeWindowView()
+ .isVisibleToUser()) {
getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
getNotificationPanelViewController().getView().post(executable);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 3b3b5a2..cb414ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -50,16 +50,6 @@
boolean isDeviceInVrMode();
/**
- * Updates the visual representation of the notifications.
- */
- void updateNotificationViews(String reason);
-
- /**
- * Called when the row states are updated by {@link NotificationViewHierarchyManager}.
- */
- void onUpdateRowStates();
-
- /**
* @return true if the shade is collapsing.
*/
boolean isCollapsing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
deleted file mode 100644
index 054543c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ /dev/null
@@ -1,631 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Assert;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-import java.util.Stack;
-
-/**
- * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
- * on their group structure. For example, if a notification becomes bundled with another,
- * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will
- * tell NotificationListContainer which notifications to display, and inform it of changes to those
- * notifications that might affect their display.
- */
-public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {
- private static final String TAG = "NotificationViewHierarchyManager";
-
- private final Handler mHandler;
-
- /**
- * Re-usable map of top-level notifications to their sorted children if any.
- * If the top-level notification doesn't have children, its key will still exist in this map
- * with its value explicitly set to null.
- */
- private final HashMap<NotificationEntry, List<NotificationEntry>> mTmpChildOrderMap =
- new HashMap<>();
-
- // Dependencies:
- private final DynamicChildBindController mDynamicChildBindController;
- private final FeatureFlags mFeatureFlags;
- protected final NotificationLockscreenUserManager mLockscreenUserManager;
- protected final NotificationGroupManagerLegacy mGroupManager;
- protected final VisualStabilityManager mVisualStabilityManager;
- private final SysuiStatusBarStateController mStatusBarStateController;
- private final NotificationEntryManager mEntryManager;
- private final LowPriorityInflationHelper mLowPriorityInflationHelper;
-
- /**
- * {@code true} if notifications not part of a group should by default be rendered in their
- * expanded state. If {@code false}, then only the first notification will be expanded if
- * possible.
- */
- private final boolean mAlwaysExpandNonGroupedNotification;
- private final Optional<Bubbles> mBubblesOptional;
- private final DynamicPrivacyController mDynamicPrivacyController;
- private final KeyguardBypassController mBypassController;
- private final NotifPipelineFlags mNotifPipelineFlags;
- private AssistantFeedbackController mAssistantFeedbackController;
- private final KeyguardStateController mKeyguardStateController;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final Context mContext;
-
- private NotificationPresenter mPresenter;
- private NotifStackController mStackController;
- private NotificationListContainer mListContainer;
-
- // Used to help track down re-entrant calls to our update methods, which will cause bugs.
- private boolean mPerformingUpdate;
- // Hack to get around re-entrant call in onDynamicPrivacyChanged() until we can track down
- // the problem.
- private boolean mIsHandleDynamicPrivacyChangeScheduled;
-
- /**
- * Injected constructor. See {@link CentralSurfacesModule}.
- */
- public NotificationViewHierarchyManager(
- Context context,
- @Main Handler mainHandler,
- FeatureFlags featureFlags,
- NotificationLockscreenUserManager notificationLockscreenUserManager,
- NotificationGroupManagerLegacy groupManager,
- VisualStabilityManager visualStabilityManager,
- StatusBarStateController statusBarStateController,
- NotificationEntryManager notificationEntryManager,
- KeyguardBypassController bypassController,
- Optional<Bubbles> bubblesOptional,
- DynamicPrivacyController privacyController,
- DynamicChildBindController dynamicChildBindController,
- LowPriorityInflationHelper lowPriorityInflationHelper,
- AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController) {
- mContext = context;
- mHandler = mainHandler;
- mFeatureFlags = featureFlags;
- mLockscreenUserManager = notificationLockscreenUserManager;
- mBypassController = bypassController;
- mGroupManager = groupManager;
- mVisualStabilityManager = visualStabilityManager;
- mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
- mEntryManager = notificationEntryManager;
- mNotifPipelineFlags = notifPipelineFlags;
- Resources res = context.getResources();
- mAlwaysExpandNonGroupedNotification =
- res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mBubblesOptional = bubblesOptional;
- mDynamicPrivacyController = privacyController;
- mDynamicChildBindController = dynamicChildBindController;
- mLowPriorityInflationHelper = lowPriorityInflationHelper;
- mAssistantFeedbackController = assistantFeedbackController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardStateController = keyguardStateController;
- }
-
- public void setUpWithPresenter(NotificationPresenter presenter,
- NotifStackController stackController,
- NotificationListContainer listContainer) {
- mPresenter = presenter;
- mStackController = stackController;
- mListContainer = listContainer;
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mDynamicPrivacyController.addListener(this);
- }
- }
-
- /**
- * Updates the visual representation of the notifications.
- */
- //TODO: Rewrite this to focus on Entries, or some other data object instead of views
- public void updateNotificationViews() {
- Assert.isMainThread();
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
- Trace.beginSection("NotificationViewHierarchyManager.updateNotificationViews");
-
- beginUpdate();
-
- boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked()
- && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
- KeyguardUpdateMonitor.getCurrentUser()))
- && !mKeyguardStateController.isKeyguardGoingAway();
- List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications();
- ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- final int N = activeNotifications.size();
- for (int i = 0; i < N; i++) {
- NotificationEntry ent = activeNotifications.get(i);
- if (shouldSuppressActiveNotification(ent)) {
- continue;
- }
-
- int userId = ent.getSbn().getUserId();
-
- // Display public version of the notification if we need to redact.
- // TODO: This area uses a lot of calls into NotificationLockscreenUserManager.
- // We can probably move some of this code there.
- int currentUserId = mLockscreenUserManager.getCurrentUserId();
- boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId);
- boolean userPublic = devicePublic
- || mLockscreenUserManager.isLockscreenPublicMode(userId);
- if (userPublic && dynamicallyUnlocked
- && (userId == currentUserId || userId == UserHandle.USER_ALL
- || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) {
- userPublic = false;
- }
- boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent);
- boolean sensitive = userPublic && needsRedaction;
- boolean deviceSensitive = devicePublic
- && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
- currentUserId);
- ent.setSensitive(sensitive, deviceSensitive);
- ent.getRow().setNeedsRedaction(needsRedaction);
- mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
- boolean isChildInGroup = mGroupManager.isChildInGroup(ent);
-
- boolean groupChangesAllowed =
- mVisualStabilityManager.areGroupChangesAllowed() // user isn't looking at notifs
- || !ent.hasFinishedInitialization(); // notif recently added
-
- NotificationEntry parent = mGroupManager.getGroupSummary(ent);
- if (!groupChangesAllowed) {
- // We don't to change groups while the user is looking at them
- boolean wasChildInGroup = ent.isChildInGroup();
- if (isChildInGroup && !wasChildInGroup) {
- isChildInGroup = wasChildInGroup;
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
- false /* persistent */);
- } else if (!isChildInGroup && wasChildInGroup) {
- // We allow grouping changes if the group was collapsed
- if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
- isChildInGroup = wasChildInGroup;
- parent = ent.getRow().getNotificationParent().getEntry();
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
- false /* persistent */);
- }
- }
- }
-
- if (isChildInGroup) {
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent);
- if (orderedChildren == null) {
- orderedChildren = new ArrayList<>();
- mTmpChildOrderMap.put(parent, orderedChildren);
- }
- orderedChildren.add(ent);
- } else {
- // Top-level notif (either a summary or single notification)
-
- // A child may have already added its summary to mTmpChildOrderMap with a
- // list of children. This can happen since there's no guarantee summaries are
- // sorted before its children.
- if (!mTmpChildOrderMap.containsKey(ent)) {
- // mTmpChildOrderMap's keyset is used to iterate through all entries, so it's
- // necessary to add each top-level notif as a key
- mTmpChildOrderMap.put(ent, null);
- }
- toShow.add(ent.getRow());
- }
-
- }
-
- ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
- for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-
- // Blocking helper is effectively a detached view. Don't bother removing it from the
- // layout.
- if (!row.isBlockingHelperShowing()) {
- viewsToRemove.add((ExpandableNotificationRow) child);
- }
- }
- }
-
- for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
- NotificationEntry entry = viewToRemove.getEntry();
- if (mEntryManager.getPendingOrActiveNotif(entry.getKey()) != null
- && !shouldSuppressActiveNotification(entry)) {
- // we are only transferring this notification to its parent, don't generate an
- // animation. If the notification is suppressed, this isn't a transfer.
- mListContainer.setChildTransferInProgress(true);
- }
- if (viewToRemove.isSummaryWithChildren()) {
- viewToRemove.removeAllChildren();
- }
- mListContainer.removeContainerView(viewToRemove);
- mListContainer.setChildTransferInProgress(false);
- }
-
- removeNotificationChildren();
-
- for (int i = 0; i < toShow.size(); i++) {
- View v = toShow.get(i);
- if (v.getParent() == null) {
- mVisualStabilityManager.notifyViewAddition(v);
- mListContainer.addContainerView(v);
- } else if (!mListContainer.containsView(v)) {
- // the view is added somewhere else. Let's make sure
- // the ordering works properly below, by excluding these
- toShow.remove(v);
- i--;
- }
- }
-
- addNotificationChildrenAndSort();
-
- // So after all this work notifications still aren't sorted correctly.
- // Let's do that now by advancing through toShow and mListContainer in
- // lock-step, making sure mListContainer matches what we see in toShow.
- int j = 0;
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
- if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) {
- // Don't count/reorder notifications that are showing the blocking helper!
- continue;
- }
-
- ExpandableNotificationRow targetChild = toShow.get(j);
- if (child != targetChild) {
- // Oops, wrong notification at this position. Put the right one
- // here and advance both lists.
- if (mVisualStabilityManager.canReorderNotification(targetChild)) {
- mListContainer.changeViewPosition(targetChild, i);
- } else {
- mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager,
- false /* persistent */);
- }
- }
- j++;
-
- }
-
- mDynamicChildBindController.updateContentViews(mTmpChildOrderMap);
- mVisualStabilityManager.onReorderingFinished();
- // clear the map again for the next usage
- mTmpChildOrderMap.clear();
-
- updateRowStatesInternal();
- updateNotifStats();
-
- mListContainer.onNotificationViewUpdateFinished();
-
- endUpdate();
- Trace.endSection();
- }
-
- /**
- * In the spirit of unidirectional data flow, calculate this information when the notification
- * views are updated, and set it once, speeding up lookups later.
- * This is analogous to logic in the
- * {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator}
- */
- private void updateNotifStats() {
- Trace.beginSection("NotificationViewHierarchyManager.updateNotifStats");
- boolean hasNonClearableAlertingNotifs = false;
- boolean hasClearableAlertingNotifs = false;
- boolean hasNonClearableSilentNotifs = false;
- boolean hasClearableSilentNotifs = false;
- final int childCount = mListContainer.getContainerChildCount();
- int visibleTopLevelEntries = 0;
- for (int i = 0; i < childCount; i++) {
- View child = mListContainer.getContainerChildAt(i);
- if (child == null || child.getVisibility() == View.GONE) {
- continue;
- }
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT;
- // NOTE: NotificationEntry.isClearable() will internally check group children to ensure
- // the group itself definitively clearable.
- boolean isClearable = row.getEntry().isClearable();
- visibleTopLevelEntries++;
- if (isSilent) {
- if (isClearable) {
- hasClearableSilentNotifs = true;
- } else { // !isClearable
- hasNonClearableSilentNotifs = true;
- }
- } else { // !isSilent
- if (isClearable) {
- hasClearableAlertingNotifs = true;
- } else { // !isClearable
- hasNonClearableAlertingNotifs = true;
- }
- }
- }
- mStackController.setNotifStats(new NotifStats(
- visibleTopLevelEntries /* numActiveNotifs */,
- hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */,
- hasClearableAlertingNotifs /* hasClearableAlertingNotifs */,
- hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */,
- hasClearableSilentNotifs /* hasClearableSilentNotifs */
- ));
- Trace.endSection();
- }
-
- /**
- * Should a notification entry from the active list be suppressed and not show?
- */
- private boolean shouldSuppressActiveNotification(NotificationEntry ent) {
- final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
- && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
- ent.getKey(), ent.getSbn().getGroupKey());
- if (ent.isRowDismissed() || ent.isRowRemoved()
- || isBubbleNotificationSuppressedFromShade) {
- // we want to suppress removed notifications because they could
- // temporarily become children if they were isolated before.
- return true;
- }
- return false;
- }
-
- private void addNotificationChildrenAndSort() {
- // Let's now add all notification children which are missing
- boolean orderChanged = false;
- ArrayList<ExpandableNotificationRow> orderedRows = new ArrayList<>();
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View view = mListContainer.getContainerChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getAttachedChildren();
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
- if (orderedChildren == null) {
- // Not a group
- continue;
- }
- parent.setUntruncatedChildCount(orderedChildren.size());
- for (int childIndex = 0; childIndex < orderedChildren.size(); childIndex++) {
- ExpandableNotificationRow childView = orderedChildren.get(childIndex).getRow();
- if (children == null || !children.contains(childView)) {
- if (childView.getParent() != null) {
- Log.wtf(TAG, "trying to add a notification child that already has "
- + "a parent. class:" + childView.getParent().getClass()
- + "\n child: " + childView);
- // This shouldn't happen. We can recover by removing it though.
- ((ViewGroup) childView.getParent()).removeView(childView);
- }
- mVisualStabilityManager.notifyViewAddition(childView);
- parent.addChildNotification(childView, childIndex);
- mListContainer.notifyGroupChildAdded(childView);
- }
- orderedRows.add(childView);
- }
-
- // Finally after removing and adding has been performed we can apply the order.
- orderChanged |= parent.applyChildOrder(orderedRows, mVisualStabilityManager,
- mEntryManager);
- orderedRows.clear();
- }
- if (orderChanged) {
- mListContainer.generateChildOrderChangedEvent();
- }
- }
-
- private void removeNotificationChildren() {
- // First let's remove all children which don't belong in the parents
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
- for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
- View view = mListContainer.getContainerChildAt(i);
- if (!(view instanceof ExpandableNotificationRow)) {
- // We don't care about non-notification views.
- continue;
- }
-
- ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getAttachedChildren();
- List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
-
- if (children != null) {
- toRemove.clear();
- for (ExpandableNotificationRow childRow : children) {
- if ((orderedChildren == null
- || !orderedChildren.contains(childRow.getEntry()))
- && !childRow.keepInParent()) {
- toRemove.add(childRow);
- }
- }
- for (ExpandableNotificationRow remove : toRemove) {
- parent.removeChildNotification(remove);
- if (mEntryManager.getActiveNotificationUnfiltered(
- remove.getEntry().getSbn().getKey()) == null) {
- // We only want to add an animation if the view is completely removed
- // otherwise it's just a transfer
- mListContainer.notifyGroupChildRemoved(remove,
- parent.getChildrenContainer());
- }
- }
- }
- }
- }
-
- /**
- * Updates expanded, dimmed and locked states of notification rows.
- */
- public void updateRowStates() {
- Assert.isMainThread();
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
-
- beginUpdate();
- updateRowStatesInternal();
- endUpdate();
- }
-
- private void updateRowStatesInternal() {
- Trace.beginSection("NotificationViewHierarchyManager.updateRowStates");
- final int N = mListContainer.getContainerChildCount();
-
- int visibleNotifications = 0;
- boolean onKeyguard =
- mStatusBarStateController.getCurrentOrUpcomingState() == StatusBarState.KEYGUARD;
- Stack<ExpandableNotificationRow> stack = new Stack<>();
- for (int i = N - 1; i >= 0; i--) {
- View child = mListContainer.getContainerChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- stack.push((ExpandableNotificationRow) child);
- }
- while(!stack.isEmpty()) {
- ExpandableNotificationRow row = stack.pop();
- NotificationEntry entry = row.getEntry();
- boolean isChildNotification = mGroupManager.isChildInGroup(entry);
-
- if (!onKeyguard) {
- // If mAlwaysExpandNonGroupedNotification is false, then only expand the
- // very first notification and if it's not a child of grouped notifications.
- row.setSystemExpanded(mAlwaysExpandNonGroupedNotification
- || (visibleNotifications == 0 && !isChildNotification
- && !row.isLowPriority()));
- }
-
- int userId = entry.getSbn().getUserId();
- boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup(
- entry.getSbn()) && !entry.isRowRemoved();
- boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry);
- if (!showOnKeyguard) {
- // min priority notifications should show if their summary is showing
- if (mGroupManager.isChildInGroup(entry)) {
- NotificationEntry summary = mGroupManager.getLogicalGroupSummary(entry);
- if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(summary)) {
- showOnKeyguard = true;
- }
- }
- }
- if (suppressedSummary
- || mLockscreenUserManager.shouldHideNotifications(userId)
- || (onKeyguard && !showOnKeyguard)) {
- entry.getRow().setVisibility(View.GONE);
- } else {
- boolean wasGone = entry.getRow().getVisibility() == View.GONE;
- if (wasGone) {
- entry.getRow().setVisibility(View.VISIBLE);
- }
- if (!isChildNotification && !entry.getRow().isRemoved()) {
- if (wasGone) {
- // notify the scroller of a child addition
- mListContainer.generateAddAnimation(entry.getRow(),
- !showOnKeyguard /* fromMoreCard */);
- }
- visibleNotifications++;
- }
- }
- if (row.isSummaryWithChildren()) {
- List<ExpandableNotificationRow> notificationChildren =
- row.getAttachedChildren();
- int size = notificationChildren.size();
- for (int i = size - 1; i >= 0; i--) {
- stack.push(notificationChildren.get(i));
- }
- }
- row.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry));
- row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
- }
-
- Trace.beginSection("NotificationPresenter#onUpdateRowStates");
- mPresenter.onUpdateRowStates();
- Trace.endSection();
- Trace.endSection();
- }
-
- @Override
- public void onDynamicPrivacyChanged() {
- mNotifPipelineFlags.assertLegacyPipelineEnabled();
- if (mPerformingUpdate) {
- Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call");
- }
- // This listener can be called from updateNotificationViews() via a convoluted listener
- // chain, so we post here to prevent a re-entrant call. See b/136186188
- // TODO: Refactor away the need for this
- if (!mIsHandleDynamicPrivacyChangeScheduled) {
- mIsHandleDynamicPrivacyChangeScheduled = true;
- mHandler.post(this::onHandleDynamicPrivacyChanged);
- }
- }
-
- private void onHandleDynamicPrivacyChanged() {
- mIsHandleDynamicPrivacyChangeScheduled = false;
- updateNotificationViews();
- }
-
- private void beginUpdate() {
- if (mPerformingUpdate) {
- Log.wtf(TAG, "Re-entrant code during update", new Exception());
- }
- mPerformingUpdate = true;
- }
-
- private void endUpdate() {
- if (!mPerformingUpdate) {
- Log.wtf(TAG, "Manager state has become desynced", new Exception());
- }
- mPerformingUpdate = false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 3013ad0..a57d849b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -61,21 +61,19 @@
private int mVisibleState = -1;
private DualToneHandler mDualToneHandler;
private boolean mForceHidden;
- private boolean mProviderModel;
/**
* Designated constructor
*/
public static StatusBarMobileView fromContext(
Context context,
- String slot,
- boolean providerModel
+ String slot
) {
LayoutInflater inflater = LayoutInflater.from(context);
StatusBarMobileView v = (StatusBarMobileView)
inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
v.setSlot(slot);
- v.init(providerModel);
+ v.init();
v.setVisibleState(STATE_ICON);
return v;
}
@@ -108,17 +106,12 @@
outRect.bottom += translationY;
}
- private void init(boolean providerModel) {
- mProviderModel = providerModel;
+ private void init() {
mDualToneHandler = new DualToneHandler(getContext());
mMobileGroup = findViewById(R.id.mobile_group);
mMobile = findViewById(R.id.mobile_signal);
mMobileType = findViewById(R.id.mobile_type);
- if (mProviderModel) {
- mMobileRoaming = findViewById(R.id.mobile_roaming_large);
- } else {
- mMobileRoaming = findViewById(R.id.mobile_roaming);
- }
+ mMobileRoaming = findViewById(R.id.mobile_roaming);
mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space);
mIn = findViewById(R.id.mobile_in);
mOut = findViewById(R.id.mobile_out);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
index 6914ae6..1638780 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/CallbackHandler.java
@@ -21,6 +21,7 @@
import android.telephony.SubscriptionInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
@@ -36,6 +37,7 @@
* Implements network listeners and forwards the calls along onto other listeners but on
* the current or specified Looper.
*/
+@SysUISingleton
public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
private static final String TAG = "CallbackHandler";
private static final int MSG_EMERGENCE_CHANGED = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
index f0e52f1..7938179 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt
@@ -21,12 +21,14 @@
import android.telephony.TelephonyManager
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileStatusTracker
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
/**
* Factory to make MobileSignalController injectable
*/
+@SysUISingleton
internal class MobileSignalControllerFactory @Inject constructor(
val context: Context,
val callbackHandler: CallbackHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 9e77dbc..48e3450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -23,13 +23,11 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -42,23 +40,16 @@
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.RemoteInputNotificationRebuilder;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -73,13 +64,11 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
-import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -182,47 +171,6 @@
NotificationRemoteInputManager.Callback provideNotificationRemoteInputManagerCallback(
StatusBarRemoteInputCallback callbackImpl);
- /** */
- @SysUISingleton
- @Provides
- static NotificationViewHierarchyManager provideNotificationViewHierarchyManager(
- Context context,
- @Main Handler mainHandler,
- FeatureFlags featureFlags,
- NotificationLockscreenUserManager notificationLockscreenUserManager,
- NotificationGroupManagerLegacy groupManager,
- VisualStabilityManager visualStabilityManager,
- StatusBarStateController statusBarStateController,
- NotificationEntryManager notificationEntryManager,
- KeyguardBypassController bypassController,
- Optional<Bubbles> bubblesOptional,
- DynamicPrivacyController privacyController,
- DynamicChildBindController dynamicChildBindController,
- LowPriorityInflationHelper lowPriorityInflationHelper,
- AssistantFeedbackController assistantFeedbackController,
- NotifPipelineFlags notifPipelineFlags,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController) {
- return new NotificationViewHierarchyManager(
- context,
- mainHandler,
- featureFlags,
- notificationLockscreenUserManager,
- groupManager,
- visualStabilityManager,
- statusBarStateController,
- notificationEntryManager,
- bypassController,
- bubblesOptional,
- privacyController,
- dynamicChildBindController,
- lowPriorityInflationHelper,
- assistantFeedbackController,
- notifPipelineFlags,
- keyguardUpdateMonitor,
- keyguardStateController);
- }
-
/**
* Provides our instance of CommandQueue which is considered optional.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 0c6a91f..7fbdd35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -29,10 +29,6 @@
val featureFlags: FeatureFlags
) {
fun checkLegacyPipelineEnabled(): Boolean {
- if (!isNewPipelineEnabled()) {
- return true
- }
-
if (Compile.IS_DEBUG) {
Toast.makeText(context, "Old pipeline code running!", Toast.LENGTH_SHORT).show()
}
@@ -45,11 +41,6 @@
return false
}
- fun assertLegacyPipelineEnabled(): Unit =
- check(!isNewPipelineEnabled()) { "Old pipeline code running w/ new pipeline enabled" }
-
- fun isNewPipelineEnabled(): Boolean = true
-
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 852d5f7..7583a98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -46,11 +46,9 @@
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
@@ -113,7 +111,6 @@
private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
private final LeakDetector mLeakDetector;
private final IStatusBarService mStatusBarService;
- private final NotifLiveDataStoreImpl mNotifLiveDataStore;
private final DumpManager mDumpManager;
private final Executor mBgExecutor;
@@ -141,7 +138,6 @@
private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
private LegacyNotificationRanker mRanker = new LegacyNotificationRankerStub();
- private NotificationPresenter mPresenter;
private RankingMap mLatestRankingMap;
@VisibleForTesting
@@ -161,7 +157,6 @@
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
- NotifLiveDataStoreImpl notifLiveDataStore,
DumpManager dumpManager,
@Background Executor bgExecutor
) {
@@ -172,7 +167,6 @@
mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
mLeakDetector = leakDetector;
mStatusBarService = statusBarService;
- mNotifLiveDataStore = notifLiveDataStore;
mDumpManager = dumpManager;
mBgExecutor = bgExecutor;
}
@@ -250,10 +244,6 @@
mRemoveInterceptors.remove(interceptor);
}
- public void setUpWithPresenter(NotificationPresenter presenter) {
- mPresenter = presenter;
- }
-
/** Adds multiple {@link NotificationLifetimeExtender}s. */
public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
for (NotificationLifetimeExtender extender : extenders) {
@@ -663,11 +653,6 @@
listener.onEntryBind(entry, notification);
}
- // Construct the expanded view.
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
- }
-
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -724,10 +709,6 @@
listener.onEntryUpdated(entry, fromSystem);
}
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback);
- }
-
updateNotifications("updateNotificationInternal");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -752,17 +733,7 @@
* @param reason why the notifications are updating
*/
public void updateNotifications(String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.updateNotifications");
- reapplyFilterAndSort(reason);
- if (mPresenter != null) {
- mPresenter.updateNotificationViews(reason);
- }
- mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications());
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("updateNotifications", reason);
}
public void updateNotificationRanking(RankingMap rankingMap) {
@@ -939,26 +910,12 @@
/** Resorts / filters the current notification set with the current RankingMap */
public void reapplyFilterAndSort(String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.reapplyFilterAndSort");
- updateRankingAndSort(mRanker.getRankingMap(), reason);
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("reapplyFilterAndSort", reason);
}
/** Calls to NotificationRankingManager and updates mSortedAndFiltered */
private void updateRankingAndSort(RankingMap rankingMap, String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
- return;
- }
- Trace.beginSection("NotificationEntryManager.updateRankingAndSort");
- mSortedAndFiltered.clear();
- mSortedAndFiltered.addAll(mRanker.updateRanking(
- rankingMap, mActiveNotifications.values(), reason));
- Trace.endSection();
+ mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason);
}
/** dump the current active notification list. Called from CentralSurfaces */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 126a986..dbf4810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -18,8 +18,10 @@
import android.animation.ObjectAnimator
import android.util.FloatProperty
+import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,17 +34,20 @@
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.min
@SysUISingleton
class NotificationWakeUpCoordinator @Inject constructor(
+ dumpManager: DumpManager,
private val mHeadsUpManager: HeadsUpManager,
private val statusBarStateController: StatusBarStateController,
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController
-) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
+) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener,
+ Dumpable {
private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
"notificationVisibility") {
@@ -60,6 +65,7 @@
private var mLinearDozeAmount: Float = 0.0f
private var mDozeAmount: Float = 0.0f
+ private var mDozeAmountSource: String = "init"
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
@@ -142,6 +148,7 @@
}
init {
+ dumpManager.registerDumpable(this)
mHeadsUpManager.addListener(this)
statusBarStateController.addCallback(this)
addListener(object : WakeUpListener {
@@ -248,13 +255,14 @@
// Let's notify the scroller that an animation started
notifyAnimationStart(mLinearDozeAmount == 1.0f)
}
- setDozeAmount(linear, eased)
+ setDozeAmount(linear, eased, source = "StatusBar")
}
- fun setDozeAmount(linear: Float, eased: Float) {
+ fun setDozeAmount(linear: Float, eased: Float, source: String) {
val changed = linear != mLinearDozeAmount
mLinearDozeAmount = linear
mDozeAmount = eased
+ mDozeAmountSource = source
mStackScrollerController.setDozeAmount(mDozeAmount)
updateHideAmount()
if (changed && linear == 0.0f) {
@@ -271,7 +279,7 @@
// undefined state, so it's an indication that we should do state cleanup. We override
// the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
// See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
- setDozeAmount(0f, 0f)
+ setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
}
if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -311,12 +319,11 @@
*/
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
- var amount = 1.0f
- if (statusBarStateController.state == StatusBarState.SHADE ||
- statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
- amount = 0.0f
+ if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
+ } else {
+ setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
}
- setDozeAmount(amount, amount)
return true
}
return false
@@ -332,7 +339,7 @@
*/
private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
- setDozeAmount(1f, 1f)
+ setDozeAmount(1f, 1f, source = "Override: animating screen off")
return true
}
@@ -426,4 +433,24 @@
*/
@JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
}
-}
\ No newline at end of file
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("mLinearDozeAmount: $mLinearDozeAmount")
+ pw.println("mDozeAmount: $mDozeAmount")
+ pw.println("mDozeAmountSource: $mDozeAmountSource")
+ pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
+ pw.println("mNotificationsVisible: $mNotificationsVisible")
+ pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
+ pw.println("mVisibilityAmount: $mVisibilityAmount")
+ pw.println("mLinearVisibilityAmount: $mLinearVisibilityAmount")
+ pw.println("pulseExpanding: $pulseExpanding")
+ pw.println("state: ${StatusBarState.toString(state)}")
+ pw.println("fullyAwake: $fullyAwake")
+ pw.println("wakingUp: $wakingUp")
+ pw.println("willWakeUp: $willWakeUp")
+ pw.println("collapsedEnoughToHide: $collapsedEnoughToHide")
+ pw.println("pulsing: $pulsing")
+ pw.println("notificationsFullyHidden: $notificationsFullyHidden")
+ pw.println("canShowPulsingHuns: $canShowPulsingHuns")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 4c406e3..d8dae5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -336,7 +336,7 @@
}
fun logPipelineRunSuppressed() =
- buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
+ buffer.log(TAG, INFO, {}, { "Suppressing pipeline run during animation." })
}
private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index bf08fc7..fac234c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -36,6 +36,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.NotifPanelEventsModule;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
@@ -85,7 +86,6 @@
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.wmshell.BubblesManager;
@@ -127,7 +127,6 @@
Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
LeakDetector leakDetector,
IStatusBarService statusBarService,
- NotifLiveDataStoreImpl notifLiveDataStore,
DumpManager dumpManager,
@Background Executor bgExecutor) {
return new NotificationEntryManager(
@@ -138,7 +137,6 @@
notificationRemoteInputManagerLazy,
leakDetector,
statusBarService,
- notifLiveDataStore,
dumpManager,
bgExecutor);
}
@@ -174,7 +172,6 @@
accessibilityManager,
highPriorityProvider,
notificationManager,
- notificationEntryManager,
peopleSpaceWidgetManager,
launcherApps,
shortcutManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index a72b381..558fd62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -37,7 +37,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -60,13 +59,11 @@
private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
- private final NotificationFilter mNotificationFilter;
private final ContentResolver mContentResolver;
private final PowerManager mPowerManager;
private final IDreamManager mDreamManager;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatteryController mBatteryController;
- private final ContentObserver mHeadsUpObserver;
private final HeadsUpManager mHeadsUpManager;
private final NotificationInterruptLogger mLogger;
private final NotifPipelineFlags mFlags;
@@ -81,7 +78,6 @@
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter notificationFilter,
BatteryController batteryController,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
@@ -95,14 +91,13 @@
mDreamManager = dreamManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
- mNotificationFilter = notificationFilter;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManager;
mLogger = logger;
mFlags = flags;
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mHeadsUpObserver = new ContentObserver(mainHandler) {
+ ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
boolean wasUsing = mUseHeadsUp;
@@ -125,12 +120,12 @@
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
true,
- mHeadsUpObserver);
+ headsUpObserver);
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
- mHeadsUpObserver);
+ headsUpObserver);
}
- mHeadsUpObserver.onChange(true); // set up
+ headsUpObserver.onChange(true); // set up
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index c2c40d8..4939a9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -47,8 +47,8 @@
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 27aa4b3..7120fe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -68,10 +68,10 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.wmshell.BubblesManager;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 8a7155a..c4ff259 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -21,7 +21,6 @@
import android.app.INotificationManager;
import android.app.NotificationChannel;
-import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
@@ -55,6 +54,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -62,16 +62,13 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
@@ -114,7 +111,6 @@
private NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private NotificationListContainer mListContainer;
- private CheckSaveListener mCheckSaveListener;
private OnSettingsClickListener mOnSettingsClickListener;
@VisibleForTesting
protected String mKeyToRemoveOnGutsClosed;
@@ -131,7 +127,6 @@
private final UserContextProvider mContextTracker;
private final UiEventLogger mUiEventLogger;
private final ShadeController mShadeController;
- private final AppWidgetManager mAppWidgetManager;
private NotifGutsViewListener mGutsListener;
/**
@@ -144,7 +139,6 @@
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
- NotificationEntryManager notificationEntryManager,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
LauncherApps launcherApps,
ShortcutManager shortcutManager,
@@ -173,17 +167,15 @@
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
dumpManager.registerDumpable(this);
}
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
- CheckSaveListener checkSave, OnSettingsClickListener onSettingsClick) {
+ OnSettingsClickListener onSettingsClick) {
mPresenter = presenter;
mListContainer = listContainer;
- mCheckSaveListener = checkSave;
mOnSettingsClickListener = onSettingsClick;
}
@@ -218,14 +210,11 @@
}
private void startAppDetailsSettingsActivity(String packageName, final int appUid,
- final NotificationChannel channel, ExpandableNotificationRow row) {
+ ExpandableNotificationRow row) {
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", packageName, null));
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- if (channel != null) {
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
- }
mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
}
@@ -233,7 +222,7 @@
ExpandableNotificationRow row) {
if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) {
if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
- startAppDetailsSettingsActivity(pkg, uid, null, row);
+ startAppDetailsSettingsActivity(pkg, uid, row);
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION);
intent.setData(Uri.fromParts("package", pkg, null));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index defae5b..427004e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -84,6 +84,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -108,7 +109,6 @@
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.util.Assert;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index defbfd8..cc539b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -69,6 +69,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -111,7 +112,6 @@
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
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 a0f386f..65ba5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -60,6 +60,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -161,7 +162,7 @@
private final Handler mHandler;
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index f4b7772..747c4de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -57,6 +57,7 @@
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
import com.android.systemui.statusbar.VibratorHelper;
@@ -76,7 +77,7 @@
public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
private final CentralSurfaces mCentralSurfaces;
private final Context mContext;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final NotificationPanelViewController mNotificationPanelViewController;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index c5aa512..a2ea0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -176,6 +176,7 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
@@ -194,14 +195,12 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.PowerButtonReveal;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -469,7 +468,7 @@
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final InitController mInitController;
private final PluginDependencyProvider mPluginDependencyProvider;
@@ -498,7 +497,6 @@
protected final NotificationEntryManager mEntryManager;
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
- private final NotificationViewHierarchyManager mViewHierarchyManager;
private final PanelExpansionStateManager mPanelExpansionStateManager;
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@@ -634,7 +632,6 @@
private final Optional<Bubbles> mBubblesOptional;
private final Bubbles.BubbleExpandListener mBubbleExpandListener;
private final Optional<StartingSurface> mStartingSurfaceOptional;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final ActivityIntentHelper mActivityIntentHelper;
private NotificationStackScrollLayoutController mStackScrollerController;
@@ -676,7 +673,6 @@
NotificationGutsManager notificationGutsManager,
NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- NotificationViewHierarchyManager notificationViewHierarchyManager,
PanelExpansionStateManager panelExpansionStateManager,
KeyguardViewMediator keyguardViewMediator,
DisplayMetrics displayMetrics,
@@ -739,7 +735,6 @@
WallpaperManager wallpaperManager,
Optional<StartingSurface> startingSurfaceOptional,
ActivityLaunchAnimator activityLaunchAnimator,
- NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController,
@@ -766,7 +761,6 @@
mGutsManager = notificationGutsManager;
mNotificationLogger = notificationLogger;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mViewHierarchyManager = notificationViewHierarchyManager;
mPanelExpansionStateManager = panelExpansionStateManager;
mKeyguardViewMediator = keyguardViewMediator;
mDisplayMetrics = displayMetrics;
@@ -827,7 +821,6 @@
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
- mNotifPipelineFlags = notifPipelineFlags;
mDreamManager = dreamManager;
lockscreenShadeTransitionController.setCentralSurfaces(this);
statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 6dbbf0d..fc8e7d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -30,7 +30,6 @@
import com.android.systemui.R;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -255,9 +254,7 @@
public void addMobileView(MobileIconState state) {
Log.d(TAG, "addMobileView: ");
- StatusBarMobileView view = StatusBarMobileView.fromContext(
- mContext, state.slot,
- mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS));
+ StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, state.slot);
view.applyMobileState(state);
view.setStaticDrawableColor(mColor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index ff3d122..cc451d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
@@ -57,6 +58,8 @@
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
@@ -125,6 +128,9 @@
}
};
+ @Nullable private KeyguardBottomAreaViewBinder.Binding mBinding;
+ private boolean mUsesBinder;
+
public KeyguardBottomAreaView(Context context) {
this(context, null);
}
@@ -142,13 +148,36 @@
super(context, attrs, defStyleAttr, defStyleRes);
}
- /** Initializes the {@link KeyguardBottomAreaView} with the given dependencies */
+ /**
+ * Initializes the view.
+ */
+ public void init(
+ final KeyguardBottomAreaViewModel viewModel,
+ final FalsingManager falsingManager) {
+ Log.i(TAG, System.identityHashCode(this) + " initialized with a binder");
+ mUsesBinder = true;
+ mBinding = KeyguardBottomAreaViewBinder.bind(this, viewModel, falsingManager);
+ }
+
+ /**
+ * Initializes the {@link KeyguardBottomAreaView} with the given dependencies
+ *
+ * @deprecated Use
+ * {@link #init(KeyguardBottomAreaViewModel, FalsingManager)} instead
+ */
+ @Deprecated
public void init(
FalsingManager falsingManager,
QuickAccessWalletController controller,
ControlsComponent controlsComponent,
QRCodeScannerController qrCodeScannerController) {
+ if (mUsesBinder) {
+ return;
+ }
+
+ Log.i(TAG, "initialized without a binder");
mFalsingManager = falsingManager;
+
mQuickAccessWalletController = controller;
mQuickAccessWalletController.setupWalletChangeObservers(
mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
@@ -174,6 +203,10 @@
* another {@link KeyguardBottomAreaView}
*/
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
+ if (mUsesBinder) {
+ return;
+ }
+
// if it exists, continue to use the original ambient indication container
// instead of the newly inflated one
if (mAmbientIndicationArea != null) {
@@ -201,6 +234,10 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ if (mUsesBinder) {
+ return;
+ }
+
mOverlayContainer = findViewById(R.id.overlay_container);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
@@ -229,6 +266,10 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ if (mUsesBinder) {
+ return;
+ }
+
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
@@ -237,6 +278,10 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ if (mUsesBinder) {
+ return;
+ }
+
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
if (mQuickAccessWalletController != null) {
@@ -259,6 +304,13 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ if (mUsesBinder) {
+ if (mBinding != null) {
+ mBinding.onConfigurationChanged();
+ }
+ return;
+ }
+
mIndicationBottomMargin = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
@@ -301,6 +353,10 @@
}
private void updateWalletVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mDozing
|| mQuickAccessWalletController == null
|| !mQuickAccessWalletController.isWalletEnabled()
@@ -318,6 +374,10 @@
}
private void updateControlsVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mControlsComponent == null) return;
mControlsButton.setImageResource(mControlsComponent.getTileImageId());
@@ -344,6 +404,10 @@
}
public void setDarkAmount(float darkAmount) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (darkAmount == mDarkAmount) {
return;
}
@@ -355,6 +419,10 @@
* Returns a list of animators to use to animate the indication areas.
*/
public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
+ if (mUsesBinder) {
+ return checkNotNull(mBinding).getIndicationAreaAnimators();
+ }
+
List<ViewPropertyAnimator> animators =
new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
animators.add(mIndicationArea.animate());
@@ -394,6 +462,10 @@
}
public void setDozing(boolean dozing, boolean animate) {
+ if (mUsesBinder) {
+ return;
+ }
+
mDozing = dozing;
updateWalletVisibility();
@@ -411,6 +483,10 @@
}
public void dozeTimeTick() {
+ if (mUsesBinder) {
+ return;
+ }
+
int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */)
- mBurnInYOffset;
mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount);
@@ -420,6 +496,10 @@
}
public void setAntiBurnInOffsetX(int burnInXOffset) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mBurnInXOffset == burnInXOffset) {
return;
}
@@ -435,6 +515,10 @@
* action buttons. Does not set the alpha of the lock icon.
*/
public void setComponentAlphas(float alpha) {
+ if (mUsesBinder) {
+ return;
+ }
+
setImportantForAccessibility(
alpha == 0f
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -461,6 +545,10 @@
}
private void updateQRCodeButtonVisibility() {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mQuickAccessWalletController != null
&& mQuickAccessWalletController.isWalletEnabled()) {
// Don't enable if quick access wallet is enabled
@@ -481,6 +569,10 @@
}
private void onQRCodeScannerClicked(View view) {
+ if (mUsesBinder) {
+ return;
+ }
+
Intent intent = mQRCodeScannerController.getIntent();
if (intent != null) {
try {
@@ -500,6 +592,10 @@
}
private void updateAffordanceColors() {
+ if (mUsesBinder) {
+ return;
+ }
+
int iconColor = Utils.getColorAttrDefaultColor(
mContext,
com.android.internal.R.attr.textColorPrimary);
@@ -516,6 +612,10 @@
}
private void onWalletClick(View v) {
+ if (mUsesBinder) {
+ return;
+ }
+
// More coming here; need to inform the user about how to proceed
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
@@ -531,6 +631,10 @@
}
private void onControlsClick(View v) {
+ if (mUsesBinder) {
+ return;
+ }
+
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 70ec13b14..4496607 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone
import android.annotation.ColorInt
-import android.graphics.Color
import android.graphics.Rect
import android.view.InsetsFlags
import android.view.ViewDebug
@@ -39,7 +38,13 @@
class LetterboxAppearance(
@Appearance val appearance: Int,
val appearanceRegions: Array<AppearanceRegion>
-)
+) {
+ override fun toString(): String {
+ val appearanceString =
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance)
+ return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}"
+ }
+}
/**
* Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
@@ -51,6 +56,7 @@
constructor(
private val lightBarController: LightBarController,
private val dumpManager: DumpManager,
+ private val letterboxBackgroundProvider: LetterboxBackgroundProvider,
) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {
private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
@@ -184,13 +190,11 @@
@ColorInt
private fun outerLetterboxBackgroundColor(): Int {
- // TODO(b/238607453): retrieve this information from WindowManager.
- return Color.BLACK
+ return letterboxBackgroundProvider.letterboxBackgroundColor
}
private fun isOuterLetterboxMultiColored(): Boolean {
- // TODO(b/238607453): retrieve this information from WindowManager.
- return false
+ return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored
}
private fun getEndSideIconsBounds(): Rect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
new file mode 100644
index 0000000..96b9aca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.phone
+
+import android.annotation.ColorInt
+import android.graphics.Color
+import android.os.RemoteException
+import android.view.IWindowManager
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Responsible for providing information about the background of letterboxed apps. */
+@CentralSurfacesScope
+class LetterboxBackgroundProvider
+@Inject
+constructor(
+ private val windowManager: IWindowManager,
+ @Background private val backgroundExecutor: Executor,
+ private val dumpManager: DumpManager,
+) : CentralSurfacesComponent.Startable, Dumpable {
+
+ @ColorInt
+ var letterboxBackgroundColor: Int = Color.BLACK
+ private set
+
+ var isLetterboxBackgroundMultiColored: Boolean = false
+ private set
+
+ override fun start() {
+ dumpManager.registerDumpable(javaClass.simpleName, this)
+
+ // Using a background executor, as binder calls to IWindowManager are blocking
+ backgroundExecutor.execute {
+ try {
+ isLetterboxBackgroundMultiColored = windowManager.isLetterboxBackgroundMultiColored
+ letterboxBackgroundColor = windowManager.letterboxBackgroundColorInArgb
+ } catch (e: RemoteException) {
+ e.rethrowFromSystemServer()
+ }
+ }
+ }
+
+ override fun stop() {
+ dumpManager.unregisterDumpable(javaClass.simpleName)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(
+ """
+ letterboxBackgroundColor: ${Color.valueOf(letterboxBackgroundColor)}
+ isLetterboxBackgroundMultiColored: $isLetterboxBackgroundMultiColored
+ """.trimIndent())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a2140c6ab..7b8c5fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -423,6 +423,21 @@
+ getActualPaddingEnd();
}
+ @VisibleForTesting
+ boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount,
+ int maxVisibleIcons) {
+ return speedBumpIndex != -1 && i >= speedBumpIndex
+ && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
+ }
+
+ @VisibleForTesting
+ boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
+ float iconSize) {
+ // Layout end, as used here, does not include padding end.
+ final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
+ return translationX >= overflowX;
+ }
+
/**
* Calculate the horizontal translations for each notification based on how much the icons
* are inserted into the notification container.
@@ -448,26 +463,26 @@
if (mFirstVisibleIconState == null) {
mFirstVisibleIconState = iconState;
}
- boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
- && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
- boolean isLastChild = i == childCount - 1;
- float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
- ? ((StatusBarIconView) view).getIconScaleIncreased()
- : 1f;
iconState.visibleState = iconState.hidden
? StatusBarIconView.STATE_HIDDEN
: StatusBarIconView.STATE_ICON;
- final float overflowDotX = layoutEnd - mIconSize;
- boolean isOverflowing = translationX > overflowDotX;
+ final boolean forceOverflow = shouldForceOverflow(i, mSpeedBumpIndex,
+ iconState.iconAppearAmount, maxVisibleIcons);
+ final boolean isOverflowing = forceOverflow || isOverflowing(
+ /* isLastChild= */ i == childCount - 1, translationX, layoutEnd, mIconSize);
- if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) {
- firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i;
+ // First icon to overflow.
+ if (firstOverflowIndex == -1 && isOverflowing) {
+ firstOverflowIndex = i;
mVisualOverflowStart = layoutEnd - mIconSize;
if (forceOverflow || mIsStaticLayout) {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
}
}
+ final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
+ ? ((StatusBarIconView) view).getIconScaleIncreased()
+ : 1f;
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
mNumDots = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 31d9266..30b640b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -38,7 +38,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -361,9 +360,7 @@
}
private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
- StatusBarMobileView view = StatusBarMobileView.fromContext(
- mContext, slot,
- mFeatureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS));
+ StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
return view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 2a2529a..dac532b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -19,6 +19,7 @@
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
@@ -56,6 +57,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -121,7 +123,7 @@
@Nullable
private final FoldAodAnimationController mFoldAodAnimationController;
private KeyguardMessageAreaController mKeyguardMessageAreaController;
- private final Lazy<ShadeController> mShadeController;
+ private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
private boolean mBouncerAnimating;
@@ -386,6 +388,7 @@
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else if (mShowing && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
+ && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
&& !mCentralSurfaces.isInLaunchTransition()
&& !isUnlockCollapsing()) {
mBouncer.setExpansion(fraction);
@@ -397,9 +400,8 @@
}
} else if (!mShowing && mBouncer.inTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
- // We need to keep propagating the expansion state to the bouncer, otherwise it will be
- // stuck in transit.
- mBouncer.setExpansion(fraction);
+ // We need to hide the bouncer, otherwise it will be stuck in transit.
+ mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
// unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -738,8 +740,10 @@
}
mNotificationShadeWindowController.setKeyguardOccluded(mOccluded);
- // setDozing(false) will call reset once we stop dozing.
- if (!mDozing) {
+ // setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
+ // no need to reset the keyguard views as we'll be gone shortly. Resetting now could cause
+ // unexpected visible behavior if the keyguard is still visible as we're animating unlocked.
+ if (!mDozing && !mKeyguardStateController.isKeyguardGoingAway()) {
// If Keyguard is reshown, don't hide the bouncer as it might just have been requested
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 451612a..374f091 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -53,6 +53,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -101,7 +102,7 @@
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private final KeyguardStateController mKeyguardStateController;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final LockPatternUtils mLockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 9c1512a..b1104ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -91,10 +91,8 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
- private final ScrimController mScrimController;
private final KeyguardIndicationController mKeyguardIndicationController;
private final CentralSurfaces mCentralSurfaces;
- private final ShadeController mShadeController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final CommandQueue mCommandQueue;
@@ -109,20 +107,19 @@
protected boolean mVrMode;
@Inject
- StatusBarNotificationPresenter(Context context,
+ StatusBarNotificationPresenter(
+ Context context,
NotificationPanelViewController panel,
HeadsUpManagerPhone headsUp,
NotificationShadeWindowView statusBarWindow,
ActivityStarter activityStarter,
NotificationStackScrollLayoutController stackScrollerController,
DozeScrimController dozeScrimController,
- ScrimController scrimController,
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
KeyguardIndicationController keyguardIndicationController,
CentralSurfaces centralSurfaces,
- ShadeController shadeController,
LockscreenShadeTransitionController shadeTransitionController,
CommandQueue commandQueue,
NotificationLockscreenUserManager lockscreenUserManager,
@@ -145,7 +142,6 @@
mKeyguardIndicationController = keyguardIndicationController;
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mCentralSurfaces = centralSurfaces;
- mShadeController = shadeController;
mShadeTransitionController = shadeTransitionController;
mCommandQueue = commandQueue;
mLockscreenUserManager = lockscreenUserManager;
@@ -161,7 +157,6 @@
R.id.notification_container_parent));
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDozeScrimController = dozeScrimController;
- mScrimController = scrimController;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -187,7 +182,7 @@
notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
mLockscreenUserManager.setUpWithPresenter(this);
mGutsManager.setUpWithPresenter(
- this, mNotifListContainer, mCheckSaveListener, mOnSettingsClickListener);
+ this, mNotifListContainer, mOnSettingsClickListener);
// ForegroundServiceNotificationListener adds its listener in its constructor
// but we need to request it here in order for it to be instantiated.
// TODO: figure out how to do this correctly once Dependency.get() is gone.
@@ -224,23 +219,6 @@
}
@Override
- public void updateNotificationViews(final String reason) {
- if (!mNotifPipelineFlags.checkLegacyPipelineEnabled()) {
- return;
- }
- // The function updateRowStates depends on both of these being non-null, so check them here.
- // We may be called before they are set from DeviceProvisionedController's callback.
- if (mScrimController == null) return;
-
- // Do not modify the notifications during collapse.
- if (isCollapsing()) {
- mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
- return;
- }
- mNotificationPanel.updateNotificationViews(reason);
- }
-
- @Override
public void onUserSwitched(int newUserId) {
// Begin old BaseStatusBar.userSwitched
mHeadsUpManager.setUser(newUserId);
@@ -294,11 +272,6 @@
}
@Override
- public void onUpdateRowStates() {
- mNotificationPanel.onUpdateRowStates();
- }
-
- @Override
public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
boolean nowExpanded) {
mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 262dc83..40b9a15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -37,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
@@ -66,7 +67,7 @@
private final ActivityStarter mActivityStarter;
private final Context mContext;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final ShadeController mShadeController;
+ private final com.android.systemui.shade.ShadeController mShadeController;
private Executor mExecutor;
private final ActivityIntentHelper mActivityIntentHelper;
private final GroupExpansionManager mGroupExpansionManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
index d57e6a7..b0532d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.dagger;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
+import com.android.systemui.statusbar.phone.LetterboxBackgroundProvider;
import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import java.util.Set;
@@ -40,4 +41,9 @@
@IntoSet
CentralSurfacesComponent.Startable sysBarAttrsListener(
SystemBarAttributesListener systemBarAttributesListener);
+
+ @Binds
+ @IntoSet
+ CentralSurfacesComponent.Startable letterboxBgProvider(
+ LetterboxBackgroundProvider letterboxBackgroundProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index f406825..f61b488 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -473,6 +473,10 @@
int state = mAnimationScheduler.getAnimationState();
if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
animateShow(mEndSideContent, animate);
+ } else {
+ // We are in the middle of a system status event animation, which will animate the
+ // alpha (but not the visibility). Allow the view to become visible again
+ mEndSideContent.setVisibility(View.VISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
new file mode 100644
index 0000000..6c02b0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.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
+
+import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Interface exposing a flow for raw connectivity information. Clients should collect on
+ * [rawConnectivityInfoFlow] to get updates on connectivity information.
+ *
+ * Note: [rawConnectivityInfoFlow] should be a *hot* flow, so that we only create one instance of it
+ * and all clients get references to the same flow.
+ *
+ * This will be used for the new status bar pipeline to compile information we need to display some
+ * of the icons in the RHS of the status bar.
+ */
+interface ConnectivityInfoCollector {
+ val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo>
+}
+
+/**
+ * An object containing all of the raw connectivity information.
+ *
+ * Importantly, all the information in this object should not be processed at all (i.e., the data
+ * that we receive from callbacks should be piped straight into this object and not be filtered,
+ * manipulated, or processed in any way). Instead, any listeners on
+ * [ConnectivityInfoCollector.rawConnectivityInfoFlow] can do the processing.
+ *
+ * This allows us to keep all the processing in one place which is beneficial for logging and
+ * debugging purposes.
+ */
+data class RawConnectivityInfo(
+ val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
new file mode 100644
index 0000000..8d69422
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.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.statusbar.pipeline
+
+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 javax.inject.Inject
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * The real implementation of [ConnectivityInfoCollector] that will collect information from all the
+ * relevant connectivity callbacks and compile it into [rawConnectivityInfoFlow].
+ */
+@SysUISingleton
+class ConnectivityInfoCollectorImpl @Inject constructor(
+ networkCapabilitiesRepo: NetworkCapabilitiesRepo,
+ @Application scope: CoroutineScope,
+) : ConnectivityInfoCollector {
+ override val rawConnectivityInfoFlow: StateFlow<RawConnectivityInfo> =
+ // TODO(b/238425913): Collect all the separate flows for individual raw information into
+ // this final flow.
+ networkCapabilitiesRepo.dataStream
+ .map {
+ RawConnectivityInfo(networkCapabilityInfo = it)
+ }
+ .stateIn(scope, started = Lazily, initialValue = RawConnectivityInfo())
+}
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 bd6cf9a..1aae250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -16,8 +16,18 @@
package com.android.systemui.statusbar.pipeline
+import android.content.Context
+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 javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* A processor that transforms raw connectivity information that we get from callbacks and turns it
@@ -25,6 +35,43 @@
*
* This will be used for the new status bar pipeline to calculate the list of icons that should be
* displayed in the RHS of the status bar.
+ *
+ * Anyone can listen to [processedInfoFlow] to get updates to the processed data.
*/
@SysUISingleton
-class ConnectivityInfoProcessor @Inject constructor()
+class ConnectivityInfoProcessor @Inject constructor(
+ connectivityInfoCollector: ConnectivityInfoCollector,
+ context: Context,
+ @Application private val scope: CoroutineScope,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+) : 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.
+ val processedInfoFlow: Flow<ProcessedConnectivityInfo> =
+ if (!statusBarPipelineFlags.isNewPipelineEnabled())
+ emptyFlow()
+ else connectivityInfoCollector.rawConnectivityInfoFlow
+ .map { it.process() }
+ .stateIn(
+ scope,
+ started = Lazily,
+ initialValue = ProcessedConnectivityInfo()
+ )
+
+ override fun start() {
+ }
+
+ private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo {
+ // TODO(b/238425913): Actually process the raw info into meaningful data.
+ return ProcessedConnectivityInfo(this.networkCapabilityInfo)
+ }
+}
+
+/**
+ * An object containing connectivity info that has been processed into data that can be directly
+ * used by the status bar (and potentially other SysUI areas) to display icons.
+ */
+data class ProcessedConnectivityInfo(
+ val networkCapabilityInfo: Map<Int, NetworkCapabilityInfo> = emptyMap(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
new file mode 100644
index 0000000..f88e9d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
@@ -0,0 +1,59 @@
+/*
+ * 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/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
new file mode 100644
index 0000000..589cdb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.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.statusbar.pipeline
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+/** All flagging methods related to the new status bar pipeline (see b/238425913). */
+@SysUISingleton
+class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+ /**
+ * Returns true if we should run the new pipeline.
+ *
+ * TODO(b/238425913): We may want to split this out into:
+ * (1) isNewPipelineLoggingEnabled(), where the new pipeline runs and logs its decisions but
+ * doesn't change the UI at all.
+ * (2) isNewPipelineEnabled(), where the new pipeline runs and does change the UI (and the old
+ * pipeline doesn't change the UI).
+ */
+ fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)
+}
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 734bd2d..c4e2b73 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
@@ -16,27 +16,25 @@
package com.android.systemui.statusbar.pipeline.dagger
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
-import dagger.Lazy
+import dagger.Binds
import dagger.Module
-import dagger.Provides
-import java.util.Optional
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
-class StatusBarPipelineModule {
- @Provides
- @SysUISingleton
- fun provideConnectivityInfoProcessor(
- featureFlags: FeatureFlags,
- processorLazy: Lazy<ConnectivityInfoProcessor>
- ): Optional<ConnectivityInfoProcessor> {
- return if (featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)) {
- Optional.of(processorLazy.get())
- } else {
- Optional.empty()
- }
- }
+abstract class StatusBarPipelineModule {
+ /** Inject into ConnectivityInfoProcessor. */
+ @Binds
+ @IntoMap
+ @ClassKey(ConnectivityInfoProcessor::class)
+ abstract fun bindConnectivityInfoProcessor(cip: ConnectivityInfoProcessor): CoreStartable
+
+ @Binds
+ abstract fun provideConnectivityInfoCollector(
+ impl: ConnectivityInfoCollectorImpl
+ ): ConnectivityInfoCollector
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
new file mode 100644
index 0000000..e5980c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.pipeline.repository
+
+import android.annotation.SuppressLint
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+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 javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository that contains all relevant [NetworkCapabilites] for the current networks */
+@SysUISingleton
+class NetworkCapabilitiesRepo @Inject constructor(
+ connectivityManager: ConnectivityManager,
+ @Application scope: CoroutineScope,
+ logger: ConnectivityPipelineLogger,
+) {
+ @SuppressLint("MissingPermission")
+ val dataStream: StateFlow<Map<Int, NetworkCapabilityInfo>> = run {
+ var state = emptyMap<Int, NetworkCapabilityInfo>()
+ callbackFlow {
+ val networkRequest: NetworkRequest =
+ NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build()
+ val callback =
+ // TODO (b/240569788): log these using [LogBuffer]
+ object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ state =
+ state.toMutableMap().also {
+ it[network.getNetId()] =
+ NetworkCapabilityInfo(network, networkCapabilities)
+ }
+ trySend(state)
+ }
+
+ override fun onLost(network: Network) {
+ logger.logOnLost(network)
+ state = state.toMutableMap().also { it.remove(network.getNetId()) }
+ trySend(state)
+ }
+ }
+ connectivityManager.registerNetworkCallback(networkRequest, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .stateIn(scope, started = Lazily, initialValue = state)
+ }
+}
+
+/** contains info about network capabilities. */
+data class NetworkCapabilityInfo(
+ val network: Network,
+ val capabilities: NetworkCapabilities,
+)
+
+private const val TAG = "ConnectivityRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 6264301..0fe10cb 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -47,6 +47,9 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -59,9 +62,6 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.ShadeControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryControllerImpl;
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index d6dfcea..8f127fd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -22,11 +22,12 @@
import android.os.PowerManager
import android.provider.Settings
import androidx.core.view.OneShotPreDrawListener
+import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
import com.android.systemui.util.settings.GlobalSettings
@@ -47,7 +48,8 @@
private val context: Context,
private val deviceStateManager: DeviceStateManager,
private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val globalSettings: GlobalSettings
+ private val globalSettings: GlobalSettings,
+ private val latencyTracker: LatencyTracker,
) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
private lateinit var mCentralSurfaces: CentralSurfaces
@@ -64,12 +66,14 @@
private var isAnimationPlaying = false
private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+ private val foldToAodLatencyTracker = FoldToAodLatencyTracker()
private val startAnimationRunnable = Runnable {
- mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation {
- // End action
- setAnimationState(playing = false)
- }
+ mCentralSurfaces.notificationPanelViewController.startFoldToAodAnimation(
+ /* startAction= */ { foldToAodLatencyTracker.onAnimationStarted() },
+ /* endAction= */ { setAnimationState(playing = false) },
+ /* cancelAction= */ { setAnimationState(playing = false) },
+ )
}
override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
@@ -82,11 +86,13 @@
/** Returns true if we should run fold to AOD animation */
override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
- override fun startAnimation(): Boolean =
- if (alwaysOnEnabled &&
+ private fun shouldStartAnimation(): Boolean =
+ alwaysOnEnabled &&
wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
- ) {
+
+ override fun startAnimation(): Boolean =
+ if (shouldStartAnimation()) {
setAnimationState(playing = true)
mCentralSurfaces.notificationPanelViewController.prepareFoldToAodAnimation()
true
@@ -97,6 +103,7 @@
override fun onStartedWakingUp() {
if (isAnimationPlaying) {
+ foldToAodLatencyTracker.cancel()
handler.removeCallbacks(startAnimationRunnable)
mCentralSurfaces.notificationPanelViewController.cancelFoldToAodAnimation()
}
@@ -137,7 +144,8 @@
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
OneShotPreDrawListener.add(
- mCentralSurfaces.notificationPanelViewController.view, onReady
+ mCentralSurfaces.notificationPanelViewController.view,
+ onReady
)
} else {
// No animation, call ready callback immediately
@@ -209,5 +217,41 @@
isFoldHandled = false
}
this.isFolded = isFolded
- })
+ if (isFolded) {
+ foldToAodLatencyTracker.onFolded()
+ }
+ }
+ )
+
+ /**
+ * Tracks the latency of fold to AOD using [LatencyTracker].
+ *
+ * Events that trigger start and end are:
+ *
+ * - Start: Once [DeviceStateManager] sends the folded signal [FoldToAodLatencyTracker.onFolded]
+ * is called and latency tracking starts.
+ * - End: Once the fold -> AOD animation starts, [FoldToAodLatencyTracker.onAnimationStarted] is
+ * called, and latency tracking stops.
+ */
+ private inner class FoldToAodLatencyTracker {
+
+ /** Triggers the latency logging, if needed. */
+ fun onFolded() {
+ if (shouldStartAnimation()) {
+ latencyTracker.onActionStart(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+ }
+ /**
+ * Called once the Fold -> AOD animation is started.
+ *
+ * For latency tracking, this determines the end of the fold to aod action.
+ */
+ fun onAnimationStarted() {
+ latencyTracker.onActionEnd(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+
+ fun cancel() {
+ latencyTracker.onActionCancel(LatencyTracker.ACTION_FOLD_TO_AOD)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 345fc99..4dc78f9 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -21,6 +21,7 @@
import android.app.Notification.Action;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -215,9 +216,11 @@
} else {
// Boo, annoy the user to reinsert the private volume
- final CharSequence title = mContext.getString(R.string.ext_media_missing_title,
+ final CharSequence title =
+ mContext.getString(R.string.ext_media_missing_title,
rec.getNickname());
- final CharSequence text = mContext.getString(R.string.ext_media_missing_message);
+ final CharSequence text =
+ mContext.getString(R.string.ext_media_missing_message);
Notification.Builder builder =
new Notification.Builder(mContext, NotificationChannels.STORAGE)
@@ -381,8 +384,8 @@
if (rec.isSnoozed() && disk.isAdoptable()) {
return null;
}
-
- if (disk.isAdoptable() && !rec.isInited()) {
+ if (disk.isAdoptable() && !rec.isInited() && rec.getType() != VolumeInfo.TYPE_PUBLIC
+ && rec.getType() != VolumeInfo.TYPE_PRIVATE) {
final CharSequence title = disk.getDescription();
final CharSequence text = mContext.getString(
R.string.ext_media_new_notification_message, disk.getDescription());
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index c94a915..13c3df3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -378,11 +378,13 @@
// The ringer and rows container has extra height at the top to fit the expanded ringer
// drawer. This area should not be touchable unless the ringer drawer is open.
+ // In landscape the ringer expands to the left and it has to be ensured that if there
+ // are multiple rows they are touchable.
if (view == mTopContainer && !mIsRingerDrawerOpen) {
if (!isLandscape()) {
y += getRingerDrawerOpenExtraSize();
- } else {
- x += getRingerDrawerOpenExtraSize();
+ } else if (getRingerDrawerOpenExtraSize() > getVisibleRowsExtraSize()) {
+ x += (getRingerDrawerOpenExtraSize() - getVisibleRowsExtraSize());
}
}
@@ -1968,6 +1970,21 @@
return (mRingerCount - 1) * mRingerDrawerItemSize;
}
+ /**
+ * Return the size of the additionally visible rows next to the default stream.
+ * An additional row is visible for example while receiving a voice call.
+ */
+ private int getVisibleRowsExtraSize() {
+ VolumeRow activeRow = getActiveRow();
+ int visibleRows = 0;
+ for (final VolumeRow row : mRows) {
+ if (shouldBeVisibleH(row, activeRow)) {
+ visibleRows++;
+ }
+ }
+ return (visibleRows - 1) * (mDialogWidth + mRingerRowsPadding);
+ }
+
private void updateBackgroundForDrawerClosedAmount() {
if (mRingerAndDrawerContainerBackground == null) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 708a8ab..e22a896 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -40,7 +40,6 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -54,6 +53,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -69,7 +69,6 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -83,6 +82,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -262,7 +262,7 @@
}
@Override
- public void getShouldRestoredEntries(ArraySet<String> savedBubbleKeys,
+ public void getShouldRestoredEntries(Set<String> savedBubbleKeys,
Consumer<List<BubbleEntry>> callback) {
sysuiMainExecutor.execute(() -> {
List<BubbleEntry> result = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 68e49c0..dc87a6a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -318,7 +318,7 @@
@Test
public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
- setSidedSecurityMode(false);
+ setSideFpsHintEnabledFromResources(false);
reset(mSidefpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -383,7 +383,7 @@
private void setupConditionsToEnableSideFpsHint() {
attachView();
- setSidedSecurityMode(true);
+ setSideFpsHintEnabledFromResources(true);
setFingerprintDetectionRunning(true);
setNeedsStrongAuth(false);
}
@@ -399,8 +399,9 @@
BiometricSourceType.FINGERPRINT);
}
- private void setSidedSecurityMode(boolean sided) {
- when(mView.isSidedSecurityMode()).thenReturn(sided);
+ private void setSideFpsHintEnabledFromResources(boolean enabled) {
+ when(mResources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)).thenReturn(
+ enabled);
}
private void setNeedsStrongAuth(boolean needed) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 84903d1..035404c 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.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -31,7 +32,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -41,6 +41,7 @@
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
@@ -52,6 +53,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -68,6 +70,7 @@
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IRemoteCallback;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.ServiceState;
@@ -86,6 +89,7 @@
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
+import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -179,6 +183,20 @@
private KeyguardUpdateMonitorCallback mTestCallback;
@Mock
private ActiveUnlockConfig mActiveUnlockConfig;
+ @Mock
+ private KeyguardUpdateMonitorLogger mKeyguardUpdateMonitorLogger;
+ @Mock
+ private IActivityManager mActivityService;
+
+ private final int mCurrentUserId = 100;
+ private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
+
+ @Captor
+ private ArgumentCaptor<IBiometricEnabledOnKeyguardCallback>
+ mBiometricEnabledCallbackArgCaptor;
+ @Captor
+ private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+
// Direct executor
private Executor mBackgroundExecutor = Runnable::run;
private Executor mMainExecutor = Runnable::run;
@@ -187,18 +205,16 @@
private TestableContext mSpiedContext;
private MockitoSession mMockitoSession;
private StatusBarStateController.StateListener mStatusBarStateListener;
+ private IBiometricEnabledOnKeyguardCallback mBiometricEnabledOnKeyguardCallback;
@Before
- public void setup() {
+ public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
mSpiedContext = spy(mContext);
when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
- doAnswer(invocation -> {
- IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0);
- callback.onChanged(true /* enabled */, KeyguardUpdateMonitor.getCurrentUser());
- return null;
- }).when(mBiometricManager).registerEnabledOnKeyguardCallback(any());
+ when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
+ when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
@@ -259,13 +275,20 @@
.startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(SubscriptionManager::getDefaultSubscriptionId);
+ KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
.when(ActivityManager::getCurrentUser);
+ ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+ verify(mBiometricManager)
+ .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
+ mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
+ biometricsEnabledForCurrentUser();
+
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
mKeyguardUpdateMonitor.registerCallback(mTestCallback);
@@ -715,7 +738,6 @@
verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt());
}
-
@Test
public void testFaceAndFingerprintLockout() {
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
@@ -921,6 +943,7 @@
@Test
public void testSecondaryLockscreenRequirement() {
+ KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
int user = KeyguardUpdateMonitor.getCurrentUser();
String packageName = "fake.test.package";
String cls = "FakeService";
@@ -1094,8 +1117,7 @@
@Test
public void testShouldNotUpdateBiometricListeningStateOnStatusBarStateChange() {
// GIVEN state for face auth should run aside from StatusBarState
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
- KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0);
+ biometricsNotDisabledThroughDevicePolicyManager();
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
setKeyguardBouncerVisibility(false /* isVisible */);
mKeyguardUpdateMonitor.dispatchStartedWakingUp();
@@ -1150,6 +1172,306 @@
verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable");
}
+ @Test
+ public void testShouldListenForFace_whenFaceManagerNotAvailable_returnsFalse() {
+ mFaceManager = null;
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenFpIsLockedOut_returnsFalse() throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ // Fingerprint is locked out.
+ fingerprintErrorLockedOut();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ bouncerFullyVisibleAndNotGoingToSleep();
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ triggerSuccessfulFaceAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
+ // This disables face auth
+ when(mUserManager.isPrimaryUser()).thenReturn(false);
+ mKeyguardUpdateMonitor =
+ new TestableKeyguardUpdateMonitor(mSpiedContext);
+
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenStrongAuthDoesNotAllowScanning_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ // This disables face auth
+ when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ mTestableLooper.processAllMessages();
+
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ // This disables face auth
+ biometricsDisabledForCurrentUser();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ userCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ secureCameraLaunched();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ bouncerFullyVisibleAndNotGoingToSleep();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ secureCameraLaunched();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+
+ occludingAppRequestsFaceAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+
+ bouncerFullyVisibleAndNotGoingToSleep();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
+ @Test
+ public void testShouldListenForFace_whenAuthInterruptIsActive_returnsTrue()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+
+ triggerAuthInterrupt();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+ }
+
+ private void triggerAuthInterrupt() {
+ mKeyguardUpdateMonitor.onAuthInterruptDetected(true);
+ }
+
+ private void occludingAppRequestsFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+ }
+
+ private void secureCameraLaunched() {
+ mKeyguardUpdateMonitor.onCameraLaunched();
+ }
+
+ private void userCurrentlySwitching() {
+ mKeyguardUpdateMonitor.setSwitchingUser(true);
+ }
+
+ private void fingerprintErrorLockedOut() {
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+ .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
+ }
+
+ private void triggerSuccessfulFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuth(true);
+ verify(mFaceManager).authenticate(any(),
+ any(),
+ mAuthenticationCallbackCaptor.capture(),
+ any(),
+ anyInt(),
+ anyBoolean());
+ mAuthenticationCallbackCaptor.getValue()
+ .onAuthenticationSucceeded(
+ new FaceManager.AuthenticationResult(null, null, mCurrentUserId, false));
+ }
+
+ private void currentUserIsPrimary() {
+ when(mUserManager.isPrimaryUser()).thenReturn(true);
+ }
+
+ private void biometricsNotDisabledThroughDevicePolicyManager() {
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null,
+ KeyguardUpdateMonitor.getCurrentUser())).thenReturn(0);
+ }
+
+ private void biometricsEnabledForCurrentUser() throws RemoteException {
+ mBiometricEnabledOnKeyguardCallback.onChanged(true, KeyguardUpdateMonitor.getCurrentUser());
+ }
+
+ private void biometricsDisabledForCurrentUser() throws RemoteException {
+ mBiometricEnabledOnKeyguardCallback.onChanged(
+ false,
+ KeyguardUpdateMonitor.getCurrentUser()
+ );
+ }
+
+ private void strongAuthNotRequired() {
+ when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(0);
+ }
+
+ private void currentUserDoesNotHaveTrust() {
+ mKeyguardUpdateMonitor.onTrustChanged(
+ false,
+ KeyguardUpdateMonitor.getCurrentUser(),
+ -1,
+ new ArrayList<>()
+ );
+ }
+
+ private void userNotCurrentlySwitching() {
+ mKeyguardUpdateMonitor.setSwitchingUser(false);
+ }
+
+ private void keyguardNotGoingAway() {
+ mKeyguardUpdateMonitor.setKeyguardGoingAway(false);
+ }
+
+ private void bouncerFullyVisibleAndNotGoingToSleep() {
+ mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true, true);
+ mKeyguardUpdateMonitor.dispatchFinishedGoingToSleep(/* value doesn't matter */1);
+ }
+
private void setKeyguardBouncerVisibility(boolean isVisible) {
mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible, isVisible);
mTestableLooper.processAllMessages();
@@ -1189,7 +1511,8 @@
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
- mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig);
+ mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
+ mKeyguardUpdateMonitorLogger);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 6157ccb..8d969d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -45,6 +45,8 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
@@ -438,6 +440,44 @@
assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
}
+
+ @Test
+ fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+ ) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(
+ windowManager.defaultDisplay,
+ indicatorBounds
+ )
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+ }
+
+ @Test
+ fun testLayoutParams_hasTrustedOverlayWindowFlag() = testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
+ ) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(
+ windowManager.defaultDisplay,
+ indicatorBounds
+ )
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+ }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 6978490..3ac28c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -35,12 +35,12 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
-import org.mockito.Mockito.reset
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -63,6 +63,7 @@
controller = WiredChargingRippleController(
commandRegistry, batteryController, configurationController,
featureFlags, context, windowManager, systemClock, uiEventLogger)
+ rippleView.setupShader()
controller.rippleView = rippleView // Replace the real ripple view with a mock instance
controller.registerCallbacks()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
index 365c529..2915f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -19,6 +19,7 @@
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType;
@@ -57,6 +58,8 @@
.isEqualTo(COMPLICATION_TYPE_AIR_QUALITY);
assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO))
.isEqualTo(COMPLICATION_TYPE_CAST_INFO);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS))
+ .isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
deleted file mode 100644
index 86aa14d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java
+++ /dev/null
@@ -1,128 +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.dreams.complication;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import javax.inject.Provider;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DreamClockDateComplicationTest extends SysuiTestCase {
- @SuppressWarnings("HidingField")
- @Mock
- private Context mContext;
-
- @Mock
- private DreamOverlayStateController mDreamOverlayStateController;
-
- @Mock
- private DreamClockDateComplication mComplication;
-
- @Mock
- private Provider<DreamClockDateComplication.DreamClockDateViewHolder>
- mDreamClockDateViewHolderProvider;
-
- @Mock
- private DreamClockDateComplication.DreamClockDateViewHolder
- mDreamClockDateViewHolder;
-
- @Mock
- private ComplicationViewModel mComplicationViewModel;
-
- @Mock
- private View mView;
-
- @Mock
- private ComplicationLayoutParams mLayoutParams;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- when(mDreamClockDateViewHolderProvider.get()).thenReturn(mDreamClockDateViewHolder);
-
- }
-
- /**
- * Ensures {@link DreamClockDateComplication} is registered.
- */
- @Test
- public void testComplicationAdded() {
- final DreamClockDateComplication.Registrant registrant =
- new DreamClockDateComplication.Registrant(
- mContext,
- mDreamOverlayStateController,
- mComplication);
- registrant.start();
- verify(mDreamOverlayStateController).addComplication(eq(mComplication));
- }
-
- /**
- * Verifies {@link DreamClockDateComplication} has the required type.
- */
- @Test
- public void testComplicationRequiredTypeAvailability() {
- final DreamClockDateComplication complication =
- new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
- assertEquals(Complication.COMPLICATION_TYPE_DATE,
- complication.getRequiredTypeAvailability());
- }
-
- /**
- * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} is obtainable from its
- * provider when the complication creates view.
- */
- @Test
- public void testComplicationViewHolderProviderOnCreateView() {
- final DreamClockDateComplication complication =
- new DreamClockDateComplication(mDreamClockDateViewHolderProvider);
- final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
- verify(mDreamClockDateViewHolderProvider).get();
- assertThat(viewHolder).isEqualTo(mDreamClockDateViewHolder);
- }
-
- /**
- * Verifies {@link DreamClockDateComplication.DreamClockDateViewHolder} has the intended view
- * and layout parameters from constructor.
- */
- @Test
- public void testComplicationViewHolderContentAccessors() {
- final DreamClockDateComplication.DreamClockDateViewHolder viewHolder =
- new DreamClockDateComplication.DreamClockDateViewHolder(mView, mLayoutParams);
- assertThat(viewHolder.getView()).isEqualTo(mView);
- assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
new file mode 100644
index 0000000..04ff7ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.dreams.complication;
+
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.controls.ControlsServiceInfo;
+import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.controller.StructureInfo;
+import com.android.systemui.controls.dagger.ControlsComponent;
+import com.android.systemui.controls.management.ControlsListingController;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
+
+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;
+
+import java.util.List;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamHomeControlsComplicationTest extends SysuiTestCase {
+ @Mock
+ private DreamHomeControlsComplication mComplication;
+
+ @Mock
+ private DreamOverlayStateController mDreamOverlayStateController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ControlsComponent mControlsComponent;
+
+ @Mock
+ private ControlsController mControlsController;
+
+ @Mock
+ private ControlsListingController mControlsListingController;
+
+ @Mock
+ private DreamHomeControlsComplicationComponent.Factory mComponentFactory;
+
+ @Captor
+ private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getString(anyInt())).thenReturn("");
+ when(mControlsComponent.getControlsController()).thenReturn(
+ Optional.of(mControlsController));
+ when(mControlsComponent.getControlsListingController()).thenReturn(
+ Optional.of(mControlsListingController));
+ when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
+ }
+
+ @Test
+ public void complicationType() {
+ final DreamHomeControlsComplication complication =
+ new DreamHomeControlsComplication(mComponentFactory);
+ assertThat(complication.getRequiredTypeAvailability()).isEqualTo(
+ COMPLICATION_TYPE_HOME_CONTROLS);
+ }
+
+ @Test
+ public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(false);
+ setServiceAvailable(false);
+
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+ }
+
+ @Test
+ public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(false);
+ setServiceAvailable(true);
+
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+ }
+
+ @Test
+ public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(true);
+ setServiceAvailable(false);
+
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+ }
+
+ @Test
+ public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mContext, mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(true);
+ setServiceAvailable(true);
+
+ verify(mDreamOverlayStateController).addComplication(mComplication);
+ }
+
+ private void setHaveFavorites(boolean value) {
+ final List<StructureInfo> favorites = mock(List.class);
+ when(favorites.isEmpty()).thenReturn(!value);
+ when(mControlsController.getFavorites()).thenReturn(favorites);
+ }
+
+ private void setServiceAvailable(boolean value) {
+ final List<ControlsServiceInfo> serviceInfos = mock(List.class);
+ when(serviceInfos.isEmpty()).thenReturn(!value);
+ triggerControlsListingCallback(serviceInfos);
+ }
+
+ private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) {
+ verify(mControlsListingController).addCallback(mCallbackCaptor.capture());
+ mCallbackCaptor.getValue().onServicesUpdated(serviceInfos);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index 964e6d7..7d54758 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams;
+package com.android.systemui.dreams.complication;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,8 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
@@ -183,9 +183,9 @@
@Test
public void testGetView_reusesSameView() {
- final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
- mSmartspaceController);
- final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
+ final Complication.ViewHolder viewHolder =
+ new SmartSpaceComplication.SmartSpaceComplicationViewHolder(getContext(),
+ mSmartspaceController, mock(ComplicationLayoutParams.class));
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mBcSmartspaceView);
assertEquals(viewHolder.getView(), viewHolder.getView());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
new file mode 100644
index 0000000..b2a4e33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.flags
+
+import android.util.SparseArray
+import android.util.SparseBooleanArray
+import androidx.core.util.containsKey
+import java.lang.IllegalStateException
+
+class FakeFeatureFlags : FeatureFlags {
+ private val booleanFlags = SparseBooleanArray()
+ private val stringFlags = SparseArray<String>()
+ private val knownFlagNames = mutableMapOf<Int, String>()
+
+ init {
+ Flags.getFlagFields().forEach { field ->
+ val flag: Flag<*> = field.get(null) as Flag<*>
+ knownFlagNames[flag.id] = field.name
+ }
+ }
+
+ fun set(flag: BooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: ResourceBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: SysPropBooleanFlag, value: Boolean) {
+ booleanFlags.put(flag.id, value)
+ }
+
+ fun set(flag: StringFlag, value: String) {
+ stringFlags.put(flag.id, value)
+ }
+
+ fun set(flag: ResourceStringFlag, value: String) {
+ stringFlags.put(flag.id, value)
+ }
+
+ override fun isEnabled(flag: BooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
+
+ override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
+
+ override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
+
+ override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {}
+
+ override fun removeListener(listener: FlagListenable.Listener) {}
+
+ private fun flagName(flagId: Int): String {
+ return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
+ }
+
+ private fun requireBooleanValue(flagId: Int): Boolean {
+ if (!booleanFlags.containsKey(flagId)) {
+ throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+ }
+ return booleanFlags[flagId]
+ }
+
+ private fun requireStringValue(flagId: Int): String {
+ if (!stringFlags.containsKey(flagId)) {
+ throw IllegalStateException("Flag ${flagName(flagId)} was accessed but not specified.")
+ }
+ return stringFlags[flagId]
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
new file mode 100644
index 0000000..7d4b4f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.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.flags
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.lang.IllegalStateException
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FakeFeatureFlagsTest : SysuiTestCase() {
+
+ private val booleanFlag = BooleanFlag(-1000)
+ private val stringFlag = StringFlag(-1001)
+ private val resourceBooleanFlag = ResourceBooleanFlag(-1002, resourceId = -1)
+ private val resourceStringFlag = ResourceStringFlag(-1003, resourceId = -1)
+ private val sysPropBooleanFlag = SysPropBooleanFlag(-1004, name = "test")
+
+ /**
+ * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
+ * specified. If not, an exception is thrown.
+ */
+ @Test
+ fun throwsIfUnspecifiedFlagIsAccessed() {
+ val flags: FeatureFlags = FakeFeatureFlags()
+ try {
+ assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("TEAMFOOD")
+ }
+ try {
+ assertThat(flags.isEnabled(booleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1000)")
+ }
+ try {
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1002)")
+ }
+ try {
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1004)")
+ }
+ try {
+ assertThat(flags.getString(stringFlag)).isEmpty()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1001)")
+ }
+ try {
+ assertThat(flags.getString(resourceStringFlag)).isEmpty()
+ fail("Expected an exception when accessing an unspecified flag.")
+ } catch (ex: IllegalStateException) {
+ assertThat(ex.message).contains("UNKNOWN(id=-1003)")
+ }
+ }
+
+ @Test
+ fun specifiedFlagsReturnCorrectValues() {
+ val flags = FakeFeatureFlags()
+ flags.set(booleanFlag, false)
+ flags.set(resourceBooleanFlag, false)
+ flags.set(sysPropBooleanFlag, false)
+ flags.set(resourceStringFlag, "")
+
+ assertThat(flags.isEnabled(booleanFlag)).isFalse()
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
+ assertThat(flags.getString(resourceStringFlag)).isEmpty()
+
+ flags.set(booleanFlag, true)
+ flags.set(resourceBooleanFlag, true)
+ flags.set(sysPropBooleanFlag, true)
+ flags.set(resourceStringFlag, "Android")
+
+ assertThat(flags.isEnabled(booleanFlag)).isTrue()
+ assertThat(flags.isEnabled(resourceBooleanFlag)).isTrue()
+ assertThat(flags.isEnabled(sysPropBooleanFlag)).isTrue()
+ assertThat(flags.getString(resourceStringFlag)).isEqualTo("Android")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9b66555..21c018a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -186,6 +186,31 @@
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway_initiallyNotShowing() {
+ // When showing and provisioned
+ mViewMediator.onSystemReady();
+ when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
+ mViewMediator.setShowingLocked(false);
+
+ // and a SIM becomes locked and requires a PIN
+ mViewMediator.mUpdateCallback.onSimStateChanged(
+ 1 /* subId */,
+ 0 /* slotId */,
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+
+ // and the keyguard goes away
+ mViewMediator.setShowingLocked(false);
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
+ mViewMediator.mUpdateCallback.onKeyguardVisibilityChanged(false);
+
+ TestableLooper.get(this).processAllMessages();
+
+ // then make sure it comes back
+ verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null);
+ }
+
+ @Test
public void testBouncerPrompt_deviceLockedByAdmin() {
// GIVEN no trust agents enabled and biometrics aren't enrolled
when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..592e80b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var component: ControlsComponent
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ HomeControlsKeyguardQuickAffordanceConfig(
+ context = context,
+ component = component,
+ )
+ }
+
+ @Test
+ fun `state - when listing controller is missing - returns None`() = runBlockingTest {
+ whenever(component.isEnabled()).thenReturn(true)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ val controlsController = mock<ControlsController>()
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController()).thenReturn(Optional.empty())
+ whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
+
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ assertThat(values.last())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ job.cancel()
+ }
+
+ @Test
+ fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
+
+ val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+ assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+ }
+
+ @Test
+ fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+ whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
+
+ val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
+
+ assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..6fd04de
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var controller: QRCodeScannerController
+
+ private lateinit var underTest: QrCodeScannerKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(controller.intent).thenReturn(INTENT_1)
+
+ underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+ }
+
+ @Test
+ fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+ verify(controller)
+ .registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner activity changed - delivers model with updated intent`() =
+ runBlockingTest {
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.intent).thenReturn(INTENT_2)
+ callbackCaptor.value.onQRCodeScannerActivityChanged()
+
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
+ callbackCaptor.value.onQRCodeScannerPreferenceChanged()
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() {
+ assertThat(underTest.onQuickAffordanceClicked(mock()))
+ .isEqualTo(
+ OnClickedResult.StartActivity(
+ intent = INTENT_1,
+ canShowWhileLocked = true,
+ )
+ )
+ }
+
+ private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
+ val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+ assertThat(visibleState.icon).isNotNull()
+ assertThat(visibleState.contentDescriptionResourceId).isNotNull()
+ }
+
+ companion object {
+ private val INTENT_1 = Intent("intent1")
+ private val INTENT_2 = Intent("intent2")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..345c51f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var walletController: QuickAccessWalletController
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ QuickAccessWalletKeyguardQuickAffordanceConfig(
+ walletController,
+ activityStarter,
+ )
+ }
+
+ @Test
+ fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
+ setUpState()
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+ assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
+ assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
+ setUpState(isWalletEnabled = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - query not successful - model is none`() = runBlockingTest {
+ setUpState(isWalletQuerySuccessful = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - missing icon - model is none`() = runBlockingTest {
+ setUpState(hasWalletIcon = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `affordance - no selected card - model is none`() = runBlockingTest {
+ setUpState(hasWalletIcon = false)
+ var latest: KeyguardQuickAffordanceConfig.State? = null
+
+ val job = underTest.state.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+
+ job.cancel()
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() {
+ val animationController: ActivityLaunchAnimator.Controller = mock()
+
+ assertThat(underTest.onQuickAffordanceClicked(animationController))
+ .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+ verify(walletController)
+ .startQuickAccessUiIntent(
+ activityStarter,
+ animationController,
+ /* hasCard= */ true,
+ )
+ }
+
+ private fun setUpState(
+ isWalletEnabled: Boolean = true,
+ isWalletQuerySuccessful: Boolean = true,
+ hasWalletIcon: Boolean = true,
+ hasSelectedCard: Boolean = true,
+ ) {
+ whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
+
+ val walletClient: QuickAccessWalletClient = mock()
+ val icon: Drawable? =
+ if (hasWalletIcon) {
+ ICON
+ } else {
+ null
+ }
+ whenever(walletClient.tileIcon).thenReturn(icon)
+ whenever(walletController.walletClient).thenReturn(walletClient)
+
+ whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+ with(
+ invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+ ) {
+ if (isWalletQuerySuccessful) {
+ onWalletCardsRetrieved(
+ if (hasSelectedCard) {
+ GetWalletCardsResponse(listOf(mock()), 0)
+ } else {
+ GetWalletCardsResponse(emptyList(), 0)
+ }
+ )
+ } else {
+ onWalletCardRetrievalError(mock())
+ }
+ }
+ }
+ }
+
+ companion object {
+ private val ICON: Drawable = mock()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 0000000..6fff440
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfig.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.systemui.keyguard.data.repository
+
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/**
+ * Fake implementation of a quick affordance data source.
+ *
+ * This class is abstract to force tests to provide extensions of it as the system that references
+ * these configs uses each implementation's class type to refer to them.
+ */
+abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+
+ private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
+ val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
+
+ var onClickedResult: OnClickedResult = OnClickedResult.Handled
+
+ private val _state =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
+ KeyguardQuickAffordanceConfig.State.Hidden
+ )
+ override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+
+ override fun onQuickAffordanceClicked(
+ animationController: ActivityLaunchAnimator.Controller?,
+ ): OnClickedResult {
+ _onClickedInvocations.add(animationController)
+ return onClickedResult
+ }
+
+ suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
+ _state.value = state
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
new file mode 100644
index 0000000..a24fc93
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceConfigs.kt
@@ -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.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlin.reflect.KClass
+
+/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
+class FakeKeyguardQuickAffordanceConfigs(
+ private val configsByPosition:
+ Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceConfigs {
+
+ override fun getAll(
+ position: KeyguardQuickAffordancePosition
+ ): List<KeyguardQuickAffordanceConfig> {
+ return configsByPosition.getValue(position)
+ }
+
+ override fun get(
+ configClass: KClass<out KeyguardQuickAffordanceConfig>
+ ): KeyguardQuickAffordanceConfig {
+ return configsByPosition.values
+ .flatten()
+ .associateBy { config -> config::class }
+ .getValue(configClass)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..10d2e4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.yield
+
+/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
+class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
+
+ private val modelByPosition =
+ mutableMapOf<
+ KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
+
+ init {
+ KeyguardQuickAffordancePosition.values().forEach { value ->
+ modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
+ }
+ }
+
+ override fun affordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return modelByPosition.getValue(position)
+ }
+
+ suspend fun setModel(
+ position: KeyguardQuickAffordancePosition,
+ model: KeyguardQuickAffordanceModel
+ ) {
+ modelByPosition.getValue(position).value = model
+ // Yield to allow the test's collection coroutine to "catch up" and collect this value
+ // before the test continues to the next line.
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
+ yield()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
new file mode 100644
index 0000000..d40b985
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.common.data.model.Position
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardRepository : KeyguardRepository {
+
+ private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
+ override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
+ _animateBottomAreaDozingTransitions
+
+ private val _bottomAreaAlpha = MutableStateFlow(1f)
+ override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
+
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ override val clockPosition: StateFlow<Position> = _clockPosition
+
+ private val _isKeyguardShowing = MutableStateFlow(false)
+ override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
+
+ private val _isDozing = MutableStateFlow(false)
+ override val isDozing: Flow<Boolean> = _isDozing
+
+ private val _dozeAmount = MutableStateFlow(0f)
+ override val dozeAmount: Flow<Float> = _dozeAmount
+
+ init {
+ setDozeAmount(0f)
+ setDozing(false)
+ }
+
+ override fun setAnimateDozingTransitions(animate: Boolean) {
+ _animateBottomAreaDozingTransitions.tryEmit(animate)
+ }
+
+ override fun setBottomAreaAlpha(alpha: Float) {
+ _bottomAreaAlpha.value = alpha
+ }
+
+ override fun setClockPosition(x: Int, y: Int) {
+ _clockPosition.value = Position(x, y)
+ }
+
+ fun setKeyguardShowing(isShowing: Boolean) {
+ _isKeyguardShowing.value = isShowing
+ }
+
+ fun setDozing(isDozing: Boolean) {
+ _isDozing.value = isDozing
+ }
+
+ fun setDozeAmount(dozeAmount: Float) {
+ _dozeAmount.value = dozeAmount
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
new file mode 100644
index 0000000..bcc76ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() {
+
+ companion object {
+ @Parameters(
+ name =
+ "feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" +
+ " visible = {3}"
+ )
+ @JvmStatic
+ fun data() =
+ (0 until 8)
+ .map { combination ->
+ arrayOf(
+ /* isFeatureEnabled= */ combination and 0b100 != 0,
+ /* hasFavorites= */ combination and 0b010 != 0,
+ /* hasServiceInfos= */ combination and 0b001 != 0,
+ /* isVisible= */ combination == 0b111,
+ )
+ }
+ .toList()
+ }
+
+ @Mock private lateinit var component: ControlsComponent
+ @Mock private lateinit var controlsController: ControlsController
+ @Mock private lateinit var controlsListingController: ControlsListingController
+ @Captor
+ private lateinit var callbackCaptor:
+ ArgumentCaptor<ControlsListingController.ControlsListingCallback>
+
+ private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
+
+ @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
+ @JvmField @Parameter(1) var hasFavorites: Boolean = false
+ @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
+ @JvmField @Parameter(3) var isVisible: Boolean = false
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
+ whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
+ whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
+ whenever(component.getControlsListingController())
+ .thenReturn(Optional.of(controlsListingController))
+
+ underTest =
+ HomeControlsKeyguardQuickAffordanceConfig(
+ context = context,
+ component = component,
+ )
+ }
+
+ @Test
+ fun state() = runBlockingTest {
+ whenever(component.isEnabled()).thenReturn(isFeatureEnabled)
+ whenever(controlsController.getFavorites())
+ .thenReturn(
+ if (hasFavorites) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
+ val job = underTest.state.onEach(values::add).launchIn(this)
+
+ verify(controlsListingController).addCallback(callbackCaptor.capture())
+ callbackCaptor.value.onServicesUpdated(
+ if (hasServiceInfos) {
+ listOf(mock())
+ } else {
+ emptyList()
+ }
+ )
+
+ assertThat(values.last())
+ .isInstanceOf(
+ if (isVisible) {
+ KeyguardQuickAffordanceConfig.State.Visible::class.java
+ } else {
+ KeyguardQuickAffordanceConfig.State.Hidden::class.java
+ }
+ )
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
new file mode 100644
index 0000000..dc0e6f7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryImplTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
+ private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+
+ underTest =
+ KeyguardQuickAffordanceRepositoryImpl(
+ configs =
+ FakeKeyguardQuickAffordanceConfigs(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWallet,
+ qrCodeScanner,
+ ),
+ ),
+ ),
+ )
+ }
+
+ @Test
+ fun `bottom start affordance - none`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom start affordance - home controls`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ homeControls.setState(state)
+
+ assertThat(latest).isEqualTo(state.toModel(homeControls::class))
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - none`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - quick access wallet`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val quickAccessWalletState =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAccessWallet.setState(quickAccessWalletState)
+ val qrCodeScannerState =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ qrCodeScanner.setState(qrCodeScannerState)
+
+ assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
+ job.cancel()
+ }
+
+ @Test
+ fun `bottom end affordance - qr code scanner`() = runBlockingTest {
+ // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
+ // https://developer.android.com/kotlin/flow/test#continuous-collection
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest
+ .affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = mock(),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ qrCodeScanner.setState(state)
+
+ assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
+ job.cancel()
+ }
+
+ private fun KeyguardQuickAffordanceConfig.State?.toModel(
+ configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ ): KeyguardQuickAffordanceModel? {
+ return when (this) {
+ is KeyguardQuickAffordanceConfig.State.Visible ->
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = icon,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
+ null -> null
+ }
+ }
+
+ companion object {
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
new file mode 100644
index 0000000..3d2c51a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.model.Position
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardRepositoryImplTest : SysuiTestCase() {
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ private lateinit var underTest: KeyguardRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = KeyguardRepositoryImpl(statusBarStateController, keyguardStateController)
+ }
+
+ @Test
+ fun animateBottomAreaDozingTransitions() = runBlockingTest {
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+
+ underTest.setAnimateDozingTransitions(false)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+ }
+
+ @Test
+ fun bottomAreaAlpha() = runBlockingTest {
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+
+ underTest.setBottomAreaAlpha(0.1f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+
+ underTest.setBottomAreaAlpha(0.2f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+
+ underTest.setBottomAreaAlpha(0.3f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+
+ underTest.setBottomAreaAlpha(0.5f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+
+ underTest.setBottomAreaAlpha(1.0f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+ }
+
+ @Test
+ fun clockPosition() = runBlockingTest {
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+
+ underTest.setClockPosition(0, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+
+ underTest.setClockPosition(1, 9)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+
+ underTest.setClockPosition(1, 0)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+
+ underTest.setClockPosition(3, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+ }
+
+ @Test
+ fun isKeyguardShowing() = runBlockingTest {
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDozing() = runBlockingTest {
+ var latest: Boolean? = null
+ val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
+
+ captor.value.onDozingChanged(true)
+ assertThat(latest).isTrue()
+
+ captor.value.onDozingChanged(false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
+
+ @Test
+ fun dozeAmount() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
+
+ captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ captor.value.onDozeAmountChanged(0.661f, 0.65f)
+
+ assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
new file mode 100644
index 0000000..ba0c31f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import android.content.Intent
+import com.android.systemui.animation.ActivityLaunchAnimator
+
+/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
+class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
+
+ data class Invocation(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ val animationController: ActivityLaunchAnimator.Controller?
+ )
+
+ private val _invocations = mutableListOf<Invocation>()
+ val invocations: List<Invocation> = _invocations
+
+ override fun invoke(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?
+ ) {
+ _invocations.add(
+ Invocation(
+ intent = intent,
+ canShowWhileLocked = canShowWhileLocked,
+ animationController = animationController,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
new file mode 100644
index 0000000..b3c1ae0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.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.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ ),
+ )
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ LaunchKeyguardQuickAffordanceUseCaseImpl(
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ )
+ }
+
+ @Test
+ fun invoke() {
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ underTest(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ animationController = animationController,
+ )
+
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ INTENT,
+ /* delay= */ 0,
+ animationController,
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ INTENT,
+ /* dismissShade= */ true,
+ animationController,
+ /* showOverLockscreenWhenLocked= */ true,
+ )
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
new file mode 100644
index 0000000..b90400be
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.usecase
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ObserveKeyguardQuickAffordanceUseCaseTest : SysuiTestCase() {
+
+ private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+
+ private lateinit var repository: FakeKeyguardRepository
+ private lateinit var quickAffordanceRepository: FakeKeyguardQuickAffordanceRepository
+ private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+ private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
+
+ @Before
+ fun setUp() {
+ repository = FakeKeyguardRepository()
+ repository.setKeyguardShowing(true)
+ isDozingUseCase = ObserveIsDozingUseCase(repository)
+ isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
+ quickAffordanceRepository = FakeKeyguardQuickAffordanceRepository()
+
+ underTest =
+ ObserveKeyguardQuickAffordanceUseCase(
+ repository = quickAffordanceRepository,
+ isDozingUseCase = isDozingUseCase,
+ isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+ )
+ }
+
+ @Test
+ fun `invoke - affordance is visible`() = runBlockingTest {
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.contentDescriptionResourceId)
+ .isEqualTo(CONTENT_DESCRIPTION_RESOURCE_ID)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance not visible while dozing`() = runBlockingTest {
+ repository.setDozing(true)
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance not visible when lockscreen is not showing`() = runBlockingTest {
+ repository.setKeyguardShowing(false)
+ val configKey = HomeControlsKeyguardQuickAffordanceConfig::class
+ val model =
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configKey,
+ icon = ICON,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_END,
+ model,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `invoke - affordance is none`() = runBlockingTest {
+ quickAffordanceRepository.setModel(
+ KeyguardQuickAffordancePosition.BOTTOM_START,
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+
+ var latest: KeyguardQuickAffordanceModel? = null
+ val job =
+ underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { latest = it }
+ .launchIn(this)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ job.cancel()
+ }
+
+ companion object {
+ private val ICON: ContainedDrawable = mock()
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
new file mode 100644
index 0000000..00dd58e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceConfigs
+import com.android.systemui.keyguard.data.repository.FakeKeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveIsKeyguardShowingUseCase
+import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
+import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
+
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+ @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+
+ private lateinit var underTest: KeyguardBottomAreaViewModel
+
+ private lateinit var affordanceRepository: FakeKeyguardQuickAffordanceRepository
+ private lateinit var repository: FakeKeyguardRepository
+ private lateinit var isDozingUseCase: ObserveIsDozingUseCase
+ private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
+ private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
+ private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+ private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+ private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+ .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+ affordanceRepository = FakeKeyguardQuickAffordanceRepository()
+ repository = FakeKeyguardRepository()
+ isDozingUseCase =
+ ObserveIsDozingUseCase(
+ repository = repository,
+ )
+ isKeyguardShowingUseCase =
+ ObserveIsKeyguardShowingUseCase(
+ repository = repository,
+ )
+ launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
+ homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+
+ underTest =
+ KeyguardBottomAreaViewModel(
+ observeQuickAffordanceUseCase =
+ ObserveKeyguardQuickAffordanceUseCase(
+ repository = affordanceRepository,
+ isDozingUseCase = isDozingUseCase,
+ isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+ ),
+ onQuickAffordanceClickedUseCase =
+ OnKeyguardQuickAffordanceClickedUseCase(
+ configs =
+ FakeKeyguardQuickAffordanceConfigs(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControlsQuickAffordanceConfig,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ quickAccessWalletAffordanceConfig,
+ qrCodeScannerAffordanceConfig,
+ ),
+ ),
+ ),
+ launchAffordanceUseCase = launchQuickAffordanceUseCase,
+ ),
+ observeBottomAreaAlphaUseCase =
+ ObserveBottomAreaAlphaUseCase(
+ repository = repository,
+ ),
+ observeIsDozingUseCase = isDozingUseCase,
+ observeAnimateBottomAreaTransitionsUseCase =
+ ObserveAnimateBottomAreaTransitionsUseCase(
+ repository = repository,
+ ),
+ observeDozeAmountUseCase =
+ ObserveDozeAmountUseCase(
+ repository = repository,
+ ),
+ observeClockPositionUseCase =
+ ObserveClockPositionUseCase(
+ repository = repository,
+ ),
+ burnInHelperWrapper = burnInHelperWrapper,
+ )
+ }
+
+ @Test
+ fun `startButton - present - not dozing - lockscreen showing - visible model - starts activity on click`() = // ktlint-disable max-line-length
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(true)
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = testConfig,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `endButton - present - not dozing - lockscreen showing - visible model - do nothing on click`() = // ktlint-disable max-line-length
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.endButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(true)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent =
+ null, // This will cause it to tell the system that the click was handled.
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - not present - not dozing - lockscreen showing - model is none`() =
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(true)
+ val config =
+ TestConfig(
+ isVisible = false,
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = config,
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - present - dozing - lockscreen showing - model is none`() = runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(true)
+ repository.setKeyguardShowing(true)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = TestConfig(isVisible = false),
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `startButton - present - not dozing - lockscreen not showing - model is none`() =
+ runBlockingTest {
+ var latest: KeyguardQuickAffordanceViewModel? = null
+ val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+
+ repository.setDozing(false)
+ repository.setKeyguardShowing(false)
+ val config =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest,
+ testConfig = TestConfig(isVisible = false),
+ configKey = configKey,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun animateButtonReveal() = runBlockingTest {
+ val values = mutableListOf<Boolean>()
+ val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+
+ repository.setAnimateDozingTransitions(true)
+ repository.setAnimateDozingTransitions(false)
+
+ assertThat(values).isEqualTo(listOf(false, true, false))
+ job.cancel()
+ }
+
+ @Test
+ fun isOverlayContainerVisible() = runBlockingTest {
+ val values = mutableListOf<Boolean>()
+ val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
+
+ repository.setDozing(true)
+ repository.setDozing(false)
+
+ assertThat(values).isEqualTo(listOf(true, false, true))
+ job.cancel()
+ }
+
+ @Test
+ fun alpha() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.alpha.onEach(values::add).launchIn(this)
+
+ repository.setBottomAreaAlpha(0.1f)
+ repository.setBottomAreaAlpha(0.5f)
+ repository.setBottomAreaAlpha(0.2f)
+ repository.setBottomAreaAlpha(0f)
+
+ assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
+ job.cancel()
+ }
+
+ @Test
+ fun isIndicationAreaPadded() = runBlockingTest {
+ repository.setKeyguardShowing(true)
+ val values = mutableListOf<Boolean>()
+ val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
+
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = true,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initially, no button is visible so the indication area is not padded.
+ false,
+ // Once we add the first visible button, the indication area becomes padded.
+ // This
+ // continues to be true after we add the second visible button and even after we
+ // make the first button not visible anymore.
+ true,
+ // Once both buttons are not visible, the indication area is, again, not padded.
+ false,
+ )
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun indicationAreaTranslationX() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
+
+ repository.setClockPosition(100, 100)
+ repository.setClockPosition(200, 100)
+ repository.setClockPosition(200, 200)
+ repository.setClockPosition(300, 100)
+
+ assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
+ job.cancel()
+ }
+
+ @Test
+ fun indicationAreaTranslationY() = runBlockingTest {
+ val values = mutableListOf<Float>()
+ val job =
+ underTest
+ .indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
+ .onEach(values::add)
+ .launchIn(this)
+
+ val expectedTranslationValues =
+ listOf(
+ -0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
+ // FML
+ setDozeAmountAndCalculateExpectedTranslationY(0.1f),
+ setDozeAmountAndCalculateExpectedTranslationY(0.2f),
+ setDozeAmountAndCalculateExpectedTranslationY(0.5f),
+ setDozeAmountAndCalculateExpectedTranslationY(1f),
+ )
+
+ assertThat(values).isEqualTo(expectedTranslationValues)
+ job.cancel()
+ }
+
+ private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ repository.setDozeAmount(dozeAmount)
+ return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+ }
+
+ private suspend fun setUpQuickAffordanceModel(
+ position: KeyguardQuickAffordancePosition,
+ testConfig: TestConfig,
+ ): KClass<*> {
+ val config =
+ when (position) {
+ KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
+ KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
+ }
+
+ affordanceRepository.setModel(
+ position = position,
+ model =
+ if (testConfig.isVisible) {
+ if (testConfig.intent != null) {
+ config.onClickedResult =
+ KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = testConfig.intent,
+ canShowWhileLocked = testConfig.canShowWhileLocked,
+ )
+ }
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = config::class,
+ icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ )
+ return config::class
+ }
+
+ private fun assertQuickAffordanceViewModel(
+ viewModel: KeyguardQuickAffordanceViewModel?,
+ testConfig: TestConfig,
+ configKey: KClass<*>,
+ ) {
+ checkNotNull(viewModel)
+ assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
+ if (testConfig.isVisible) {
+ assertThat(viewModel.icon).isEqualTo(testConfig.icon)
+ viewModel.onClicked.invoke(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = configKey,
+ animationController = animationController,
+ )
+ )
+ testConfig.intent?.let { intent ->
+ assertThat(launchQuickAffordanceUseCase.invocations)
+ .isEqualTo(
+ listOf(
+ FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
+ intent = intent,
+ canShowWhileLocked = testConfig.canShowWhileLocked,
+ animationController = animationController,
+ )
+ )
+ )
+ }
+ ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
+ } else {
+ assertThat(viewModel.isVisible).isFalse()
+ }
+ }
+
+ private data class TestConfig(
+ val isVisible: Boolean,
+ val icon: ContainedDrawable? = null,
+ val canShowWhileLocked: Boolean = false,
+ val intent: Intent? = null,
+ ) {
+ init {
+ check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_BURN_IN_OFFSET = 5
+ private const val RETURNED_BURN_IN_OFFSET = 3
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
new file mode 100644
index 0000000..80f3e46
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
@@ -0,0 +1,319 @@
+/*
+ * 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.lifecycle
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+@RunWithLooper
+class RepeatWhenAttachedTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
+
+ @Mock private lateinit var view: View
+ @Mock private lateinit var viewTreeObserver: ViewTreeObserver
+
+ private lateinit var block: Block
+ private lateinit var attachListeners: MutableList<View.OnAttachStateChangeListener>
+
+ @Before
+ fun setUp() {
+ Assert.setTestThread(Thread.currentThread())
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+ whenever(view.windowVisibility).thenReturn(View.GONE)
+ whenever(view.hasWindowFocus()).thenReturn(false)
+ attachListeners = mutableListOf()
+ whenever(view.addOnAttachStateChangeListener(any())).then {
+ attachListeners.add(it.arguments[0] as View.OnAttachStateChangeListener)
+ }
+ whenever(view.removeOnAttachStateChangeListener(any())).then {
+ attachListeners.remove(it.arguments[0] as View.OnAttachStateChangeListener)
+ }
+ block = Block()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `repeatWhenAttached - enforces main thread`() = runBlockingTest {
+ Assert.setTestThread(null)
+
+ repeatWhenAttached()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `repeatWhenAttached - dispose enforces main thread`() = runBlockingTest {
+ val disposableHandle = repeatWhenAttached()
+ Assert.setTestThread(null)
+
+ disposableHandle.dispose()
+ }
+
+ @Test
+ fun `repeatWhenAttached - view starts detached - runs block when attached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ repeatWhenAttached()
+ assertThat(block.invocationCount).isEqualTo(0)
+
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ attachListeners.last().onViewAttachedToWindow(view)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - view already attached - immediately runs block`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - starts visible without focus - STARTED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - starts with focus but invisible - CREATED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.hasWindowFocus()).thenReturn(true)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - starts visible and with focus - RESUMED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ whenever(view.hasWindowFocus()).thenReturn(true)
+
+ repeatWhenAttached()
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - becomes visible without focus - STARTED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(listenerCaptor.capture())
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ listenerCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - gains focus but invisible - CREATED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val listenerCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(listenerCaptor.capture())
+
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ listenerCaptor.value.onWindowFocusChanged(true)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - becomes visible and gains focus - RESUMED`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ val visibleCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(visibleCaptor.capture())
+ val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(focusCaptor.capture())
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ visibleCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ focusCaptor.value.onWindowFocusChanged(true)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - view gets detached - destroys the lifecycle`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - view gets reattached - recreates a lifecycle`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ repeatWhenAttached()
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ attachListeners.last().onViewAttachedToWindow(view)
+
+ assertThat(block.invocationCount).isEqualTo(2)
+ assertThat(block.invocations[0].lifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ assertThat(block.invocations[1].lifecycleState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - dispose attached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val handle = repeatWhenAttached()
+
+ handle.dispose()
+
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ @Test
+ fun `repeatWhenAttached - dispose never attached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ val handle = repeatWhenAttached()
+
+ handle.dispose()
+
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(0)
+ }
+
+ @Test
+ fun `repeatWhenAttached - dispose previously attached now detached`() = runBlockingTest {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val handle = repeatWhenAttached()
+ attachListeners.last().onViewDetachedFromWindow(view)
+
+ handle.dispose()
+
+ assertThat(attachListeners).isEmpty()
+ assertThat(block.invocationCount).isEqualTo(1)
+ assertThat(block.latestLifecycleState).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ private fun CoroutineScope.repeatWhenAttached(): DisposableHandle {
+ return view.repeatWhenAttached(
+ coroutineContext = coroutineContext,
+ block = block,
+ )
+ }
+
+ private class Block : suspend LifecycleOwner.(View) -> Unit {
+ data class Invocation(
+ val lifecycleOwner: LifecycleOwner,
+ ) {
+ val lifecycleState: Lifecycle.State
+ get() = lifecycleOwner.lifecycle.currentState
+ }
+
+ private val _invocations = mutableListOf<Invocation>()
+ val invocations: List<Invocation> = _invocations
+ val invocationCount: Int
+ get() = _invocations.size
+ val latestLifecycleState: Lifecycle.State
+ get() = _invocations.last().lifecycleState
+
+ override suspend fun invoke(lifecycleOwner: LifecycleOwner, view: View) {
+ _invocations.add(Invocation(lifecycleOwner))
+ }
+ }
+
+ /**
+ * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
+ * in LifecycleRegistry.
+ */
+ class InstantTaskExecutorRule : TestWatcher() {
+ // TODO(b/240620122): This is a copy of
+ // androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be replaced
+ // with a dependency on the real library once b/ is cleared.
+ override fun starting(description: Description) {
+ super.starting(description)
+ ArchTaskExecutor.getInstance()
+ .setDelegate(
+ object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun postToMainThread(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun isMainThread(): Boolean {
+ return true
+ }
+ }
+ )
+ }
+
+ override fun finished(description: Description) {
+ super.finished(description)
+ ArchTaskExecutor.getInstance().setDelegate(null)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
deleted file mode 100644
index 4f5c570..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-package com.android.systemui.lifecycle
-
-import android.view.View
-import android.view.ViewTreeObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleRegistry
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class WindowAddedViewLifecycleOwnerTest : SysuiTestCase() {
-
- @Mock lateinit var view: View
- @Mock lateinit var viewTreeObserver: ViewTreeObserver
-
- private lateinit var underTest: WindowAddedViewLifecycleOwner
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
- whenever(view.isAttachedToWindow).thenReturn(false)
- whenever(view.windowVisibility).thenReturn(View.INVISIBLE)
- whenever(view.hasWindowFocus()).thenReturn(false)
-
- underTest = WindowAddedViewLifecycleOwner(view) { LifecycleRegistry.createUnsafe(it) }
- }
-
- @Test
- fun `detached - invisible - does not have focus -- INITIALIZED`() {
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `detached - invisible - has focus -- INITIALIZED`() {
- whenever(view.hasWindowFocus()).thenReturn(true)
- val captor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(captor))
- captor.value.onWindowFocusChanged(true)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `detached - visible - does not have focus -- INITIALIZED`() {
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val captor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(captor))
- captor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `detached - visible - has focus -- INITIALIZED`() {
- whenever(view.hasWindowFocus()).thenReturn(true)
- val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
- focusCaptor.value.onWindowFocusChanged(true)
-
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
- visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
- }
-
- @Test
- fun `attached - invisible - does not have focus -- CREATED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val captor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(captor))
- captor.value.onWindowAttached()
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
- }
-
- @Test
- fun `attached - invisible - has focus -- CREATED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
- attachCaptor.value.onWindowAttached()
-
- whenever(view.hasWindowFocus()).thenReturn(true)
- val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
- focusCaptor.value.onWindowFocusChanged(true)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
- }
-
- @Test
- fun `attached - visible - does not have focus -- STARTED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
- attachCaptor.value.onWindowAttached()
-
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
- visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
- }
-
- @Test
- fun `attached - visible - has focus -- RESUMED`() {
- whenever(view.isAttachedToWindow).thenReturn(true)
- val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
- verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
- attachCaptor.value.onWindowAttached()
-
- whenever(view.hasWindowFocus()).thenReturn(true)
- val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
- verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
- focusCaptor.value.onWindowFocusChanged(true)
-
- whenever(view.windowVisibility).thenReturn(View.VISIBLE)
- val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
- verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
- visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
-
- assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
- }
-
- @Test
- fun dispose() {
- underTest.dispose()
-
- verify(viewTreeObserver).removeOnWindowAttachListener(any())
- verify(viewTreeObserver).removeOnWindowVisibilityChangeListener(any())
- verify(viewTreeObserver).removeOnWindowFocusChangeListener(any())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
new file mode 100644
index 0000000..4abb973
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -0,0 +1,167 @@
+package com.android.systemui.log
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnitRunner
+
+@SmallTest
+@RunWith(MockitoJUnitRunner::class)
+class LogBufferTest : SysuiTestCase() {
+ private lateinit var buffer: LogBuffer
+
+ private lateinit var outputWriter: StringWriter
+
+ @Mock
+ private lateinit var logcatEchoTracker: LogcatEchoTracker
+
+ @Before
+ fun setup() {
+ outputWriter = StringWriter()
+ buffer = createBuffer(UNBOUNDED_STACK_TRACE, NESTED_TRACE_DEPTH)
+ }
+
+ private fun createBuffer(rootTraceDepth: Int, nestedTraceDepth: Int): LogBuffer {
+ return LogBuffer("TestBuffer",
+ 1,
+ logcatEchoTracker,
+ false,
+ rootStackTraceDepth = rootTraceDepth,
+ nestedStackTraceDepth = nestedTraceDepth)
+ }
+
+ @Test
+ fun log_shouldSaveLogToBuffer() {
+ buffer.log("Test", LogLevel.INFO, "Some test message")
+
+ val dumpedString = dumpBuffer()
+
+ assertThat(dumpedString).contains("Some test message")
+ }
+
+ @Test
+ fun log_shouldRotateIfLogBufferIsFull() {
+ buffer.log("Test", LogLevel.INFO, "This should be rotated")
+ buffer.log("Test", LogLevel.INFO, "New test message")
+
+ val dumpedString = dumpBuffer()
+
+ assertThat(dumpedString).contains("New test message")
+ }
+
+ @Test
+ fun dump_writesExceptionAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 2, nestedTraceDepth = -1)
+ // stack trace depth of 5
+ val exception = createTestException("Exception message", "TestClass", 5)
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedString = dumpBuffer()
+
+ // logs are limited to depth 2
+ assertThat(dumpedString).contains("E Tag: Extra message")
+ assertThat(dumpedString).contains("E Tag: java.lang.RuntimeException: Exception message")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+ assertThat(dumpedString)
+ .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ }
+
+ @Test
+ fun dump_writesCauseAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ val exception = createTestException("Exception message",
+ "TestClass",
+ 1,
+ cause = createTestException("The real cause!", "TestClass", 5))
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedString = dumpBuffer()
+
+ // logs are limited to depth 2
+ assertThat(dumpedString)
+ .contains("E Tag: Caused by: java.lang.RuntimeException: The real cause!")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)")
+ assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)")
+ assertThat(dumpedString)
+ .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)")
+ }
+
+ @Test
+ fun dump_writesSuppressedExceptionAndStacktraceLimitedToGivenDepth() {
+ buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2)
+ val exception = RuntimeException("Root exception message")
+ exception.addSuppressed(
+ createTestException(
+ "First suppressed exception",
+ "FirstClass",
+ 5,
+ createTestException("Cause of suppressed exp", "ThirdClass", 5)
+ ))
+ exception.addSuppressed(
+ createTestException("Second suppressed exception", "SecondClass", 5))
+ buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+
+ val dumpedStr = dumpBuffer()
+
+ // logs are limited to depth 2
+ // first suppressed exception
+ assertThat(dumpedStr)
+ .contains("E Tag: Suppressed: " +
+ "java.lang.RuntimeException: First suppressed exception")
+ assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat FirstClass.TestMethod(FirstClass.java:3)")
+
+ assertThat(dumpedStr)
+ .contains("E Tag: Caused by: java.lang.RuntimeException: Cause of suppressed exp")
+ assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:3)")
+
+ // second suppressed exception
+ assertThat(dumpedStr)
+ .contains("E Tag: Suppressed: " +
+ "java.lang.RuntimeException: Second suppressed exception")
+ assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:1)")
+ assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:2)")
+ assertThat(dumpedStr)
+ .doesNotContain("E Tag: \tat SecondClass.TestMethod(SecondClass.java:3)")
+ }
+
+ private fun createTestException(
+ message: String,
+ errorClass: String,
+ stackTraceLength: Int,
+ cause: Throwable? = null
+ ): Exception {
+ val exception = RuntimeException(message, cause)
+ exception.stackTrace = createStackTraceElements(errorClass, stackTraceLength)
+ return exception
+ }
+
+ private fun dumpBuffer(): String {
+ buffer.dump(PrintWriter(outputWriter), tailLength = 100)
+ return outputWriter.toString()
+ }
+
+ private fun createStackTraceElements(
+ errorClass: String,
+ stackTraceLength: Int
+ ): Array<StackTraceElement> {
+ return (1..stackTraceLength).map { lineNumber ->
+ StackTraceElement(errorClass,
+ "TestMethod",
+ "$errorClass.java",
+ lineNumber)
+ }.toTypedArray()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 2eb4783..f133068 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -21,7 +21,6 @@
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.PowerManager
-import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -31,7 +30,8 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -53,7 +53,7 @@
@SmallTest
class MediaTttChipControllerCommonTest : SysuiTestCase() {
- private lateinit var controllerCommon: MediaTttChipControllerCommon<ChipInfo>
+ private lateinit var controllerCommon: TestControllerCommon
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
@@ -68,12 +68,12 @@
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@Mock
- private lateinit var tapGestureDetector: TapGestureDetector
- @Mock
private lateinit var powerManager: PowerManager
@Before
@@ -98,35 +98,49 @@
fakeExecutor = FakeExecutor(fakeClock)
controllerCommon = TestControllerCommon(
- context,
- logger,
- windowManager,
- viewUtil,
- fakeExecutor,
- accessibilityManager,
- tapGestureDetector,
- powerManager
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
)
}
@Test
- fun displayChip_chipAddedAndGestureDetectionStartedAndScreenOn() {
+ fun displayChip_chipAdded() {
controllerCommon.displayChip(getState())
verify(windowManager).addView(any(), any())
- verify(tapGestureDetector).addOnGestureDetectedCallback(any(), any())
+ }
+
+ @Test
+ fun displayChip_screenOff_screenWakes() {
+ whenever(powerManager.isScreenOn).thenReturn(false)
+
+ controllerCommon.displayChip(getState())
+
verify(powerManager).wakeUp(any(), any(), any())
}
@Test
- fun displayChip_twice_chipAndGestureDetectionNotAddedTwice() {
+ fun displayChip_screenAlreadyOn_screenNotWoken() {
+ whenever(powerManager.isScreenOn).thenReturn(true)
+
+ controllerCommon.displayChip(getState())
+
+ verify(powerManager, never()).wakeUp(any(), any(), any())
+ }
+
+ @Test
+ fun displayChip_twice_chipNotAddedTwice() {
controllerCommon.displayChip(getState())
reset(windowManager)
- reset(tapGestureDetector)
controllerCommon.displayChip(getState())
verify(windowManager, never()).addView(any(), any())
- verify(tapGestureDetector, never()).addOnGestureDetectedCallback(any(), any())
}
@Test
@@ -186,7 +200,20 @@
}
@Test
- fun removeChip_chipRemovedAndGestureDetectionStoppedAndRemovalLogged() {
+ fun displayScaleChange_chipReinflatedWithMostRecentState() {
+ controllerCommon.displayChip(getState(name = "First name"))
+ controllerCommon.displayChip(getState(name = "Second name"))
+ reset(windowManager)
+
+ getConfigurationListener().onDensityOrFontScaleChanged()
+
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ assertThat(controllerCommon.mostRecentChipInfo?.name).isEqualTo("Second name")
+ }
+
+ @Test
+ fun removeChip_chipRemovedAndRemovalLogged() {
// First, add the chip
controllerCommon.displayChip(getState())
@@ -195,7 +222,6 @@
controllerCommon.removeChip(reason)
verify(windowManager).removeView(any())
- verify(tapGestureDetector).removeOnGestureDetectedCallback(any())
verify(logger).logChipRemoval(reason)
}
@@ -307,41 +333,7 @@
assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE)
}
- @Test
- fun tapGestureDetected_outsideViewBounds_viewHidden() {
- controllerCommon.displayChip(getState())
- whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(false)
- val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
- verify(tapGestureDetector).addOnGestureDetectedCallback(
- any(), capture(gestureCallbackCaptor)
- )
- val callback = gestureCallbackCaptor.value!!
-
- callback.invoke(
- MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- )
-
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun tapGestureDetected_insideViewBounds_viewNotHidden() {
- controllerCommon.displayChip(getState())
- whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(true)
- val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
- verify(tapGestureDetector).addOnGestureDetectedCallback(
- any(), capture(gestureCallbackCaptor)
- )
- val callback = gestureCallbackCaptor.value!!
-
- callback.invoke(
- MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- )
-
- verify(windowManager, never()).removeView(any())
- }
-
- private fun getState() = ChipInfo()
+ private fun getState(name: String = "name") = ChipInfo(name)
private fun getChipView(): ViewGroup {
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
@@ -351,6 +343,12 @@
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
+ private fun getConfigurationListener(): ConfigurationListener {
+ val callbackCaptor = argumentCaptor<ConfigurationListener>()
+ verify(configurationController).addCallback(capture(callbackCaptor))
+ return callbackCaptor.value
+ }
+
inner class TestControllerCommon(
context: Context,
logger: MediaTttLogger,
@@ -358,8 +356,8 @@
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
- tapGestureDetector: TapGestureDetector,
- powerManager: PowerManager
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
) : MediaTttChipControllerCommon<ChipInfo>(
context,
logger,
@@ -367,16 +365,21 @@
viewUtil,
mainExecutor,
accessibilityManager,
- tapGestureDetector,
+ configurationController,
powerManager,
- R.layout.media_ttt_chip
+ R.layout.media_ttt_chip,
) {
+ var mostRecentChipInfo: ChipInfo? = null
+
override val windowLayoutParams = commonWindowLayoutParams
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun updateChipView(newChipInfo: ChipInfo, currentChipView: ViewGroup) {
+ super.updateChipView(newChipInfo, currentChipView)
+ mostRecentChipInfo = newChipInfo
+ }
override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
- inner class ChipInfo : ChipInfoCommon {
+ inner class ChipInfo(val name: String) : ChipInfoCommon {
override fun getTimeoutMs() = 1L
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index bbc5641..dbc5f7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -36,7 +36,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -68,6 +68,8 @@
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -103,7 +105,7 @@
viewUtil,
FakeExecutor(FakeSystemClock()),
accessibilityManager,
- TapGestureDetector(context),
+ configurationController,
powerManager,
Handler.getMain(),
receiverUiEventLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index 7ca0cd3..cd8ee73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -37,13 +37,12 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
-
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -52,8 +51,8 @@
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -70,6 +69,8 @@
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
+ private lateinit var configurationController: ConfigurationController
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -112,7 +113,7 @@
viewUtil,
fakeExecutor,
accessibilityManager,
- TapGestureDetector(context),
+ configurationController,
powerManager,
senderUiEventLogger
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index edcf479..8073103 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -112,7 +113,7 @@
mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager,
mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
- () -> Optional.of(mock(CentralSurfaces.class)),
+ () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardViewController.class),
mNavigationModeController, mUserTracker, mDumpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 4e3bdea..b0cf061 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
@@ -96,7 +97,8 @@
mock(AutoHideController.class),
mock(LightBarController.class),
Optional.of(mock(Pip.class)),
- Optional.of(mock(BackAnimation.class))));
+ Optional.of(mock(BackAnimation.class)),
+ mock(FeatureFlags.class)));
initializeNavigationBars();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 8fa5c93..51f0953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -72,6 +72,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
@@ -90,6 +91,7 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -98,7 +100,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -193,6 +194,8 @@
@Mock
private CentralSurfaces mCentralSurfaces;
@Mock
+ private KeyguardViewController mKeyguardViewController;
+ @Mock
private UserContextProvider mUserContextProvider;
@Mock
private Resources mResources;
@@ -237,8 +240,8 @@
mock(AccessibilityButtonTargetsObserver.class),
mSystemActions, mOverviewProxyService,
() -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
- mock(NavigationModeController.class), mock(UserTracker.class),
- mock(DumpManager.class)));
+ mKeyguardViewController, mock(NavigationModeController.class),
+ mock(UserTracker.class), mock(DumpManager.class)));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
});
@@ -377,7 +380,7 @@
// Verify navbar didn't alter and showing back icon when the keyguard is showing without
// requesting IME insets visible.
- doReturn(true).when(mCentralSurfaces).isKeyguardShowing();
+ doReturn(true).when(mKeyguardViewController).isShowing();
mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 1963e30..1e7722a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -16,19 +16,26 @@
package com.android.systemui.qs.carrier;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.Intent;
import android.os.Handler;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -49,6 +56,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -83,6 +91,7 @@
private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
private FakeSlotIndexResolver mSlotIndexResolver;
+ private ClickListenerTextView mNoCarrierTextView;
@Before
public void setup() throws Exception {
@@ -106,7 +115,8 @@
.when(mCarrierTextManager)
.setListening(any(CarrierTextManager.CarrierTextCallback.class));
- when(mQSCarrierGroup.getNoSimTextView()).thenReturn(new TextView(mContext));
+ mNoCarrierTextView = new ClickListenerTextView(mContext);
+ when(mQSCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
when(mQSCarrierGroup.getCarrier1View()).thenReturn(mQSCarrier1);
when(mQSCarrierGroup.getCarrier2View()).thenReturn(mQSCarrier2);
when(mQSCarrierGroup.getCarrier3View()).thenReturn(mQSCarrier3);
@@ -374,6 +384,47 @@
verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean());
}
+ @Test
+ public void testOnlyInternalViewsHaveClickableListener() {
+ ArgumentCaptor<View.OnClickListener> captor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+
+ verify(mQSCarrier1).setOnClickListener(captor.capture());
+ verify(mQSCarrier2).setOnClickListener(captor.getValue());
+ verify(mQSCarrier3).setOnClickListener(captor.getValue());
+
+ assertThat(mNoCarrierTextView.getOnClickListener()).isSameInstanceAs(captor.getValue());
+ verify(mQSCarrierGroup, never()).setOnClickListener(any());
+ }
+
+ @Test
+ public void testOnClickListenerDoesntStartActivityIfViewNotVisible() {
+ ArgumentCaptor<View.OnClickListener> captor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+
+ verify(mQSCarrier1).setOnClickListener(captor.capture());
+ when(mQSCarrier1.isVisibleToUser()).thenReturn(false);
+
+ captor.getValue().onClick(mQSCarrier1);
+ verifyZeroInteractions(mActivityStarter);
+ }
+
+ @Test
+ public void testOnClickListenerLaunchesActivityIfViewVisible() {
+ ArgumentCaptor<View.OnClickListener> listenerCaptor =
+ ArgumentCaptor.forClass(View.OnClickListener.class);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ verify(mQSCarrier1).setOnClickListener(listenerCaptor.capture());
+ when(mQSCarrier1.isVisibleToUser()).thenReturn(true);
+
+ listenerCaptor.getValue().onClick(mQSCarrier1);
+ verify(mActivityStarter)
+ .postStartActivityDismissingKeyguard(intentCaptor.capture(), anyInt());
+ assertThat(intentCaptor.getValue().getAction())
+ .isEqualTo(Settings.ACTION_WIRELESS_SETTINGS);
+ }
+
private class FakeSlotIndexResolver implements QSCarrierGroupController.SlotIndexResolver {
public boolean overrideInvalid;
@@ -382,4 +433,22 @@
return overrideInvalid ? -1 : subscriptionId;
}
}
+
+ private class ClickListenerTextView extends TextView {
+ View.OnClickListener mListener = null;
+
+ ClickListenerTextView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ super.setOnClickListener(l);
+ mListener = l;
+ }
+
+ View.OnClickListener getOnClickListener() {
+ return mListener;
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index e4c5299..e6bd396 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertFalse;
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;
@@ -38,6 +39,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
@@ -73,6 +75,8 @@
@Mock
private QSLogger mQSLogger;
@Mock
+ private FeatureFlags mFeatureFlags;
+ @Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private DialogLaunchAnimator mDialogLaunchAnimator;
@@ -94,6 +98,7 @@
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
mMetricsLogger,
+ mFeatureFlags,
mStatusBarStateController,
mActivityStarter,
mQSLogger,
@@ -125,7 +130,8 @@
mTestableLooper.processAllMessages();
ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class);
- verify(mController).createScreenRecordDialog(any(), onStartRecordingClicked.capture());
+ verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
+ eq(mDialogLaunchAnimator), eq(mActivityStarter), onStartRecordingClicked.capture());
// When starting the recording, we collapse the shade and disable the dialog animation.
assertNotNull(onStartRecordingClicked.getValue());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
new file mode 100644
index 0000000..2d2f4cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.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.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RippleViewTest : SysuiTestCase() {
+ @Mock
+ private lateinit var rippleView: RippleView
+
+ @Before
+ fun setup() {
+ rippleView = RippleView(context, null)
+ }
+
+ @Test
+ fun testSetupShader_compilesCircle() {
+ rippleView.setupShader(RippleShader.RippleShape.CIRCLE)
+ }
+
+ @Test
+ fun testSetupShader_compilesRoundedBox() {
+ rippleView.setupShader(RippleShader.RippleShape.ROUNDED_BOX)
+ }
+
+ @Test
+ fun testSetupShader_compilesEllipse() {
+ rippleView.setupShader(RippleShader.RippleShape.ELLIPSE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index b05d9a3..a1d78cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -30,6 +30,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
@@ -38,6 +39,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaProjectionCaptureTarget;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -108,8 +110,17 @@
}
@Test
- public void testLogStartRecording() {
- Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false);
+ public void testLogStartFullScreenRecording() {
+ Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
+ mRecordingService.onStartCommand(startIntent, 0, 0);
+
+ verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
+ }
+
+ @Test
+ public void testLogStartPartialRecording() {
+ MediaProjectionCaptureTarget target = new MediaProjectionCaptureTarget(new Binder());
+ Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, target);
mRecordingService.onStartCommand(startIntent, 0, 0);
verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
@@ -142,7 +153,7 @@
// When the screen recording does not start properly
doThrow(new RuntimeException("fail")).when(mScreenMediaRecorder).start();
- Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false);
+ Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
mRecordingService.onStartCommand(startIntent, 0, 0);
// Then the state is set to not recording
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt
new file mode 100644
index 0000000..03d9444
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenrecord
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserContextProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class ScreenRecordDialogTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var starter: ActivityStarter
+ @Mock
+ private lateinit var controller: RecordingController
+ @Mock
+ private lateinit var userContextProvider: UserContextProvider
+ @Mock
+ private lateinit var flags: FeatureFlags
+ @Mock
+ private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock
+ private lateinit var onStartRecordingClicked: Runnable
+
+ private lateinit var dialog: ScreenRecordDialog
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ dialog = ScreenRecordDialog(
+ context, controller, starter, userContextProvider, flags, dialogLaunchAnimator,
+ onStartRecordingClicked
+ )
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun testShowDialog_partialScreenSharingDisabled_appButtonIsNotVisible() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
+
+ dialog.show()
+
+ val visibility = dialog.requireViewById<View>(R.id.button_app).visibility
+ assertThat(visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testShowDialog_partialScreenSharingEnabled_appButtonIsVisible() {
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+
+ dialog.show()
+
+ val visibility = dialog.requireViewById<View>(R.id.button_app).visibility
+ assertThat(visibility).isEqualTo(View.VISIBLE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
new file mode 100644
index 0000000..ce3f20d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageCaptureImplTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.IActivityTaskManager
+import android.graphics.Rect
+import android.hardware.display.DisplayManager
+import android.os.Binder
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.SurfaceControl.ScreenshotHardwareBuffer
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test the logic within ImageCaptureImpl
+ */
+@RunWith(AndroidTestingRunner::class)
+class ImageCaptureImplTest : SysuiTestCase() {
+ private val displayManager = mock<DisplayManager>()
+ private val atmService = mock<IActivityTaskManager>()
+ private val capture = TestableImageCaptureImpl(displayManager, atmService)
+
+ @Test
+ fun captureDisplayWithCrop() {
+ capture.captureDisplay(Display.DEFAULT_DISPLAY, Rect(1, 2, 3, 4))
+ assertThat(capture.token).isNotNull()
+ assertThat(capture.width!!).isEqualTo(2)
+ assertThat(capture.height!!).isEqualTo(2)
+ assertThat(capture.crop!!).isEqualTo(Rect(1, 2, 3, 4))
+ }
+
+ @Test
+ fun captureDisplayWithNullCrop() {
+ capture.captureDisplay(Display.DEFAULT_DISPLAY, null)
+ assertThat(capture.token).isNotNull()
+ assertThat(capture.width!!).isEqualTo(0)
+ assertThat(capture.height!!).isEqualTo(0)
+ assertThat(capture.crop!!).isEqualTo(Rect())
+ }
+
+ class TestableImageCaptureImpl(
+ displayManager: DisplayManager,
+ atmService: IActivityTaskManager
+ ) :
+ ImageCaptureImpl(displayManager, atmService) {
+
+ var token: IBinder? = null
+ var width: Int? = null
+ var height: Int? = null
+ var crop: Rect? = null
+
+ override fun physicalDisplayToken(displayId: Int): IBinder {
+ return Binder()
+ }
+
+ override fun captureDisplay(displayToken: IBinder, width: Int, height: Int, crop: Rect):
+ ScreenshotHardwareBuffer {
+ this.token = displayToken
+ this.width = width
+ this.height = height
+ this.crop = crop
+ 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
new file mode 100644
index 0000000..002f23a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.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.screenshot
+
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.ColorSpace
+import android.graphics.Insets
+import android.graphics.Rect
+import android.hardware.HardwareBuffer
+import android.net.Uri
+import android.view.WindowManager
+import android.view.WindowManager.ScreenshotSource
+import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
+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.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import org.junit.Test
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.isNull
+
+class RequestProcessorTest {
+ private val controller = mock<ScreenshotController>()
+ private val bitmapCaptor = argumentCaptor<Bitmap>()
+
+ @Test
+ fun testFullScreenshot() {
+ val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+ val onSavedListener = mock<Consumer<Uri>>()
+ val callback = mock<RequestCallback>()
+ val processor = RequestProcessor(controller)
+
+ processor.processRequest(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, onSavedListener,
+ request, callback)
+
+ verify(controller).takeScreenshotFullscreen(/* topComponent */ isNull(),
+ eq(onSavedListener), eq(callback))
+ }
+
+ @Test
+ fun testSelectedRegionScreenshot() {
+ val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_KEY_CHORD)
+ val onSavedListener = mock<Consumer<Uri>>()
+ val callback = mock<RequestCallback>()
+ val processor = RequestProcessor(controller)
+
+ processor.processRequest(WindowManager.TAKE_SCREENSHOT_SELECTED_REGION, onSavedListener,
+ request, callback)
+
+ verify(controller).takeScreenshotPartial(/* topComponent */ isNull(),
+ eq(onSavedListener), eq(callback))
+ }
+
+ @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)
+
+ 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))!!
+ val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
+
+ val request = ScreenshotRequest(ScreenshotSource.SCREENSHOT_OTHER, bitmapBundle,
+ bounds, Insets.NONE, taskId, userId, topComponent)
+
+ val onSavedListener = mock<Consumer<Uri>>()
+ val callback = mock<RequestCallback>()
+
+ processor.processRequest(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, onSavedListener,
+ request, 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()
+ }
+
+ private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
+ return bitmap.hardwareBuffer == this.hardwareBuffer &&
+ bitmap.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 e2673bb..fc28349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -47,6 +47,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.PointF;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
@@ -97,6 +98,10 @@
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
+import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
@@ -373,6 +378,11 @@
private ViewParent mViewParent;
@Mock
private ViewTreeObserver mViewTreeObserver;
+ @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
+ @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
+ @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
+ @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
+ mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -452,6 +462,7 @@
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
+ mDumpManager,
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager,
mInteractionJankMonitor),
@@ -518,7 +529,7 @@
mNotificationShadeWindowController,
mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
- mMetricsLogger, mActivityManager, mConfigurationController,
+ mMetricsLogger, mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHiearchyManager,
mStatusBarKeyguardViewManager,
@@ -564,7 +575,11 @@
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
mSystemClock,
- mock(CameraGestureHelper.class));
+ mock(CameraGestureHelper.class),
+ () -> mKeyguardBottomAreaViewModel,
+ () -> mSetClockPositionUseCase,
+ () -> mSetKeyguardBottomAreaAlphaUseCase,
+ () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
@@ -889,6 +904,76 @@
}
@Test
+ public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isTrue();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ true, /* dozingAlwaysOn= */ false);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_pulsing_isCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(true);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_splitShade_notPulsing_isNotCentered() {
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
+ mStatusBarStateController.setState(KEYGUARD);
+ enableSplitShade(/* enabled= */ true);
+
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ true);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
+ public void keyguardStatusView_singleShade_isCentered() {
+ enableSplitShade(/* enabled= */ false);
+ // The conditions below would make the clock NOT be centered on split shade.
+ // On single shade it should always be centered though.
+ when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+ when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
+ mStatusBarStateController.setState(KEYGUARD);
+ setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
+
+ assertThat(isKeyguardStatusViewCentered()).isFalse();
+ }
+
+ @Test
public void testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() {
givenViewAttached();
when(mResources.getBoolean(
@@ -1460,4 +1545,19 @@
.thenReturn(splitShadeFullTransitionDistance);
mNotificationPanelViewController.updateResources();
}
+
+ private void setDozing(boolean dozing, boolean dozingAlwaysOn) {
+ when(mDozeParameters.getAlwaysOn()).thenReturn(dozingAlwaysOn);
+ mNotificationPanelViewController.setDozing(
+ /* dozing= */ dozing,
+ /* animate= */ false,
+ /* wakeUpTouchLocation= */ new PointF()
+ );
+ }
+
+ private boolean isKeyguardStatusViewCentered() {
+ mNotificationPanelViewController.updateResources();
+ return getConstraintSetLayout(R.id.keyguard_status_view).endToEnd
+ == ConstraintSet.PARENT_ID;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 1dfd7c2..ad3d3d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -28,7 +28,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -44,8 +43,6 @@
import android.view.View;
import android.view.WindowManager;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.ViewTreeLifecycleOwner;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -57,7 +54,6 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -189,24 +185,6 @@
}
@Test
- public void attach_setsUpLifecycleOwner() {
- mNotificationShadeWindowController.attach();
-
- assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView)).isNotNull();
- }
-
- @Test
- public void attach_doesNotSetUpLifecycleOwnerIfAlreadySet() {
- final LifecycleOwner previouslySet = mock(LifecycleOwner.class);
- ViewTreeLifecycleOwner.set(mNotificationShadeWindowView, previouslySet);
-
- mNotificationShadeWindowController.attach();
-
- assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView))
- .isEqualTo(previouslySet);
- }
-
- @Test
public void setScrimsVisibility_earlyReturn() {
clearInvocations(mWindowManager);
mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 665d849..fa16fef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -48,7 +48,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index a4ce9cd..79b1bb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -29,14 +29,11 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
import org.junit.Ignore;
@@ -55,11 +52,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NonPhoneDependencyTest extends SysuiTestCase {
@Mock private NotificationPresenter mPresenter;
- @Mock private NotifStackController mStackController;
@Mock private NotificationListContainer mListContainer;
- @Mock
- private NotificationEntryListener mEntryListener;
- @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationEntryListener mEntryListener;
@Mock private RemoteInputController.Delegate mDelegate;
@Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
@Mock private CheckSaveListener mCheckSaveListener;
@@ -76,28 +70,22 @@
@Ignore("Causes binder calls which fail")
@Test
public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
- mDependency.injectMockDependency(ShadeController.class);
NotificationEntryManager entryManager = Dependency.get(NotificationEntryManager.class);
NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
- NotificationListener notificationListener = Dependency.get(NotificationListener.class);
NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
NotificationRemoteInputManager remoteInputManager =
Dependency.get(NotificationRemoteInputManager.class);
NotificationLockscreenUserManager lockscreenUserManager =
Dependency.get(NotificationLockscreenUserManager.class);
- NotificationViewHierarchyManager viewHierarchyManager =
- Dependency.get(NotificationViewHierarchyManager.class);
- entryManager.setUpWithPresenter(mPresenter);
entryManager.addNotificationEntryListener(mEntryListener);
gutsManager.setUpWithPresenter(mPresenter, mListContainer,
- mCheckSaveListener, mOnSettingsClickListener);
+ mOnSettingsClickListener);
notificationLogger.setUpWithContainer(mListContainer);
mediaManager.setUpWithPresenter(mPresenter);
remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback,
mDelegate);
lockscreenUserManager.setUpWithPresenter(mPresenter);
- viewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
TestableLooper.get(this).processAllMessages();
assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
deleted file mode 100644
index 407044b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-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.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.DynamicChildBindController;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.util.List;
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotifStackController mStackController;
- @Spy private FakeListContainer mListContainer = new FakeListContainer();
-
- // Dependency mocks:
- @Mock private FeatureFlags mFeatureFlags;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private NotificationEntryManager mEntryManager;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock private NotificationGroupManagerLegacy mGroupManager;
- @Mock private VisualStabilityManager mVisualStabilityManager;
-
- private TestableLooper mTestableLooper;
- private Handler mHandler;
- private NotificationViewHierarchyManager mViewHierarchyManager;
- private NotificationTestHelper mHelper;
- private boolean mMadeReentrantCall = false;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mTestableLooper = TestableLooper.get(this);
- allowTestableLooperAsMainThread();
- mHandler = Handler.createAsync(mTestableLooper.getLooper());
-
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
- mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
- mLockscreenUserManager);
- mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
- mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
- when(mVisualStabilityManager.areGroupChangesAllowed()).thenReturn(true);
- when(mVisualStabilityManager.isReorderingAllowed()).thenReturn(true);
-
- when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
- mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
-
- mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
- mHandler, mFeatureFlags, mLockscreenUserManager, mGroupManager,
- mVisualStabilityManager,
- mock(StatusBarStateControllerImpl.class), mEntryManager,
- mock(KeyguardBypassController.class),
- Optional.of(mock(Bubbles.class)),
- mock(DynamicPrivacyController.class),
- mock(DynamicChildBindController.class),
- mock(LowPriorityInflationHelper.class),
- mock(AssistantFeedbackController.class),
- mNotifPipelineFlags,
- mock(KeyguardUpdateMonitor.class),
- mock(KeyguardStateController.class));
- mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer);
- }
-
- private NotificationEntry createEntry() throws Exception {
- ExpandableNotificationRow row = mHelper.createRow();
- return row.getEntry();
- }
-
- @Test
- public void testNotificationsBecomingBundled() throws Exception {
- // Tests 3 top level notifications becoming a single bundled notification with |entry0| as
- // the summary.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- NotificationEntry entry2 = createEntry();
-
- // Set up the prior state to look like three top level notifications.
- mListContainer.addContainerView(entry0.getRow());
- mListContainer.addContainerView(entry1.getRow());
- mListContainer.addContainerView(entry2.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1, entry2));
-
- // Set up group manager to report that they should be bundled now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(true);
- when(mGroupManager.isChildInGroup(entry2)).thenReturn(true);
- when(mGroupManager.getGroupSummary(entry1)).thenReturn(entry0);
- when(mGroupManager.getGroupSummary(entry2)).thenReturn(entry0);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildAdded(entry1.getRow());
- verify(mListContainer).notifyGroupChildAdded(entry2.getRow());
- assertTrue(Lists.newArrayList(entry0.getRow()).equals(mListContainer.mRows));
- }
-
- @Test
- public void testNotificationsBecomingUnbundled() throws Exception {
- // Tests a bundled notification becoming three top level notifications.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- NotificationEntry entry2 = createEntry();
- entry0.getRow().addChildNotification(entry1.getRow());
- entry0.getRow().addChildNotification(entry2.getRow());
-
- // Set up the prior state to look like one top level notification.
- mListContainer.addContainerView(entry0.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1, entry2));
-
- // Set up group manager to report that they should not be bundled now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry2)).thenReturn(false);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildRemoved(
- entry1.getRow(), entry0.getRow().getChildrenContainer());
- verify(mListContainer).notifyGroupChildRemoved(
- entry2.getRow(), entry0.getRow().getChildrenContainer());
- assertTrue(
- Lists.newArrayList(entry0.getRow(), entry1.getRow(), entry2.getRow())
- .equals(mListContainer.mRows));
- }
-
- @Test
- public void testNotificationsBecomingSuppressed() throws Exception {
- // Tests two top level notifications becoming a suppressed summary and a child.
- NotificationEntry entry0 = createEntry();
- NotificationEntry entry1 = createEntry();
- entry0.getRow().addChildNotification(entry1.getRow());
-
- // Set up the prior state to look like a top level notification.
- mListContainer.addContainerView(entry0.getRow());
- when(mEntryManager.getVisibleNotifications()).thenReturn(
- Lists.newArrayList(entry0, entry1));
-
- // Set up group manager to report a suppressed summary now.
- when(mGroupManager.isChildInGroup(entry0)).thenReturn(false);
- when(mGroupManager.isChildInGroup(entry1)).thenReturn(false);
- when(mGroupManager.isSummaryOfSuppressedGroup(entry0.getSbn())).thenReturn(true);
-
- // Run updateNotifications - the view hierarchy should be reorganized.
- mViewHierarchyManager.updateNotificationViews();
-
- verify(mListContainer).notifyGroupChildRemoved(
- entry1.getRow(), entry0.getRow().getChildrenContainer());
- assertTrue(Lists.newArrayList(entry0.getRow(), entry1.getRow()).equals(mListContainer.mRows));
- assertEquals(View.GONE, entry0.getRow().getVisibility());
- assertEquals(View.VISIBLE, entry1.getRow().getVisibility());
- }
-
- @Test
- public void testReentrantCallsToOnDynamicPrivacyChangedPostForLater() {
- // GIVEN a ListContainer that will make a re-entrant call to updateNotificationViews()
- mMadeReentrantCall = false;
- doAnswer((invocation) -> {
- if (!mMadeReentrantCall) {
- mMadeReentrantCall = true;
- mViewHierarchyManager.onDynamicPrivacyChanged();
- }
- return null;
- }).when(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we call updateNotificationViews()
- mViewHierarchyManager.updateNotificationViews();
-
- // THEN onNotificationViewUpdateFinished() is only called once
- verify(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we drain the looper
- mTestableLooper.processAllMessages();
-
- // THEN updateNotificationViews() is called a second time (for the reentrant call)
- verify(mListContainer, times(2)).onNotificationViewUpdateFinished();
- }
-
- @Test
- public void testMultipleReentrantCallsToOnDynamicPrivacyChangedOnlyPostOnce() {
- // GIVEN a ListContainer that will make many re-entrant calls to updateNotificationViews()
- mMadeReentrantCall = false;
- doAnswer((invocation) -> {
- if (!mMadeReentrantCall) {
- mMadeReentrantCall = true;
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- mViewHierarchyManager.onDynamicPrivacyChanged();
- }
- return null;
- }).when(mListContainer).onNotificationViewUpdateFinished();
-
- // WHEN we call updateNotificationViews() and drain the looper
- mViewHierarchyManager.updateNotificationViews();
- verify(mListContainer).onNotificationViewUpdateFinished();
- clearInvocations(mListContainer);
- mTestableLooper.processAllMessages();
-
- // THEN updateNotificationViews() is called only one more time
- verify(mListContainer).onNotificationViewUpdateFinished();
- }
-
- private class FakeListContainer implements NotificationListContainer {
- final LinearLayout mLayout = new LinearLayout(mContext);
- final List<View> mRows = Lists.newArrayList();
-
- @Override
- public void setChildTransferInProgress(boolean childTransferInProgress) {}
-
- @Override
- public void changeViewPosition(ExpandableView child, int newIndex) {
- mRows.remove(child);
- mRows.add(newIndex, child);
- }
-
- @Override
- public void notifyGroupChildAdded(ExpandableView row) {}
-
- @Override
- public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {}
-
- @Override
- public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {}
-
- @Override
- public void generateChildOrderChangedEvent() {}
-
- @Override
- public void onReset(ExpandableView view) {}
-
- @Override
- public int getContainerChildCount() {
- return mRows.size();
- }
-
- @Override
- public View getContainerChildAt(int i) {
- return mRows.get(i);
- }
-
- @Override
- public void removeContainerView(View v) {
- mLayout.removeView(v);
- mRows.remove(v);
- }
-
- @Override
- public void setNotificationActivityStarter(
- NotificationActivityStarter notificationActivityStarter) {}
-
- @Override
- public void addContainerView(View v) {
- mLayout.addView(v);
- mRows.add(v);
- }
-
- @Override
- public void addContainerViewAt(View v, int index) {
- mLayout.addView(v, index);
- mRows.add(index, v);
- }
-
- @Override
- public void setMaxDisplayedNotifications(int maxNotifications) {
- }
-
- @Override
- public ViewGroup getViewParentForNotification(NotificationEntry entry) {
- return null;
- }
-
- @Override
- public void onHeightChanged(ExpandableView view, boolean animate) {}
-
- @Override
- public void resetExposedMenuView(boolean animate, boolean force) {}
-
- @Override
- public NotificationSwipeActionHelper getSwipeActionHelper() {
- return null;
- }
-
- @Override
- public void cleanUpViewStateForEntry(NotificationEntry entry) { }
-
- @Override
- public boolean isInVisibleLocation(NotificationEntry entry) {
- return true;
- }
-
- @Override
- public void setChildLocationsChangedListener(
- NotificationLogger.OnChildLocationsChangedListener listener) {}
-
- @Override
- public boolean hasPulsingNotifications() {
- return false;
- }
-
- @Override
- public void onNotificationViewUpdateFinished() { }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index aeef6b0..842f057 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -77,10 +77,8 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -91,7 +89,6 @@
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationEntryManagerInflationTest;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -115,9 +112,7 @@
import java.util.Set;
/**
- * Unit tests for {@link NotificationEntryManager}. This test will not test any interactions with
- * inflation. Instead, for functional inflation tests, see
- * {@link NotificationEntryManagerInflationTest}.
+ * Unit tests for {@link NotificationEntryManager}.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -205,7 +200,6 @@
mStats = defaultStats(mEntry);
mSbn = mEntry.getSbn();
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
mEntryManager = new NotificationEntryManager(
mLogger,
mGroupManager,
@@ -214,7 +208,6 @@
() -> mRemoteInputManager,
mLeakDetector,
mStatusBarService,
- NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
mock(DumpManager.class),
mBgExecutor
);
@@ -230,7 +223,6 @@
mock(PeopleNotificationIdentifier.class),
mock(HighPriorityProvider.class),
mEnvironment));
- mEntryManager.setUpWithPresenter(mPresenter);
mEntryManager.addNotificationEntryListener(mEntryListener);
mEntryManager.addCollectionListener(mNotifCollectionListener);
mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
@@ -273,17 +265,6 @@
}
@Test
- public void testUpdateNotification_updatesUserSentiment() {
- mEntryManager.addActiveNotificationForTest(mEntry);
- setUserSentiment(
- mEntry.getKey(), Ranking.USER_SENTIMENT_NEGATIVE);
-
- mEntryManager.updateNotification(mSbn, mRankingMap);
-
- assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, mEntry.getUserSentiment());
- }
-
- @Test
public void testUpdateNotification_prePostEntryOrder() throws Exception {
TestableLooper.get(this).processAllMessages();
@@ -294,7 +275,6 @@
// Ensure that update callbacks happen in correct order
InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener);
order.verify(mEntryListener).onPreEntryUpdated(mEntry);
- order.verify(mPresenter).updateNotificationViews(any());
order.verify(mEntryListener).onPostEntryUpdated(mEntry);
}
@@ -305,7 +285,6 @@
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
- verify(mPresenter).updateNotificationViews(any());
verify(mEntryListener).onEntryRemoved(
argThat(matchEntryOnKey()), any(),
eq(false) /* removedByUser */, eq(UNDEFINED_DISMISS_REASON));
@@ -379,23 +358,6 @@
}
@Test
- public void testUpdateNotificationRanking() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(mRow);
- mEntry.setInflationTask(mAsyncInflationTask);
- mEntryManager.addActiveNotificationForTest(mEntry);
- setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertEquals(1, mEntry.getSmartActions().size());
- assertEquals("action", mEntry.getSmartActions().get(0).title);
- verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
- }
-
- @Test
public void testUpdateNotificationRanking_noChange() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -409,20 +371,6 @@
}
@Test
- public void testUpdateNotificationRanking_rowNotInflatedYet() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-
- mEntry.setRow(null);
- mEntryManager.addActiveNotificationForTest(mEntry);
- setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
-
- mEntryManager.updateNotificationRanking(mRankingMap);
- assertEquals(1, mEntry.getSmartActions().size());
- assertEquals("action", mEntry.getSmartActions().get(0).title);
- }
-
- @Test
public void testUpdateNotificationRanking_pendingNotification() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
@@ -614,31 +562,6 @@
/* Tests annexed from NotificationDataTest go here */
@Test
- public void testChannelIsSetWhenAdded() {
- NotificationChannel nc = new NotificationChannel(
- "testId",
- "testName",
- IMPORTANCE_DEFAULT);
-
- Ranking r = new RankingBuilder()
- .setKey(mEntry.getKey())
- .setChannel(nc)
- .build();
-
- RankingMap rm = new RankingMap(new Ranking[] { r });
-
- // GIVEN: a notification is added, and the ranking updated
- mEntryManager.addActiveNotificationForTest(mEntry);
- mEntryManager.updateRanking(rm, "testReason");
-
- // THEN the notification entry better have a channel on it
- assertEquals(
- "Channel must be set when adding a notification",
- nc.getName(),
- mEntry.getChannel().getName());
- }
-
- @Test
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
Notification.Builder n = new Notification.Builder(mContext, "di")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index ed8b532..2cacaf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -55,7 +56,6 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.wm.shell.bubbles.Bubbles;
import org.junit.After;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 5386171..e00e20f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -118,7 +118,6 @@
mPowerManager,
mDreamManager,
mAmbientDisplayConfiguration,
- mNotificationFilter,
mBatteryController,
mStatusBarStateController,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index bc54bf8..922e93d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -16,27 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static android.view.DragEvent.ACTION_DRAG_STARTED;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.DragEvent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -49,6 +28,24 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index dc6d744..381d72f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -76,10 +76,10 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
deleted file mode 100644
index bf7549a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ /dev/null
@@ -1,467 +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.systemui.statusbar.notification.row;
-
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
-import static junit.framework.Assert.assertNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.os.Handler;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.icon.IconBuilder;
-import com.android.systemui.statusbar.notification.icon.IconManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
-import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
-import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
-import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.leak.LeakDetector;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.wmshell.BubblesManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Functional tests for notification inflation from {@link NotificationEntryManager}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NotificationEntryManagerInflationTest extends SysuiTestCase {
-
- private static final String TEST_TITLE = "Title";
- private static final String TEST_TEXT = "Text";
- private static final long TIMEOUT_TIME = 10000;
- private static final Runnable TIMEOUT_RUNNABLE = () -> {
- throw new RuntimeException("Timed out waiting to inflate");
- };
-
- @Mock private NotificationListener mNotificationListener;
- @Mock private NotificationPresenter mPresenter;
- @Mock private NotificationEntryManager.KeyguardEnvironment mEnvironment;
- @Mock private NotificationListContainer mListContainer;
- @Mock private NotificationEntryListener mEntryListener;
- @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback;
- @Mock private HeadsUpManager mHeadsUpManager;
- @Mock private NotificationInterruptStateProvider mNotificationInterruptionStateProvider;
- @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock private NotificationGutsManager mGutsManager;
- @Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private NotificationMediaManager mNotificationMediaManager;
- @Mock(answer = Answers.RETURNS_SELF)
- private ExpandableNotificationRowComponent.Builder mExpandableNotificationRowComponentBuilder;
- @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent;
- @Mock private KeyguardBypassController mKeyguardBypassController;
- @Mock private StatusBarStateController mStatusBarStateController;
-
- @Mock private NotificationGroupManagerLegacy mGroupMembershipManager;
- @Mock private NotificationGroupManagerLegacy mGroupExpansionManager;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private LeakDetector mLeakDetector;
-
- @Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
- @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
- @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
- @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
- @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
-
- private StatusBarNotification mSbn;
- private NotificationListenerService.RankingMap mRankingMap;
- private NotificationEntryManager mEntryManager;
- private NotificationRowBinderImpl mRowBinder;
- private Handler mHandler;
- private FakeExecutor mBgExecutor;
- private RowContentBindStage mRowContentBindStage;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(SmartReplyController.class);
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
-
- mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
-
- // Add an action so heads up content views are made
- Notification.Action action = new Notification.Action.Builder(null, null, null).build();
- Notification notification = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle(TEST_TITLE)
- .setContentText(TEST_TEXT)
- .setActions(action)
- .build();
- mSbn = new SbnBuilder()
- .setNotification(notification)
- .build();
-
- when(mNotifPipelineFlags.checkLegacyPipelineEnabled()).thenReturn(true);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
-
- mEntryManager = new NotificationEntryManager(
- mock(NotificationEntryManagerLogger.class),
- mGroupMembershipManager,
- mNotifPipelineFlags,
- () -> mRowBinder,
- () -> mRemoteInputManager,
- mLeakDetector,
- mock(IStatusBarService.class),
- NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
- mock(DumpManager.class),
- mBgExecutor
- );
- mEntryManager.initialize(
- mNotificationListener,
- new NotificationRankingManager(
- () -> mock(NotificationMediaManager.class),
- mGroupMembershipManager,
- mHeadsUpManager,
- mock(NotificationFilter.class),
- mock(NotificationEntryManagerLogger.class),
- mock(NotificationSectionsFeatureManager.class),
- mock(PeopleNotificationIdentifier.class),
- mock(HighPriorityProvider.class),
- mEnvironment));
-
- NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager);
- NotifBindPipeline pipeline = new NotifBindPipeline(
- mEntryManager,
- mock(NotifBindPipelineLogger.class),
- TestableLooper.get(this).getLooper());
- mBgExecutor = new FakeExecutor(new FakeSystemClock());
- NotificationContentInflater binder = new NotificationContentInflater(
- cache,
- mRemoteInputManager,
- mock(ConversationNotificationProcessor.class),
- mock(MediaFeatureFlag.class),
- mBgExecutor,
- new SmartReplyStateInflater() {
- @Override
- public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
- return mInflatedSmartReplyState;
- }
-
- @Override
- public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(
- Context sysuiContext, Context notifPackageContext,
- NotificationEntry entry,
- InflatedSmartReplyState existingSmartReplyState,
- InflatedSmartReplyState newSmartReplyState) {
- return mInflatedSmartReplies;
- }
- });
- mRowContentBindStage = new RowContentBindStage(
- binder,
- mock(NotifInflationErrorManager.class),
- mock(RowContentBindStageLogger.class));
- pipeline.setStage(mRowContentBindStage);
-
- ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
- ArgumentCaptor.forClass(ExpandableNotificationRow.class);
- when(mExpandableNotificationRowComponentBuilder
- .expandableNotificationRow(viewCaptor.capture()))
- .thenReturn(mExpandableNotificationRowComponentBuilder);
- when(mExpandableNotificationRowComponentBuilder.build())
- .thenReturn(mExpandableNotificationRowComponent);
-
- when(mExpandableNotificationRowComponent.getExpandableNotificationRowController())
- .thenAnswer((Answer<ExpandableNotificationRowController>) invocation ->
- new ExpandableNotificationRowController(
- viewCaptor.getValue(),
- mock(ActivatableNotificationViewController.class),
- mock(RemoteInputViewSubcomponent.Factory.class),
- mock(MetricsLogger.class),
- mListContainer,
- mNotificationMediaManager,
- mock(SmartReplyConstants.class),
- mock(SmartReplyController.class),
- mock(PluginManager.class),
- new FakeSystemClock(),
- "FOOBAR",
- "FOOBAR",
- mKeyguardBypassController,
- mGroupMembershipManager,
- mGroupExpansionManager,
- mRowContentBindStage,
- mock(NotificationLogger.class),
- mHeadsUpManager,
- mPresenter,
- mStatusBarStateController,
- mGutsManager,
- true,
- null,
- new FalsingManagerFake(),
- new FalsingCollectorFake(),
- mock(FeatureFlags.class),
- mPeopleNotificationIdentifier,
- Optional.of(mock(BubblesManager.class)),
- mock(ExpandableNotificationRowDragController.class)));
-
- when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
- .thenReturn(mNotificationRowComponentBuilder);
- when(mNotificationRowComponentBuilder.build()).thenReturn(
- () -> mActivatableNotificationViewController);
-
- mRowBinder = new NotificationRowBinderImpl(
- mContext,
- new NotificationMessagingUtil(mContext),
- mRemoteInputManager,
- mLockscreenUserManager,
- pipeline,
- mRowContentBindStage,
- RowInflaterTask::new,
- mExpandableNotificationRowComponentBuilder,
- new IconManager(
- mEntryManager,
- mock(LauncherApps.class),
- new IconBuilder(mContext)),
- mock(LowPriorityInflationHelper.class),
- mNotifPipelineFlags);
-
- mEntryManager.setUpWithPresenter(mPresenter);
- mEntryManager.addNotificationEntryListener(mEntryListener);
-
- mRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback);
- mRowBinder.setNotificationClicker(mock(NotificationClicker.class));
-
- Ranking ranking = new Ranking();
- ranking.populate(
- mSbn.getKey(),
- 0,
- false,
- 0,
- 0,
- IMPORTANCE_DEFAULT,
- null,
- null,
- null,
- null,
- null,
- true,
- Ranking.USER_SENTIMENT_NEUTRAL,
- false,
- -1,
- false,
- null,
- null,
- false,
- false,
- false,
- null,
- 0,
- false
- );
- mRankingMap = new NotificationListenerService.RankingMap(new Ranking[] {ranking});
-
- TestableLooper.get(this).processAllMessages();
- }
-
- @After
- public void cleanUp() {
- // Don't leave anything on main thread
- TestableLooper.get(this).processAllMessages();
- }
-
- @Test
- public void testAddNotification() {
- // WHEN a notification is added
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
-
- waitForInflation();
-
- // THEN the notification has its row inflated
- assertNotNull(entry.getRow());
- assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
-
- // THEN inflation callbacks are called
- verify(mBindCallback).onBindRow(entry.getRow());
- verify(mEntryListener, never()).onInflationError(any(), any());
- verify(mEntryListener).onEntryInflated(entry);
- verify(mEntryListener).onNotificationAdded(entry);
-
- // THEN the notification is active
- assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()));
-
- // THEN we update the presenter
- verify(mPresenter).updateNotificationViews(any());
- }
-
- @Test
- public void testUpdateNotification() {
- // GIVEN a notification already added
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
- waitForInflation();
-
- Mockito.reset(mEntryListener);
- Mockito.reset(mPresenter);
-
- // WHEN the notification is updated
- mEntryManager.updateNotification(mSbn, mRankingMap);
-
- waitForInflation();
-
- // THEN the notification has its row and inflated
- assertNotNull(entry.getRow());
-
- // THEN inflation callbacks are called
- verify(mEntryListener, never()).onInflationError(any(), any());
- verify(mEntryListener).onEntryReinflated(entry);
-
- // THEN we update the presenter
- verify(mPresenter).updateNotificationViews(any());
- }
-
- @Test
- public void testContentViewInflationDuringRowInflationInflatesCorrectViews() {
- // GIVEN a notification is added and the row is inflating
- mEntryManager.addNotification(mSbn, mRankingMap);
- ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
- NotificationEntry.class);
- verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
- NotificationEntry entry = entryCaptor.getValue();
-
- // WHEN we try to bind a content view
- mRowContentBindStage.getStageParams(entry).requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
- mRowContentBindStage.requestRebind(entry, null);
-
- waitForInflation();
-
- // THEN the notification has its row and all relevant content views inflated
- assertNotNull(entry.getRow());
- assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
- assertNotNull(entry.getRow().getPrivateLayout().getHeadsUpChild());
- }
-
- /**
- * Wait for inflation to finish.
- *
- * A few things to note
- * 1) Row inflation is done via {@link AsyncLayoutInflater} on its own background thread that
- * calls back to main thread which is why we wait on main thread.
- * 2) Row *content* inflation is done on the {@link FakeExecutor} we pass in in this test class
- * so we control when that work is done. The callback is still always on the main thread.
- */
- private void waitForInflation() {
- mHandler.postDelayed(TIMEOUT_RUNNABLE, TIMEOUT_TIME);
- final CountDownLatch latch = new CountDownLatch(1);
- NotificationEntryListener inflationListener = new NotificationEntryListener() {
- @Override
- public void onEntryInflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onEntryReinflated(NotificationEntry entry) {
- latch.countDown();
- }
-
- @Override
- public void onInflationError(StatusBarNotification notification, Exception exception) {
- latch.countDown();
- }
- };
- mEntryManager.addNotificationEntryListener(inflationListener);
- while (latch.getCount() != 0) {
- mBgExecutor.runAllReady();
- TestableLooper.get(this).processMessages(1);
- }
- mHandler.removeCallbacks(TIMEOUT_RUNNABLE);
- mEntryManager.removeNotificationEntryListener(inflationListener);
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index e9d8f58..f57c409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -72,6 +72,7 @@
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
@@ -83,7 +84,6 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.wmshell.BubblesManager;
@@ -156,13 +156,13 @@
mGutsManager = new NotificationGutsManager(mContext,
() -> Optional.of(mCentralSurfaces), mHandler, mHandler, mAccessibilityManager,
- mHighPriorityProvider, mINotificationManager, mNotificationEntryManager,
+ mHighPriorityProvider, mINotificationManager,
mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
mShadeController, mock(DumpManager.class));
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
- mCheckSaveListener, mOnSettingsClickListener);
+ mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index c199147..9bcea10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -75,7 +76,6 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ZenModeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b83743c..37a48937 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -59,6 +59,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
@@ -72,7 +73,6 @@
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 272ef3d..d2680eb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 7b7a48b..57fb976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
import com.android.systemui.statusbar.VibratorHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c6fb0ce..2a3509c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -109,6 +109,8 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -119,7 +121,6 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
@@ -127,7 +128,6 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -218,7 +218,6 @@
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
- @Mock private NotificationFilter mNotificationFilter;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -240,7 +239,6 @@
@Mock private AutoHideController mAutoHideController;
@Mock private StatusBarWindowController mStatusBarWindowController;
@Mock private StatusBarWindowStateController mStatusBarWindowStateController;
- @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@Mock private UserSwitcherController mUserSwitcherController;
@Mock private Bubbles mBubbles;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -281,7 +279,6 @@
@Mock private OperatorNameViewController mOperatorNameViewController;
@Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
@Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
@Mock private NotifLiveDataStore mNotifLiveDataStore;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
@@ -296,7 +293,6 @@
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
IPowerManager powerManagerService = mock(IPowerManager.class);
IThermalService thermalService = mock(IThermalService.class);
@@ -308,7 +304,6 @@
mPowerManager,
mDreamManager,
mAmbientDisplayConfiguration,
- mNotificationFilter,
mStatusBarStateController,
mKeyguardStateController,
mBatteryController,
@@ -408,7 +403,6 @@
mNotificationGutsManager,
notificationLogger,
mNotificationInterruptStateProvider,
- mNotificationViewHierarchyManager,
new PanelExpansionStateManager(),
mKeyguardViewMediator,
new DisplayMetrics(),
@@ -470,7 +464,6 @@
mWallpaperManager,
Optional.of(mStartingSurface),
mActivityLaunchAnimator,
- mNotifPipelineFlags,
mJankMonitor,
mDeviceStateManager,
mWiredChargingRippleController, mDreamManager);
@@ -657,7 +650,6 @@
public void testShouldHeadsUp_nonSuppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
@@ -681,7 +673,6 @@
public void testShouldHeadsUp_suppressedGroupSummary() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a")
@@ -705,7 +696,6 @@
public void testShouldHeadsUp_suppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -727,7 +717,6 @@
public void testShouldHeadsUp_noSuppressedHeadsUp() throws Exception {
when(mPowerManager.isScreenOn()).thenReturn(true);
when(mHeadsUpManager.isSnoozed(anyString())).thenReturn(false);
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
Notification n = new Notification.Builder(getContext(), "a").build();
@@ -1039,7 +1028,6 @@
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter filter,
StatusBarStateController controller,
KeyguardStateController keyguardStateController,
BatteryController batteryController,
@@ -1053,7 +1041,6 @@
powerManager,
dreamManager,
ambientDisplayConfiguration,
- filter,
batteryController,
controller,
keyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
index 0531bc7..c0243dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone
+import android.graphics.Color
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.WindowInsetsController
@@ -56,6 +57,7 @@
@Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
@Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider
private lateinit var calculator: LetterboxAppearanceCalculator
@@ -63,8 +65,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
- calculator = LetterboxAppearanceCalculator(lightBarController, dumpManager)
+ calculator =
+ LetterboxAppearanceCalculator(
+ lightBarController, dumpManager, letterboxBackgroundProvider)
calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
+ whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK)
+ whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false)
}
@Test
@@ -100,6 +106,23 @@
}
@Test
+ fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() {
+ whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true)
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ expect
+ .that(letterboxAppearance.appearance)
+ .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
+ expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
+ }
+
+ @Test
fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
new file mode 100644
index 0000000..44325dd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.phone
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import android.view.IWindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxBackgroundProviderTest : SysuiTestCase() {
+
+ private val fakeSystemClock = FakeSystemClock()
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Mock private lateinit var windowManager: IWindowManager
+ @Mock private lateinit var dumpManager: DumpManager
+
+ private lateinit var provider: LetterboxBackgroundProvider
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ provider = LetterboxBackgroundProvider(windowManager, fakeExecutor, dumpManager)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_defaultValue_returnsBlack() {
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterOnStart_executorNotDone_returnsDefaultValue() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+ provider.start()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.BLACK)
+ }
+
+ @Test
+ fun letterboxBackgroundColor_afterOnStart_executorDone_returnsValueFromWindowManager() {
+ whenever(windowManager.letterboxBackgroundColorInArgb).thenReturn(Color.RED)
+
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.letterboxBackgroundColor).isEqualTo(Color.RED)
+ }
+
+ @Test
+ fun isLetterboxBackgroundMultiColored_defaultValue_returnsFalse() {
+ assertThat(provider.isLetterboxBackgroundMultiColored).isEqualTo(false)
+ }
+ @Test
+ fun isLetterboxBackgroundMultiColored_afterOnStart_executorNotDone_returnsDefaultValue() {
+ whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+ provider.start()
+
+ assertThat(provider.isLetterboxBackgroundMultiColored).isFalse()
+ }
+
+ @Test
+ fun isBackgroundMultiColored_afterOnStart_executorDone_returnsValueFromWindowManager() {
+ whenever(windowManager.isLetterboxBackgroundMultiColored).thenReturn(true)
+
+ provider.start()
+ fakeExecutor.runAllReady()
+
+ assertThat(provider.isLetterboxBackgroundMultiColored).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 2ff6dd4..086e5df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -153,6 +153,106 @@
assertTrue(iconContainer.hasOverflow())
}
+ @Test
+ fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 1,
+ /* speedBumpIndex= */ 0,
+ /* iconAppearAmount= */ 1f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertTrue(forceOverflow);
+ }
+
+ @Test
+ fun shouldForceOverflow_moreThanMaxVisible_true() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 10,
+ /* speedBumpIndex= */ 11,
+ /* iconAppearAmount= */ 0f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertTrue(forceOverflow);
+ }
+
+ @Test
+ fun shouldForceOverflow_belowSpeedBumpAndLessThanMaxVisible_false() {
+ val forceOverflow = iconContainer.shouldForceOverflow(
+ /* i= */ 0,
+ /* speedBumpIndex= */ 11,
+ /* iconAppearAmount= */ 0f,
+ /* maxVisibleIcons= */ 5
+ )
+ assertFalse(forceOverflow);
+ }
+
+ @Test
+ fun isOverflowing_lastChildXLessThanLayoutEnd_false() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 0f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertFalse(isOverflowing)
+ }
+
+
+ @Test
+ fun isOverflowing_lastChildXEqualToLayoutEnd_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 10f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ true,
+ /* translationX= */ 20f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXLessThanDotX_false() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 0f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertFalse(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXGreaterThanDotX_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 20f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
+ @Test
+ fun isOverflowing_notLastChildXEqualToDotX_true() {
+ val isOverflowing = iconContainer.isOverflowing(
+ /* isLastChild= */ false,
+ /* translationX= */ 8f,
+ /* layoutEnd= */ 10f,
+ /* iconSize= */ 2f,
+ )
+ assertTrue(isOverflowing)
+ }
+
private fun mockStatusBarIcon() : StatusBarIconView {
val iconView = mock(StatusBarIconView::class.java)
whenever(iconView.width).thenReturn(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 79fce82..2dcdcfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -201,12 +202,12 @@
}
@Test
- public void onPanelExpansionChanged_propagatesToBouncer_evenAfterHidden() {
+ public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
mStatusBarKeyguardViewManager.hide(0, 0);
when(mBouncer.inTransit()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mBouncer).setExpansion(eq(EXPANSION_EVENT.getFraction()));
+ verify(mBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
}
@Test
@@ -248,6 +249,23 @@
}
@Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() {
+ // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+ // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+ // which would mistakenly cause the bouncer to show briefly before its visibility
+ // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+ // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 7046150..c896c0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowViewController;
+import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 4b7b650..eef43bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -74,16 +75,17 @@
@RunWithLooper()
public class StatusBarNotificationPresenterTest extends SysuiTestCase {
private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
- private NotificationInterruptStateProvider mNotificationInterruptStateProvider =
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider =
mock(NotificationInterruptStateProvider.class);
private NotificationInterruptSuppressor mInterruptSuppressor;
private CommandQueue mCommandQueue;
private FakeMetricsLogger mMetricsLogger;
- private ShadeController mShadeController = mock(ShadeController.class);
- private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
- private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
- private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
- private InitController mInitController = new InitController();
+ private final ShadeController mShadeController = mock(ShadeController.class);
+ private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+ private final KeyguardStateController mKeyguardStateController =
+ mock(KeyguardStateController.class);
+ private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
+ private final InitController mInitController = new InitController();
@Before
public void setup() {
@@ -114,13 +116,11 @@
mock(ActivityStarter.class),
stackScrollLayoutController,
mock(DozeScrimController.class),
- mock(ScrimController.class),
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
mKeyguardStateController,
mock(KeyguardIndicationController.class),
mCentralSurfaces,
- mock(ShadeControllerImpl.class),
mock(LockscreenShadeTransitionController.class),
mCommandQueue,
mock(NotificationLockscreenUserManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 1fc1473..23b1404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -32,6 +32,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -56,7 +57,7 @@
public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
@Mock private NotificationEntryManager mEntryManager;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
- @Mock private ShadeController mShadeController;
+ @Mock private com.android.systemui.shade.ShadeController mShadeController;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private SysuiStatusBarStateController mStatusBarStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 1a8ffc1..4c8599d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,11 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -25,6 +30,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.animation.Animator;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.Context;
@@ -130,7 +136,8 @@
}
@Test
- public void testDisableSystemInfo() {
+ public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
+ when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -143,6 +150,98 @@
}
@Test
+ public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
+ // GIVEN the status bar hides the system info via disable flags, while there is no event
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+ when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
+ fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+
+ // WHEN the disable flags are cleared during a system event animation
+ when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM);
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ // THEN the view is made visible again, but still low alpha
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
+
+ // WHEN the system event animation finishes
+ when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
+ Animator anim = fragment.onSystemEventAnimationFinish(false);
+ anim.start();
+ processAllMessages();
+ anim.end();
+
+ // THEN the system info is full alpha
+ assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+ }
+
+ @Test
+ public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
+ // GIVEN the status bar hides the system info via disable flags, while there is no event
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+ when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
+ fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+
+ // WHEN the system event animation finishes
+ when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
+ Animator anim = fragment.onSystemEventAnimationFinish(false);
+ anim.start();
+ processAllMessages();
+ anim.end();
+
+ // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is
+ // still set)
+ assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
+ }
+
+
+ @Test
+ public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
+ // GIVEN the status bar is not disabled
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+ when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
+ // WHEN the system event animation begins
+ Animator anim = fragment.onSystemEventAnimationBegin();
+ anim.start();
+ processAllMessages();
+ anim.end();
+
+ // THEN the system info is visible but alpha 0
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
+ }
+
+ @Test
+ public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
+ // GIVEN the status bar is not disabled
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+ when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
+ // WHEN the system event animation begins
+ Animator anim = fragment.onSystemEventAnimationBegin();
+ anim.start();
+ processAllMessages();
+ anim.end();
+
+ // THEN the system info is visible but alpha 0
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
+
+ // WHEN the system event animation finishes
+ when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
+ anim = fragment.onSystemEventAnimationFinish(false);
+ anim.start();
+ processAllMessages();
+ anim.end();
+
+ // THEN the syste info is full alpha and VISIBLE
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+ assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+ }
+
+ @Test
public void testDisableNotifications() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
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
new file mode 100644
index 0000000..515a7c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.NetworkCapabilities
+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.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@OptIn(InternalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConnectivityInfoProcessorTest : SysuiTestCase() {
+
+ private val statusBarPipelineFlags = mock<StatusBarPipelineFlags>()
+
+ @Before
+ fun setUp() {
+ whenever(statusBarPipelineFlags.isNewPipelineEnabled()).thenReturn(true)
+ }
+
+ @Test
+ fun collectorInfoUpdated_processedInfoAlsoUpdated() = runBlocking {
+ // GIVEN a processor hooked up to a collector
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val collector = FakeConnectivityInfoCollector()
+ val processor = ConnectivityInfoProcessor(
+ collector,
+ context,
+ scope,
+ statusBarPipelineFlags,
+ )
+
+ var mostRecentValue: ProcessedConnectivityInfo? = null
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ processor.processedInfoFlow.collect {
+ mostRecentValue = it
+ }
+ }
+
+ // WHEN the collector emits a value
+ val networkCapabilityInfo = mapOf(
+ 10 to NetworkCapabilityInfo(mock(), NetworkCapabilities.Builder().build())
+ )
+ collector.emitValue(RawConnectivityInfo(networkCapabilityInfo))
+ // Because our job uses [CoroutineStart.UNDISPATCHED], it executes in the same thread as
+ // this test. So, our test needs to yield to let the job run.
+ // Note: Once we upgrade our Kotlin coroutines testing library, we won't need this.
+ yield()
+
+ // THEN the processor receives it
+ assertThat(mostRecentValue?.networkCapabilityInfo).isEqualTo(networkCapabilityInfo)
+
+ job.cancel()
+ scope.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
new file mode 100644
index 0000000..2915ae8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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 androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.LogcatEchoTracker
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+
+@SmallTest
+class ConnectivityPipelineLoggerTest : SysuiTestCase() {
+ private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+ .create("buffer", 10)
+ private val logger = ConnectivityPipelineLogger(buffer)
+
+ @Test
+ fun testLogNetworkCapsChange_bufferHasInfo() {
+ logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ val expectedNetId = NET_1_ID.toString()
+ val expectedCaps = NET_1_CAPS.toString()
+
+ assertThat(actualString).contains(expectedNetId)
+ assertThat(actualString).contains(expectedCaps)
+ }
+
+ @Test
+ fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
+ logger.logOnLost(NET_1)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ val expectedNetId = NET_1_ID.toString()
+
+ assertThat(actualString).contains(expectedNetId)
+ }
+
+ private val NET_1_ID = 100
+ private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
+ Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt
new file mode 100644
index 0000000..710e5f6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/FakeConnectivityInfoCollector.kt
@@ -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.
+ */
+
+package com.android.systemui.statusbar.pipeline
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A test-friendly implementation of [ConnectivityInfoCollector] that just emits whatever value it
+ * receives in [emitValue].
+ */
+class FakeConnectivityInfoCollector : ConnectivityInfoCollector {
+ private val _rawConnectivityInfoFlow = MutableStateFlow(RawConnectivityInfo())
+ override val rawConnectivityInfoFlow = _rawConnectivityInfoFlow.asStateFlow()
+
+ suspend fun emitValue(value: RawConnectivityInfo) {
+ _rawConnectivityInfoFlow.emit(value)
+ }
+}
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/repository/NetworkCapabilitiesRepoTest.kt
new file mode 100644
index 0000000..40f8fbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
@@ -0,0 +1,252 @@
+/*
+ * 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.repository
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+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.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+// TODO(b/240619365): Update this test to use `runTest` when we update the testing library
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NetworkCapabilitiesRepoTest : SysuiTestCase() {
+ @Mock private lateinit var connectivityManager: ConnectivityManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testOnCapabilitiesChanged_oneNewNetwork_networkStored() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN a new network is added
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN it is emitted from the flow
+ assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnCapabilitiesChanged_twoNewNetworks_bothStored() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN two new networks are added
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects 2 networks
+ assertThat(currentMap[NET_1_ID]?.network).isEqualTo(NET_1)
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_1_CAPS)
+ assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+ assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnCapabilitesChanged_newCapabilitiesForExistingNetwork_areCaptured() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN a network is added, and then its capabilities are changed
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_1, NET_2_CAPS)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects the new capabilities
+ assertThat(currentMap[NET_1_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnLost_networkIsRemoved() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN two new networks are added, and one is removed
+ callback.onCapabilitiesChanged(NET_1, NET_1_CAPS)
+ callback.onCapabilitiesChanged(NET_2, NET_2_CAPS)
+ callback.onLost(NET_1)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow reflects only the remaining network
+ assertThat(currentMap[NET_1_ID]).isNull()
+ assertThat(currentMap[NET_2_ID]?.network).isEqualTo(NET_2)
+ assertThat(currentMap[NET_2_ID]?.capabilities).isEqualTo(NET_2_CAPS)
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ @Test
+ fun testOnLost_noNetworks_doesNotCrash() = runBlocking {
+ // GIVEN a repo hooked up to [ConnectivityManager]
+ val scope = CoroutineScope(Dispatchers.Unconfined)
+ val repo = NetworkCapabilitiesRepo(
+ connectivityManager = connectivityManager,
+ scope = scope,
+ logger = logger,
+ )
+
+ val job = launch(start = CoroutineStart.UNDISPATCHED) {
+ repo.dataStream.collect {
+ }
+ }
+
+ val callback: NetworkCallback = withArgCaptor {
+ verify(connectivityManager)
+ .registerNetworkCallback(any(NetworkRequest::class.java), capture())
+ }
+
+ // WHEN no networks are added, and one is removed
+ callback.onLost(NET_1)
+
+ val currentMap = repo.dataStream.value
+
+ // THEN the current state of the flow shows no networks
+ assertThat(currentMap).isEmpty()
+
+ job.cancel()
+ scope.cancel()
+ }
+
+ private val NET_1_ID = 100
+ private val NET_1 = mock<Network>().also {
+ whenever(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_2_ID = 200
+ private val NET_2 = mock<Network>().also {
+ whenever(it.getNetId()).thenReturn(NET_2_ID)
+ }
+
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_VALIDATED)
+ .build()
+
+ private val NET_2_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_VALIDATED)
+ .build()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 0e25d24..a7ff59c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,12 +51,12 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
-import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.After;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
new file mode 100644
index 0000000..f51f783
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.unfold
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.unfold.util.FoldableDeviceStates
+import com.android.systemui.unfold.util.FoldableTestUtils
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.settings.GlobalSettings
+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.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class FoldAodAnimationControllerTest : SysuiTestCase() {
+
+ @Mock lateinit var deviceStateManager: DeviceStateManager
+
+ @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+
+ @Mock lateinit var globalSettings: GlobalSettings
+
+ @Mock lateinit var latencyTracker: LatencyTracker
+
+ @Mock lateinit var centralSurfaces: CentralSurfaces
+
+ @Mock lateinit var lightRevealScrim: LightRevealScrim
+
+ @Mock lateinit var notificationPanelViewController: NotificationPanelViewController
+
+ @Mock lateinit var viewGroup: ViewGroup
+
+ @Mock lateinit var viewTreeObserver: ViewTreeObserver
+
+ @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+ private lateinit var deviceStates: FoldableDeviceStates
+
+ private lateinit var testableLooper: TestableLooper
+
+ lateinit var foldAodAnimationController: FoldAodAnimationController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ foldAodAnimationController =
+ FoldAodAnimationController(
+ Handler(testableLooper.looper),
+ context.mainExecutor,
+ context,
+ deviceStateManager,
+ wakefulnessLifecycle,
+ globalSettings,
+ latencyTracker,
+ )
+ .apply { initialize(centralSurfaces, lightRevealScrim) }
+ deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+ whenever(notificationPanelViewController.view).thenReturn(viewGroup)
+ whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
+ whenever(wakefulnessLifecycle.lastSleepReason)
+ .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+ whenever(centralSurfaces.notificationPanelViewController)
+ .thenReturn(notificationPanelViewController)
+ whenever(notificationPanelViewController.startFoldToAodAnimation(any(), any(), any()))
+ .then {
+ val onActionStarted = it.arguments[0] as Runnable
+ onActionStarted.run()
+ }
+ verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+
+ foldAodAnimationController.setIsDozing(dozing = true)
+ setAodEnabled(enabled = true)
+ sendFoldEvent(folded = false)
+ }
+
+ @Test
+ fun onFolded_aodDisabled_doesNotLogLatency() {
+ setAodEnabled(enabled = false)
+
+ fold()
+ simulateScreenTurningOn()
+
+ verifyNoMoreInteractions(latencyTracker)
+ }
+
+ @Test
+ fun onFolded_aodEnabled_logsLatency() {
+ setAodEnabled(enabled = true)
+
+ fold()
+ simulateScreenTurningOn()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionEnd(any())
+ }
+
+ @Test
+ fun onFolded_animationCancelled_doesNotLogLatency() {
+ setAodEnabled(enabled = true)
+
+ fold()
+ foldAodAnimationController.onScreenTurningOn({})
+ foldAodAnimationController.onStartedWakingUp()
+ testableLooper.processAllMessages()
+
+ verify(latencyTracker).onActionStart(any())
+ verify(latencyTracker).onActionCancel(any())
+ }
+
+ private fun simulateScreenTurningOn() {
+ foldAodAnimationController.onScreenTurningOn({})
+ foldAodAnimationController.onScreenTurnedOn()
+ testableLooper.processAllMessages()
+ }
+
+ private fun fold() = sendFoldEvent(folded = true)
+
+ private fun setAodEnabled(enabled: Boolean) =
+ foldAodAnimationController.onAlwaysOnChanged(alwaysOn = enabled)
+
+ private fun sendFoldEvent(folded: Boolean) {
+ val state = if (folded) deviceStates.folded else deviceStates.unfolded
+ foldStateListenerCaptor.value.onStateChanged(state)
+ }
+}
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 9866013..fee17c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -43,6 +43,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -85,13 +86,14 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -105,9 +107,7 @@
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -134,6 +134,7 @@
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Ignore;
@@ -143,7 +144,10 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@@ -215,6 +219,8 @@
private BubbleEntry mBubbleEntry2User11;
@Mock
+ private ShellInit mShellInit;
+ @Mock
private ShellController mShellController;
@Mock
private Bubbles.BubbleExpandListener mBubbleExpandListener;
@@ -252,6 +258,8 @@
private TaskViewTransitions mTaskViewTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
+ @Mock
+ private UserManager mUserManager;
private TestableBubblePositioner mPositioner;
@@ -314,12 +322,14 @@
mPositioner.setMaxBubbles(5);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
+ when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
+ Collections.singletonList(mock(UserInfo.class)));
+
TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
mock(PowerManager.class),
mock(IDreamManager.class),
mock(AmbientDisplayConfiguration.class),
- mock(NotificationFilter.class),
mock(StatusBarStateController.class),
mock(KeyguardStateController.class),
mock(BatteryController.class),
@@ -332,6 +342,7 @@
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleController = new TestableBubbleController(
mContext,
+ mShellInit,
mShellController,
mBubbleData,
mFloatingContentCoordinator,
@@ -339,7 +350,7 @@
mStatusBarService,
mWindowManager,
mWindowManagerShellWrapper,
- mock(UserManager.class),
+ mUserManager,
mLauncherApps,
mBubbleLogger,
mTaskStackListener,
@@ -382,6 +393,11 @@
}
@Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
public void instantiateController_registerConfigChangeListener() {
verify(mShellController, times(1)).addConfigurationChangeListener(any());
}
@@ -1025,7 +1041,7 @@
assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
// Switch users
- mBubbleController.onUserChanged(secondUserId);
+ switchUser(secondUserId);
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
// Give this user some bubbles
@@ -1042,6 +1058,41 @@
verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
}
+ @Test
+ public void testOnUserChanged_bubblesRestored() {
+ int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
+ int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
+ // Mock current profile
+ when(mLockscreenUserManager.isCurrentProfile(firstUserId)).thenReturn(true);
+ when(mLockscreenUserManager.isCurrentProfile(secondUserId)).thenReturn(false);
+
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertThat(mBubbleController.hasBubbles()).isTrue();
+ // We start with 1 bubble
+ assertThat(mBubbleData.getBubbles()).hasSize(1);
+
+ // Switch to second user
+ switchUser(secondUserId);
+
+ // Second user has no bubbles
+ assertThat(mBubbleController.hasBubbles()).isFalse();
+
+ // Send bubble update for first user, ensure it does not show up
+ mBubbleController.updateBubble(mBubbleEntry2);
+ assertThat(mBubbleController.hasBubbles()).isFalse();
+
+ // Start returning notif for first user again
+ when(mCommonNotifCollection.getAllNotifs()).thenReturn(Arrays.asList(mRow, mRow2));
+
+ // Switch back to first user
+ switchUser(firstUserId);
+
+ // Check we now have two bubbles, one previous and one new that came in
+ assertThat(mBubbleController.hasBubbles()).isTrue();
+ // Now there are 2 bubbles
+ assertThat(mBubbleData.getBubbles()).hasSize(2);
+ }
+
/**
* Verifies we only load the overflow data once.
*/
@@ -1443,6 +1494,14 @@
.build();
}
+ private void switchUser(int userId) {
+ when(mLockscreenUserManager.isCurrentProfile(anyInt())).thenAnswer(
+ (Answer<Boolean>) invocation -> invocation.<Integer>getArgument(0) == userId);
+ SparseArray<UserInfo> userInfos = new SparseArray<>(1);
+ userInfos.put(userId, mock(UserInfo.class));
+ mBubbleController.onCurrentProfilesChanged(userInfos);
+ mBubbleController.onUserChanged(userId);
+ }
/**
* Asserts that the bubble stack is expanded and also validates the cached state is updated.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index f901c32..880ad187 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -39,6 +39,7 @@
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import java.util.Optional;
@@ -49,6 +50,7 @@
// Let's assume surfaces can be synchronized immediately.
TestableBubbleController(Context context,
+ ShellInit shellInit,
ShellController shellController,
BubbleData data,
FloatingContentCoordinator floatingContentCoordinator,
@@ -69,13 +71,13 @@
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
- super(context, shellController, data, Runnable::run, floatingContentCoordinator,
+ super(context, shellInit, shellController, data, Runnable::run, floatingContentCoordinator,
dataRepository, statusBarService, windowManager, windowManagerShellWrapper,
userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer,
positioner, displayController, oneHandedOptional, dragAndDropController,
shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions,
syncQueue);
setInflateSynchronously(true);
- initialize();
+ onInit();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index d80ea15..9635faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -24,7 +24,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -40,7 +39,6 @@
PowerManager powerManager,
IDreamManager dreamManager,
AmbientDisplayConfiguration ambientDisplayConfiguration,
- NotificationFilter filter,
StatusBarStateController statusBarStateController,
KeyguardStateController keyguardStateController,
BatteryController batteryController,
@@ -53,7 +51,6 @@
powerManager,
dreamManager,
ambientDisplayConfiguration,
- filter,
batteryController,
statusBarStateController,
keyguardStateController,
diff --git a/packages/SystemUI/tests/utils/AndroidManifest.xml b/packages/SystemUI/tests/utils/AndroidManifest.xml
new file mode 100644
index 0000000..cbef5f6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.tests.utils">
+
+
+</manifest>
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/TestableDependency.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
new file mode 100644
index 0000000..0bde5d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleTileRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.data.repository.PeopleTileRepository
+
+/** A fake [PeopleTileRepository] to be used in tests. */
+class FakePeopleTileRepository(
+ private val priorityTiles: List<PeopleTileModel>,
+ private val recentTiles: List<PeopleTileModel>,
+) : PeopleTileRepository {
+ override fun priorityTiles(): List<PeopleTileModel> = priorityTiles
+
+ override fun recentTiles(): List<PeopleTileModel> = recentTiles
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.kt
new file mode 100644
index 0000000..2f81409
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/people/FakePeopleWidgetRepository.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.people
+
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** A fake [PeopleWidgetRepository] to be used in tests. */
+class FakePeopleWidgetRepository(
+ private val onSetWidgetTile: (widgetId: Int, tileKey: PeopleTileKey) -> Unit = { _, _ -> },
+) : PeopleWidgetRepository {
+ override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
+ onSetWidgetTile(widgetId, tileKey)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSession.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/SbnBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeThreadFactory.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/io/FakeBasicFileAttributes.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeProximitySensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeThresholdSensor.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClock.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/util/wakelock/WakeLockFake.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeCastController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeExtensionController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeHotspotController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeLocationController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeTunerService.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeZenModeController.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/os/FakeHandler.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/utils/os/FakeHandler.java
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 0a4ecb2..f903c20 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -298,6 +298,10 @@
// Notify the user to setup their dock
NOTE_SETUP_DOCK = 72;
+ // Inform the user of wifi apm state changes.
+ // Package: android
+ NOTE_WIFI_APM_NOTIFICATION = 73;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 676bde7..d33e7b2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -146,11 +146,8 @@
if (getWindowTokenForUserAndWindowIdLocked(resolvedUserId, windowId) == null) {
return;
}
- final AccessibilityWindowAttributes currentAttrs = mWindowAttributes.get(windowId);
- if (currentAttrs == null || !currentAttrs.equals(attributes)) {
- mWindowAttributes.put(windowId, attributes);
- shouldComputeWindows = findWindowInfoByIdLocked(windowId) != null;
- }
+ mWindowAttributes.put(windowId, attributes);
+ shouldComputeWindows = findWindowInfoByIdLocked(windowId) != null;
}
if (shouldComputeWindows) {
mWindowManagerInternal.computeWindowsForAccessibility(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 803177b..3fa0ab6 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-
import android.accessibilityservice.AccessibilityService;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -34,6 +32,7 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.R;
@@ -392,8 +391,8 @@
private boolean takeScreenshot() {
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS,
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
new Handler(Looper.getMainLooper()), null);
return true;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 3ab873d..e07f412 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -439,6 +439,7 @@
log(MetricsEvent.TYPE_DISMISS);
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
+ callback.requestFallbackFromFillDialog();
}
@Override
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 35e9060..b255188 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -56,6 +56,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -208,6 +209,10 @@
}
@VisibleForTesting
+ VirtualDeviceManagerInternal getLocalServiceInstance() {
+ return mLocalService;
+ }
+
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
VirtualDeviceImpl.PendingTrampolineCallback {
@@ -308,10 +313,11 @@
final long tokenTwo = Binder.clearCallingIdentity();
try {
virtualDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, displayId);
- return displayId;
} finally {
Binder.restoreCallingIdentity(tokenTwo);
}
+ mLocalService.onVirtualDisplayCreated(displayId);
+ return displayId;
}
@Nullable
@@ -378,6 +384,10 @@
}
private final class LocalService extends VirtualDeviceManagerInternal {
+ @GuardedBy("mVirtualDeviceManagerLock")
+ private final ArrayList<VirtualDeviceManagerInternal.VirtualDisplayListener>
+ mVirtualDisplayListeners = new ArrayList<>();
+
@Override
public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) {
synchronized (mVirtualDeviceManagerLock) {
@@ -386,10 +396,30 @@
}
@Override
+ public void onVirtualDisplayCreated(int displayId) {
+ final VirtualDisplayListener[] listeners;
+ synchronized (mVirtualDeviceManagerLock) {
+ listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
+ }
+ mHandler.post(() -> {
+ for (VirtualDisplayListener listener : listeners) {
+ listener.onVirtualDisplayCreated(displayId);
+ }
+ });
+ }
+
+ @Override
public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
+ final VirtualDisplayListener[] listeners;
synchronized (mVirtualDeviceManagerLock) {
((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
+ listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]);
}
+ mHandler.post(() -> {
+ for (VirtualDisplayListener listener : listeners) {
+ listener.onVirtualDisplayRemoved(displayId);
+ }
+ });
}
@Override
@@ -435,6 +465,22 @@
}
return false;
}
+
+ @Override
+ public void registerVirtualDisplayListener(
+ @NonNull VirtualDisplayListener listener) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mVirtualDisplayListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void unregisterVirtualDisplayListener(
+ @NonNull VirtualDisplayListener listener) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mVirtualDisplayListeners.remove(listener);
+ }
+ }
}
private static final class PendingTrampolineMap {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c5b5fb9..002aa77 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4413,7 +4413,7 @@
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
thread.scheduleCreateService(r, r.serviceInfo,
- mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
+ null /* compatInfo (unused but need to keep method signature) */,
app.mState.getReportedProcState());
r.postNotification();
created = true;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9f0aeec..5729a06 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4980,7 +4980,6 @@
PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
try {
thread.scheduleCreateBackupAgent(backupTarget.appInfo,
- compatibilityInfoForPackage(backupTarget.appInfo),
backupTarget.backupMode, backupTarget.userId, backupTarget.operationType);
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
@@ -12900,8 +12899,7 @@
if (thread != null) {
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
try {
- thread.scheduleCreateBackupAgent(app,
- compatibilityInfoForPackage(app), backupMode, targetUserId,
+ thread.scheduleCreateBackupAgent(app, backupMode, targetUserId,
operationType);
} catch (RemoteException e) {
// Will time out on the backup manager side
@@ -13027,8 +13025,7 @@
final IApplicationThread thread = proc.getThread();
if (thread != null) {
try {
- thread.scheduleDestroyBackupAgent(appInfo,
- compatibilityInfoForPackage(appInfo), userId);
+ thread.scheduleDestroyBackupAgent(appInfo, userId);
} catch (Exception e) {
Slog.e(TAG, "Exception when unbinding backup agent:");
e.printStackTrace();
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
index 98ad81d..98129ed 100644
--- a/services/core/java/com/android/server/am/AssistDataRequester.java
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -23,6 +23,8 @@
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.IActivityTaskManager;
@@ -41,6 +43,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Helper class to asynchronously fetch the assist data and screenshot from the current running
@@ -141,50 +144,35 @@
}
/**
- * Request that autofill data be loaded asynchronously. The resulting data will be provided
- * through the {@link AssistDataRequesterCallbacks}.
- *
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int,
- * String, boolean)}.
- */
- public void requestAutofillData(List<IBinder> activityTokens, int callingUid,
- String callingPackage) {
- requestData(activityTokens, true /* requestAutofillData */,
- true /* fetchData */, false /* fetchScreenshot */,
- true /* fetchStructure */, true /* allowFetchData */,
- false /* allowFetchScreenshot */, false /* ignoreTopActivityCheck */, callingUid,
- callingPackage);
- }
-
- /**
* Request that assist data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int,
- * String, boolean)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, boolean,
+ * int, String, String)}.
*/
- public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+ public void requestAssistData(@NonNull List<IBinder> activityTokens, final boolean fetchData,
final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
- int callingUid, String callingPackage) {
+ int callingUid, @NonNull String callingPackage,
+ @Nullable String callingAttributionTag) {
requestAssistData(activityTokens, fetchData, fetchScreenshot, true /* fetchStructure */,
allowFetchData, allowFetchScreenshot, false /* ignoreTopActivityCheck */,
- callingUid, callingPackage);
+ callingUid, callingPackage, callingAttributionTag);
}
/**
* Request that assist data be loaded asynchronously. The resulting data will be provided
* through the {@link AssistDataRequesterCallbacks}.
*
- * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, int,
- * String, boolean)}.
+ * See {@link #requestData(List, boolean, boolean, boolean, boolean, boolean, boolean, boolean,
+ * int, String, String)}.
*/
- public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+ public void requestAssistData(@NonNull List<IBinder> activityTokens, final boolean fetchData,
final boolean fetchScreenshot, final boolean fetchStructure, boolean allowFetchData,
boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
- String callingPackage) {
+ @NonNull String callingPackage, @Nullable String callingAttributionTag) {
requestData(activityTokens, false /* requestAutofillData */, fetchData, fetchScreenshot,
fetchStructure, allowFetchData, allowFetchScreenshot, ignoreTopActivityCheck,
- callingUid, callingPackage);
+ callingUid, callingPackage, callingAttributionTag);
}
/**
@@ -208,14 +196,22 @@
* requester is allowed to fetch the assist screenshot
* @param ignoreTopActivityCheck overrides the check for whether the activity is in focus when
* making the request. Used when passing an activity from Recents.
+ * @param callingUid the uid of the real caller
+ * @param callingPackage the package name of the real caller
+ * @param callingAttributionTag The {@link Context#createAttributionContext attribution tag}
+ * of the calling context or {@code null} for default attribution
*/
- private void requestData(List<IBinder> activityTokens, final boolean requestAutofillData,
- final boolean fetchData, final boolean fetchScreenshot, final boolean fetchStructure,
- boolean allowFetchData, boolean allowFetchScreenshot, boolean ignoreTopActivityCheck,
- int callingUid, String callingPackage) {
+ private void requestData(@NonNull List<IBinder> activityTokens,
+ final boolean requestAutofillData, final boolean fetchData,
+ final boolean fetchScreenshot, final boolean fetchStructure, boolean allowFetchData,
+ boolean allowFetchScreenshot, boolean ignoreTopActivityCheck, int callingUid,
+ @NonNull String callingPackage, @Nullable String callingAttributionTag) {
// TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
// then no assist data is requested for any of the other activities
+ Objects.requireNonNull(activityTokens);
+ Objects.requireNonNull(callingPackage);
+
// Early exit if there are no activity to fetch for
if (activityTokens.isEmpty()) {
// No activities, just dispatch request-complete
@@ -241,8 +237,9 @@
mAssistScreenshot.clear();
if (fetchData) {
- if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage)
- == MODE_ALLOWED && allowFetchData) {
+ if (mAppOpsManager.noteOpNoThrow(mRequestStructureAppOps, callingUid,
+ callingPackage, callingAttributionTag, /* message */ null) == MODE_ALLOWED
+ && allowFetchData) {
final int numActivities = activityTokens.size();
for (int i = 0; i < numActivities; i++) {
IBinder topActivity = activityTokens.get(i);
@@ -291,8 +288,9 @@
}
if (fetchScreenshot) {
- if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
- == MODE_ALLOWED && allowFetchScreenshot) {
+ if (mAppOpsManager.noteOpNoThrow(mRequestScreenshotAppOps, callingUid,
+ callingPackage, callingAttributionTag, /* message */ null) == MODE_ALLOWED
+ && allowFetchScreenshot) {
try {
MetricsLogger.count(mContext, "assist_with_screen", 1);
mPendingScreenshotCount++;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a238705..768fdfd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -755,6 +755,7 @@
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
final BatteryUsageStatsQuery querySinceReset =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.build();
@@ -763,6 +764,7 @@
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.powerProfileModeledOnly()
@@ -779,6 +781,7 @@
final long sessionEnd = mStats.getStartClockTime();
final BatteryUsageStatsQuery queryBeforeReset =
new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
.includeProcessStateData()
.includeVirtualUids()
.aggregateSnapshots(sessionStart, sessionEnd)
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 0cd1bfa..6d520c3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -361,7 +361,7 @@
mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
- mService.compatibilityInfoForPackage(r.curReceiver.applicationInfo),
+ 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,
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index e8c1b54..51cb987 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -15,6 +15,7 @@
*/
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
@@ -25,6 +26,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerProto;
import android.app.IUidObserver;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -81,7 +83,9 @@
@NonNull String callingPackage, int callingUid) {
synchronized (mLock) {
mUidObservers.register(observer, new UidObserverRegistration(callingUid,
- callingPackage, which, cutpoint));
+ callingPackage, which, cutpoint,
+ ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
+ == PackageManager.PERMISSION_GRANTED));
}
}
@@ -252,6 +256,11 @@
final ChangeRecord item = mActiveUidChanges[j];
final long start = SystemClock.uptimeMillis();
final int change = item.change;
+ // Does the user have permission? Don't send a non user UID change otherwise
+ if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
+ && !reg.mCanInteractAcrossUsers) {
+ continue;
+ }
if (change == UidRecord.CHANGE_PROCSTATE
&& (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
// No-op common case: no significant change, the observer is not
@@ -437,6 +446,7 @@
private final String mPkg;
private final int mWhich;
private final int mCutpoint;
+ private final boolean mCanInteractAcrossUsers;
/**
* Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -467,11 +477,13 @@
ActivityManagerProto.UID_OBSERVER_FLAG_PROC_OOM_ADJ,
};
- UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint) {
+ UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
+ boolean canInteractAcrossUsers) {
this.mUid = uid;
this.mPkg = pkg;
this.mWhich = which;
this.mCutpoint = cutpoint;
+ this.mCanInteractAcrossUsers = canInteractAcrossUsers;
mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
? new SparseIntArray() : null;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e4224ed..0c0ae7d 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1872,15 +1872,21 @@
/** Called on handler thread */
@VisibleForTesting
void dispatchUserSwitchComplete(@UserIdInt int userId) {
+ final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+ t.traceBegin("dispatchUserSwitchComplete-" + userId);
mInjector.getWindowManager().setSwitchingUser(false);
final int observerCount = mUserSwitchObservers.beginBroadcast();
for (int i = 0; i < observerCount; i++) {
try {
+ t.traceBegin("onUserSwitchComplete-" + userId + " #" + i + " "
+ + mUserSwitchObservers.getBroadcastCookie(i));
mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(userId);
+ t.traceEnd();
} catch (RemoteException e) {
}
}
mUserSwitchObservers.finishBroadcast();
+ t.traceEnd();
}
private void dispatchLockedBootComplete(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d38cd8e..7bef6d5 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -7287,6 +7287,7 @@
return true;
}
+ // TODO (b/240617242) add overload for checkOp to support attribution tags
if (isCheckOp) {
return !perUserExclusions.includes(packageName);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 18d5194..5a20db3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -134,6 +134,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -175,6 +176,7 @@
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
import com.android.server.pm.UserManagerInternal;
@@ -367,6 +369,7 @@
private static final int MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR = 47;
private static final int MSG_ROTATION_UPDATE = 48;
private static final int MSG_FOLD_UPDATE = 49;
+ private static final int MSG_RESET_SPATIALIZER = 50;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -374,6 +377,7 @@
private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
private static final int MSG_INIT_STREAMS_VOLUMES = 101;
private static final int MSG_INIT_SPATIALIZER = 102;
+
// end of messages handled under wakelock
// retry delay in case of failure to indicate system ready to AudioFlinger
@@ -1572,6 +1576,7 @@
}
synchronized (mAudioPolicies) {
+ ArrayList<AudioPolicyProxy> invalidProxies = new ArrayList<>();
for (AudioPolicyProxy policy : mAudioPolicies.values()) {
final int status = policy.connectMixes();
if (status != AudioSystem.SUCCESS) {
@@ -1579,7 +1584,7 @@
Log.e(TAG, "onAudioServerDied: error "
+ AudioSystem.audioSystemErrorToString(status)
+ " when connecting mixes for policy " + policy.toLogFriendlyString());
- policy.release();
+ invalidProxies.add(policy);
} else {
final int deviceAffinitiesStatus = policy.setupDeviceAffinities();
if (deviceAffinitiesStatus != AudioSystem.SUCCESS) {
@@ -1587,10 +1592,12 @@
+ AudioSystem.audioSystemErrorToString(deviceAffinitiesStatus)
+ " when connecting device affinities for policy "
+ policy.toLogFriendlyString());
- policy.release();
+ invalidProxies.add(policy);
}
}
}
+ invalidProxies.forEach((policy) -> policy.release());
+
}
// Restore capture policies
@@ -3588,7 +3595,8 @@
+ "), do not change associated stream volume");
continue;
}
- setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
+ setStreamVolume(groupedStream, index, flags, /*device*/ null,
+ callingPackage, callingPackage,
attributionTag, Binder.getCallingUid(), true /*hasModifyAudioSettings*/);
}
}
@@ -3631,15 +3639,73 @@
return AudioSystem.getMinVolumeIndexForAttributes(attr);
}
+ /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
+ * Part of service interface, check permissions and parameters here */
+ public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
+ @NonNull String callingPackage, @Nullable String attributionTag) {
+ enforceModifyAudioRoutingPermission();
+ Objects.requireNonNull(vi);
+ Objects.requireNonNull(ada);
+ Objects.requireNonNull(callingPackage);
+ if (!vi.hasStreamType()) {
+ Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
+ return;
+ }
+ int index = vi.getVolumeIndex();
+ if (index == VolumeInfo.INDEX_NOT_SET) {
+ throw new IllegalArgumentException("changing device volume requires a volume index");
+ }
+
+ if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
+ || vi.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
+ // assume index meant to be in stream type range, validate
+ if ((index * 10) < mStreamStates[vi.getStreamType()].getMinIndex()
+ || (index * 10) > mStreamStates[vi.getStreamType()].getMaxIndex()) {
+ throw new IllegalArgumentException("invalid volume index " + index
+ + " not between min/max for stream " + vi.getStreamType());
+ }
+ } else {
+ // check if index needs to be rescaled
+ final int min = (mStreamStates[vi.getStreamType()].getMinIndex() + 5) / 10;
+ final int max = (mStreamStates[vi.getStreamType()].getMaxIndex() + 5) / 10;
+ if (vi.getMinVolumeIndex() != min || vi.getMaxVolumeIndex() != max) {
+ index = rescaleIndex(index,
+ /*srcMin*/ vi.getMinVolumeIndex(), /*srcMax*/ vi.getMaxVolumeIndex(),
+ /*dstMin*/ min, /*dstMax*/ max);
+ }
+ }
+ setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
+ ada, callingPackage, attributionTag);
+ }
+
/** Retain API for unsupported app usage */
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
- setStreamVolumeWithAttribution(streamType, index, flags, callingPackage, null);
+ setStreamVolumeWithAttribution(streamType, index, flags,
+ callingPackage, /*attributionTag*/ null);
}
/** @see AudioManager#setStreamVolume(int, int, int)
* Part of service interface, check permissions here */
public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
String callingPackage, String attributionTag) {
+ setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null,
+ callingPackage, attributionTag);
+ }
+
+ /**
+ * Internal method for a stream type volume change. Can be used to change the volume on a
+ * given device only
+ * @param streamType the stream type whose volume is to be changed
+ * @param index the volume index
+ * @param flags options for volume handling
+ * @param device null when controlling volume for the current routing, otherwise the device
+ * for which volume is being changed
+ * @param callingPackage client side-provided package name of caller, not to be trusted
+ * @param attributionTag client side-provided attribution name, not to be trusted
+ */
+ protected void setStreamVolumeWithAttributionInt(int streamType, int index, int flags,
+ @Nullable AudioDeviceAttributes device,
+ String callingPackage, String attributionTag) {
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
+ " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage);
@@ -3662,10 +3728,14 @@
return;
}
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
- index/*val1*/, flags/*val2*/, callingPackage));
- setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
- attributionTag, Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
+ final AudioEventLogger.Event event = (device == null)
+ ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+ index/*val1*/, flags/*val2*/, callingPackage)
+ : new DeviceVolumeEvent(streamType, index, device, callingPackage);
+ sVolumeLogger.log(event);
+ setStreamVolume(streamType, index, flags, device,
+ callingPackage, callingPackage, attributionTag,
+ Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
}
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND)
@@ -3874,14 +3944,12 @@
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
case AudioSystem.MODE_NORMAL:
+ case AudioSystem.MODE_CALL_SCREENING:
+ case AudioSystem.MODE_CALL_REDIRECT:
+ case AudioSystem.MODE_COMMUNICATION_REDIRECT:
break;
- case AudioSystem.MODE_RINGTONE:
- // not changing anything for ringtone
- return;
- case AudioSystem.MODE_CURRENT:
- case AudioSystem.MODE_INVALID:
default:
- // don't know what to do in this case, better bail
+ // no-op is enough for all other values
return;
}
@@ -3904,11 +3972,41 @@
}
}
- private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
- String caller, String attributionTag, int uid,
+ private void setLeAudioVolumeOnModeUpdate(int mode) {
+ switch (mode) {
+ case AudioSystem.MODE_IN_COMMUNICATION:
+ case AudioSystem.MODE_IN_CALL:
+ case AudioSystem.MODE_NORMAL:
+ case AudioSystem.MODE_CALL_SCREENING:
+ case AudioSystem.MODE_CALL_REDIRECT:
+ case AudioSystem.MODE_COMMUNICATION_REDIRECT:
+ break;
+ default:
+ // no-op is enough for all other values
+ return;
+ }
+
+ int streamType = getBluetoothContextualVolumeStream(mode);
+
+ // Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
+ // (See AudioDeviceBroker#createBtDeviceInfo())
+ int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
+ int maxIndex = mStreamStates[streamType].getMaxIndex();
+
+ if (DEBUG_VOL) {
+ Log.d(TAG, "setLeAudioVolumeOnModeUpdate postSetLeAudioVolumeIndex index="
+ + index + " maxIndex=" + maxIndex + " streamType=" + streamType);
+ }
+ mDeviceBroker.postSetLeAudioVolumeIndex(index, maxIndex, streamType);
+ }
+
+ private void setStreamVolume(int streamType, int index, int flags,
+ @Nullable AudioDeviceAttributes ada,
+ String callingPackage, String caller, String attributionTag, int uid,
boolean hasModifyAudioSettings) {
if (DEBUG_VOL) {
Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
+ + ", dev=" + ada
+ ", calling=" + callingPackage + ")");
}
if (mUseFixedVolume) {
@@ -3919,7 +4017,9 @@
int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];
- final int device = getDeviceForStream(streamType);
+ final int device = (ada == null)
+ ? getDeviceForStream(streamType)
+ : ada.getInternalType();
int oldIndex;
// skip a2dp absolute volume control request when the device
@@ -5276,6 +5376,10 @@
// change of mode may require volume to be re-applied on some devices
updateAbsVolumeMultiModeDevices(previousMode, mode);
+ // Forcefully set LE audio volume as a workaround, since the value of 'device'
+ // is not DEVICE_OUT_BLE_* even when BLE is connected.
+ setLeAudioVolumeOnModeUpdate(mode);
+
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
mDeviceBroker.postSetModeOwnerPid(pid, mode);
@@ -5423,7 +5527,8 @@
throw new SecurityException("Should only be called from system process");
}
- setStreamVolume(streamType, index, flags, packageName, packageName, null, uid,
+ setStreamVolume(streamType, index, flags, /*device*/ null,
+ packageName, packageName, null, uid,
hasAudioSettingsPermission(uid, pid));
}
@@ -7566,7 +7671,7 @@
&& !isFullyMuted()) {
index = 1;
}
- AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
+ mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
}
// must be called while synchronized VolumeStreamState.class
@@ -8202,6 +8307,10 @@
onPersistSpatialAudioDeviceSettings();
break;
+ case MSG_RESET_SPATIALIZER:
+ mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
+ break;
+
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
@@ -9180,6 +9289,16 @@
/*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
}
+ /**
+ * post a message to schedule a reset of the spatializer state
+ */
+ void postResetSpatializer() {
+ sendMsg(mAudioHandler,
+ MSG_RESET_SPATIALIZER,
+ SENDMSG_REPLACE,
+ /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
+ }
+
void onInitSpatializer() {
final String settings = mSettings.getSecureStringForUser(mContentResolver,
Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
@@ -11035,6 +11154,16 @@
mPlaybackMonitor.playerEvent(piid, event, eventValue, Binder.getCallingUid());
}
+ /**
+ * Update event for port id
+ * @param portId Port id to update
+ * @param event The new event for the given port
+ * @param extras Bundle of extra values to describe the event
+ */
+ public void portEvent(int portId, int event, @Nullable PersistableBundle extras) {
+ mPlaybackMonitor.portEvent(portId, event, extras, Binder.getCallingUid());
+ }
+
public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio) {
mPlaybackMonitor.playerHasOpPlayAudio(piid, hasOpPlayAudio, Binder.getCallingUid());
}
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 3225274..b5835ce 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,7 +16,9 @@
package com.android.server.audio;
+import android.annotation.NonNull;
import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaMetrics;
@@ -145,6 +147,43 @@
}
}
+ static final class DeviceVolumeEvent extends AudioEventLogger.Event {
+ final int mStream;
+ final int mVolIndex;
+ final String mDeviceNativeType;
+ final String mDeviceAddress;
+ final String mCaller;
+
+ DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device,
+ String callingPackage) {
+ mStream = streamType;
+ mVolIndex = index;
+ mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType());
+ mDeviceAddress = device.getAddress();
+ mCaller = callingPackage;
+ // log metrics
+ new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT)
+ .set(MediaMetrics.Property.EVENT, "setDeviceVolume")
+ .set(MediaMetrics.Property.STREAM_TYPE,
+ AudioSystem.streamToString(mStream))
+ .set(MediaMetrics.Property.INDEX, mVolIndex)
+ .set(MediaMetrics.Property.DEVICE, mDeviceNativeType)
+ .set(MediaMetrics.Property.ADDRESS, mDeviceAddress)
+ .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
+ .record();
+ }
+
+ @Override
+ public String eventToString() {
+ return new StringBuilder("setDeviceVolume(stream:")
+ .append(AudioSystem.streamToString(mStream))
+ .append(" index:").append(mVolIndex)
+ .append(" device:").append(mDeviceNativeType)
+ .append(" addr:").append(mDeviceAddress)
+ .append(") from ").append(mCaller).toString();
+ }
+ }
+
final static class VolumeEvent extends AudioEventLogger.Event {
static final int VOL_ADJUST_SUGG_VOL = 0;
static final int VOL_ADJUST_STREAM_VOL = 1;
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 758a4ec..c3754eb 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -387,6 +387,17 @@
}
/**
+ * Same as {@link AudioSystem#setStreamVolumeIndexAS(int, int, int)}
+ * @param stream
+ * @param index
+ * @param device
+ * @return
+ */
+ public int setStreamVolumeIndexAS(int stream, int index, int device) {
+ return AudioSystem.setStreamVolumeIndexAS(stream, index, device);
+ }
+
+ /**
* Same as {@link AudioSystem#setPhoneState(int, int)}
* @param state
* @param uid
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index d9037fe..66a4527 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -53,6 +53,8 @@
private static final String TAG = "AS.BtHelper";
+ private static final int SOURCE_CODEC_TYPE_OPUS = 6; // TODO remove in U
+
private final @NonNull AudioDeviceBroker mDeviceBroker;
BtHelper(@NonNull AudioDeviceBroker broker) {
@@ -911,6 +913,8 @@
return "ENCODING_APTX_HD";
case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
return "ENCODING_LDAC";
+ case SOURCE_CODEC_TYPE_OPUS: // TODO update in U
+ return "ENCODING_OPUS";
default:
return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 02036db..9ff72d3 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -16,6 +16,14 @@
package com.android.server.audio;
+import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
+import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_MASTER;
+import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_PLAYBACK_RESTRICTED;
+import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_MUTED;
+import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_VOLUME;
+import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
+import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -24,6 +32,7 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
import android.media.AudioSystem;
import android.media.IPlaybackConfigDispatcher;
import android.media.PlayerBase;
@@ -33,7 +42,9 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
@@ -349,6 +360,56 @@
}
}
+ /**
+ * Update event for port
+ * @param portId Port id to update
+ * @param event The new port event
+ * @param extras The values associated with this event
+ * @param binderUid Calling binder uid
+ */
+ public void portEvent(int portId, int event, @Nullable PersistableBundle extras,
+ int binderUid) {
+ if (!UserHandle.isCore(binderUid)) {
+ Log.e(TAG, "Forbidden operation from uid " + binderUid);
+ return;
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)",
+ portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
+ }
+
+ synchronized (mPlayerLock) {
+ int piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
+ if (piid == PLAYER_PIID_INVALID) {
+ if (DEBUG) {
+ Log.v(TAG, "No piid assigned for invalid/internal port id " + portId);
+ }
+ return;
+ }
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ if (DEBUG) {
+ Log.v(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid);
+ }
+ return;
+ }
+
+ if (apc.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ // FIXME SoundPool not ready for state reporting
+ return;
+ }
+
+ if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED) {
+ mEventHandler.sendMessage(
+ mEventHandler.obtainMessage(MSG_L_UPDATE_PLAYER_MUTED_EVENT, piid,
+ portId,
+ extras));
+ }
+ }
+ }
+
public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
// no check on UID yet because this is only for logging at the moment
sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
@@ -1062,18 +1123,32 @@
@Override
public String eventToString() {
- if (mEvent == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
- return AudioPlaybackConfiguration.toLogFriendlyPlayerState(mEvent) + " portId:"
- + mEventValue + " mapped to player piid:" + mPlayerIId;
- }
-
StringBuilder builder = new StringBuilder("player piid:").append(mPlayerIId).append(
- " state:")
+ " event:")
.append(AudioPlaybackConfiguration.toLogFriendlyPlayerState(mEvent));
- if (mEventValue != 0) {
- builder.append(" DeviceId:").append(mEventValue);
+
+ switch (mEvent) {
+ case AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID:
+ return AudioPlaybackConfiguration.toLogFriendlyPlayerState(mEvent) + " portId:"
+ + mEventValue + " mapped to player piid:" + mPlayerIId;
+ case AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID:
+ if (mEventValue != 0) {
+ builder.append(" deviceId:").append(mEventValue);
+ }
+ return builder.toString();
+ case AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED:
+ builder.append(" muteFromMasterMute:").append(
+ (mEventValue & PLAYER_MUTE_MASTER) != 0);
+ builder.append(" muteFromStreamVolume:").append(
+ (mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0);
+ builder.append(" muteFromStreamMute:").append(
+ (mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0);
+ builder.append(" muteFromPlaybackRestricted:").append(
+ (mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0);
+ return builder.toString();
+ default:
+ return builder.toString();
}
- return builder.toString();
}
}
@@ -1323,11 +1398,21 @@
private static final int MSG_L_UPDATE_PORT_EVENT = 2;
/**
+ * event for player getting muted
+ * args:
+ * msg.arg1: piid
+ * msg.arg2: port id
+ * msg.obj: extras describing the mute reason
+ * type: PersistableBundle
+ */
+ private static final int MSG_L_UPDATE_PLAYER_MUTED_EVENT = 3;
+
+ /**
* clear all ports assigned to a given piid
* args:
* msg.arg1: the piid
*/
- private static final int MSG_L_CLEAR_PORTS_FOR_PIID = 3;
+ private static final int MSG_L_CLEAR_PORTS_FOR_PIID = 4;
private void initEventHandler() {
mEventThread = new HandlerThread(TAG);
@@ -1349,6 +1434,28 @@
mPortIdToPiid.put(/*portId*/msg.arg1, /*piid*/msg.arg2);
}
break;
+ case MSG_L_UPDATE_PLAYER_MUTED_EVENT:
+ // TODO: replace PersistableBundle with own struct
+ PersistableBundle extras = (PersistableBundle) msg.obj;
+ if (extras == null) {
+ Log.w(TAG, "Received mute event with no extras");
+ break;
+ }
+ @PlayerMuteEvent int eventValue = extras.getInt(EXTRA_PLAYER_EVENT_MUTE);
+
+ synchronized (mPlayerLock) {
+ int piid = msg.arg1;
+
+ sEventLogger.log(
+ new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
+
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null || !apc.handleMutedEvent(eventValue)) {
+ break; // do not dispatch
+ }
+ dispatchPlaybackChange(/* iplayerReleased= */false);
+ }
+ break;
case MSG_L_CLEAR_PORTS_FOR_PIID:
int piid = msg.arg1;
if (piid == AudioPlaybackConfiguration.PLAYER_PIID_INVALID) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 1def72b..e27fb11 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -389,10 +389,10 @@
try {
mSpat.setLevel(level);
} catch (RemoteException e) {
- Log.e(TAG, "Can't set spatializer level", e);
- mState = STATE_NOT_SUPPORTED;
- mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
- enabled = false;
+ Log.e(TAG, "onRoutingUpdated() Can't set spatializer level", e);
+ // try to recover by resetting the native spatializer state
+ postReset();
+ return;
}
}
@@ -404,6 +404,10 @@
}
}
+ private void postReset() {
+ mAudioService.postResetSpatializer();
+ }
+
//------------------------------------------------------
// spatializer callback from native
private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
@@ -751,33 +755,29 @@
if (enabled) {
throw (new IllegalStateException("Can't enable when uninitialized"));
}
- return;
+ break;
case STATE_NOT_SUPPORTED:
if (enabled) {
Log.e(TAG, "Can't enable when unsupported");
}
- return;
+ break;
case STATE_DISABLED_UNAVAILABLE:
case STATE_DISABLED_AVAILABLE:
if (enabled) {
createSpat();
onRoutingUpdated();
- break;
- } else {
- // already in disabled state
- return;
- }
+ // onRoutingUpdated() can update the "enabled" state based on context
+ // and will call setDispatchFeatureEnabledState().
+ } // else { nothing to do as already disabled }
+ break;
case STATE_ENABLED_UNAVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (!enabled) {
releaseSpat();
- break;
- } else {
- // already in enabled state
- return;
- }
+ setDispatchFeatureEnabledState(false, "setSpatializerEnabledInt");
+ } // else { nothing to do as already enabled }
+ break;
}
- setDispatchFeatureEnabledState(enabled, "setSpatializerEnabledInt");
}
synchronized int getCapableImmersiveAudioLevel() {
@@ -1161,8 +1161,11 @@
case STATE_DISABLED_AVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (mSpat == null) {
- throw (new IllegalStateException(
- "null Spatializer when calling " + funcName));
+ // try to recover by resetting the native spatializer state
+ Log.e(TAG, "checkSpatForHeadTracking(): "
+ + "native spatializer should not be null in state: " + mState);
+ postReset();
+ return false;
}
break;
}
@@ -1252,8 +1255,8 @@
case STATE_DISABLED_AVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (mSpat == null) {
- throw (new IllegalStateException(
- "null Spatializer for setParameter for key:" + key));
+ Log.e(TAG, "setParameter(" + key + "): null spatializer in state: " + mState);
+ return;
}
break;
}
@@ -1276,8 +1279,8 @@
case STATE_DISABLED_AVAILABLE:
case STATE_ENABLED_AVAILABLE:
if (mSpat == null) {
- throw (new IllegalStateException(
- "null Spatializer for getParameter for key:" + key));
+ Log.e(TAG, "getParameter(" + key + "): null spatializer in state: " + mState);
+ return;
}
break;
}
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 23f0ffb..351a1e9 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -39,7 +39,6 @@
public class BroadcastRadioService extends SystemService {
private static final String TAG = "BcRadioSrv";
- private static final boolean DEBUG = false;
private final ServiceImpl mServiceImpl = new ServiceImpl();
@@ -74,6 +73,7 @@
@Override
public List<RadioManager.ModuleProperties> listModules() {
+ Slog.v(TAG, "Listing HIDL modules");
enforcePolicyAccess();
List<RadioManager.ModuleProperties> modules = new ArrayList<>();
modules.addAll(mV1Modules);
@@ -84,7 +84,7 @@
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
boolean withAudio, ITunerCallback callback) throws RemoteException {
- if (DEBUG) Slog.i(TAG, "Opening module " + moduleId);
+ Slog.v(TAG, "Opening module " + moduleId);
enforcePolicyAccess();
if (callback == null) {
throw new IllegalArgumentException("Callback must not be empty");
@@ -101,16 +101,14 @@
@Override
public ICloseHandle addAnnouncementListener(int[] enabledTypes,
IAnnouncementListener listener) {
- if (DEBUG) {
- Slog.i(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
- }
+ Slog.v(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
Objects.requireNonNull(enabledTypes);
Objects.requireNonNull(listener);
enforcePolicyAccess();
synchronized (mLock) {
if (!mHal2.hasAnyModules()) {
- Slog.i(TAG, "There are no HAL 2.x modules registered");
+ Slog.i(TAG, "There are no HAL 2.0 modules registered");
return new AnnouncementAggregator(listener, mLock);
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 5c07f76..534e828 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -132,6 +132,7 @@
}
public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
+ Slog.v(TAG, "List HIDL 2.0 modules");
synchronized (mLock) {
return mModules.values().stream().map(module -> module.mProperties)
.collect(Collectors.toList());
@@ -152,10 +153,11 @@
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
+ Slog.v(TAG, "Open HIDL 2.0 session");
Objects.requireNonNull(callback);
if (!withAudio) {
- throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.x");
+ throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.0");
}
RadioModule module = null;
@@ -175,6 +177,7 @@
public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@NonNull IAnnouncementListener listener) {
+ Slog.v(TAG, "Add announcementListener");
AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
boolean anySupported = false;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index ef7f4c9..aeaa678 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -142,8 +142,12 @@
public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName,
Object lock) {
try {
+ Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
IBroadcastRadio service = IBroadcastRadio.getService(fqName);
- if (service == null) return null;
+ if (service == null) {
+ Slog.w(TAG, "No service found for fqName " + fqName);
+ return null;
+ }
Mutable<AmFmRegionConfig> amfmConfig = new Mutable<>();
service.getAmFmRegionConfig(false, (result, config) -> {
@@ -160,7 +164,7 @@
return new RadioModule(service, prop, lock);
} catch (RemoteException ex) {
- Slog.e(TAG, "failed to load module " + fqName, ex);
+ Slog.e(TAG, "Failed to load module " + fqName, ex);
return null;
}
}
@@ -171,6 +175,7 @@
public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
throws RemoteException {
+ Slog.i(TAG, "Open TunerSession");
synchronized (mLock) {
if (mHalTunerSession == null) {
Mutable<ITunerSession> hwSession = new Mutable<>();
@@ -201,6 +206,7 @@
// Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
// must be called without mAidlTunerSessions locked because it can call
// onTunerSessionClosed().
+ Slog.i(TAG, "Close TunerSessions");
TunerSession[] tunerSessions;
synchronized (mLock) {
tunerSessions = new TunerSession[mAidlTunerSessions.size()];
@@ -313,7 +319,7 @@
}
onTunerSessionProgramListFilterChanged(null);
if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
- Slog.v(TAG, "closing HAL tuner session");
+ Slog.i(TAG, "Closing HAL tuner session");
try {
mHalTunerSession.close();
} catch (RemoteException ex) {
@@ -365,6 +371,7 @@
public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
+ Slog.i(TAG, "Add AnnouncementListener");
ArrayList<Byte> enabledList = new ArrayList<>();
for (int type : enabledTypes) {
enabledList.add((byte)type);
@@ -401,6 +408,7 @@
}
Bitmap getImage(int id) {
+ Slog.i(TAG, "Get image for id " + id);
if (id == 0) throw new IllegalArgumentException("Image ID is missing");
byte[] rawImage;
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index d476fd6..c13216b 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -27,6 +27,7 @@
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.RemoteException;
+import android.util.Log;
import android.util.MutableBoolean;
import android.util.MutableInt;
import android.util.Slog;
@@ -61,8 +62,13 @@
mLock = Objects.requireNonNull(lock);
}
+ private boolean isDebugEnabled() {
+ return Log.isLoggable(TAG, Log.DEBUG);
+ }
+
@Override
public void close() {
+ if (isDebugEnabled()) Slog.d(TAG, "Close");
close(null);
}
@@ -74,6 +80,7 @@
* @param error Optional error to send to client before session is closed.
*/
public void close(@Nullable Integer error) {
+ if (isDebugEnabled()) Slog.d(TAG, "Close on error " + error);
synchronized (mLock) {
if (mIsClosed) return;
if (error != null) {
@@ -104,7 +111,7 @@
synchronized (mLock) {
checkNotClosedLocked();
mDummyConfig = Objects.requireNonNull(config);
- Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.x");
+ Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
}
}
@@ -137,6 +144,10 @@
@Override
public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+ if (isDebugEnabled()) {
+ Slog.d(TAG, "Step with directionDown " + directionDown
+ + " skipSubChannel " + skipSubChannel);
+ }
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.step(!directionDown);
@@ -146,6 +157,10 @@
@Override
public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+ if (isDebugEnabled()) {
+ Slog.d(TAG, "Scan with directionDown " + directionDown
+ + " skipSubChannel " + skipSubChannel);
+ }
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.scan(!directionDown, skipSubChannel);
@@ -155,6 +170,7 @@
@Override
public void tune(ProgramSelector selector) throws RemoteException {
+ if (isDebugEnabled()) Slog.d(TAG, "Tune with selector " + selector);
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.tune(Convert.programSelectorToHal(selector));
@@ -164,6 +180,7 @@
@Override
public void cancel() {
+ Slog.i(TAG, "Cancel");
synchronized (mLock) {
checkNotClosedLocked();
Utils.maybeRethrow(mHwSession::cancel);
@@ -172,23 +189,25 @@
@Override
public void cancelAnnouncement() {
- Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in 2.x");
+ Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
}
@Override
public Bitmap getImage(int id) {
+ if (isDebugEnabled()) Slog.d(TAG, "Get image for " + id);
return mModule.getImage(id);
}
@Override
public boolean startBackgroundScan() {
- Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x");
+ Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete());
return true;
}
@Override
public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
+ if (isDebugEnabled()) Slog.d(TAG, "start programList updates " + filter);
// If the AIDL client provides a null filter, it wants all updates, so use the most broad
// filter.
if (filter == null) {
@@ -247,6 +266,7 @@
@Override
public void stopProgramListUpdates() throws RemoteException {
+ if (isDebugEnabled()) Slog.d(TAG, "Stop programList updates");
synchronized (mLock) {
checkNotClosedLocked();
mProgramInfoCache = null;
@@ -270,7 +290,7 @@
@Override
public boolean isConfigFlagSet(int flag) {
- Slog.v(TAG, "isConfigFlagSet " + ConfigFlag.toString(flag));
+ if (isDebugEnabled()) Slog.d(TAG, "Is ConfigFlagSet for " + ConfigFlag.toString(flag));
synchronized (mLock) {
checkNotClosedLocked();
@@ -292,7 +312,9 @@
@Override
public void setConfigFlag(int flag, boolean value) throws RemoteException {
- Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
+ if (isDebugEnabled()) {
+ Slog.d(TAG, "Set ConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
+ }
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.setConfigFlag(flag, value);
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index f2b4d42..230bfc5 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.companion.virtual;
+import android.annotation.NonNull;
import android.companion.virtual.IVirtualDevice;
/**
@@ -24,16 +25,41 @@
*/
public abstract class VirtualDeviceManagerInternal {
+ /** Interface to listen to the creation and destruction of virtual displays. */
+ public interface VirtualDisplayListener {
+ /** Notifies that a virtual display was created. */
+ void onVirtualDisplayCreated(int displayId);
+
+ /** Notifies that a virtual display was removed. */
+ void onVirtualDisplayRemoved(int displayId);
+ }
+
+ /** Register a listener for the creation and destruction of virtual displays. */
+ public abstract void registerVirtualDisplayListener(
+ @NonNull VirtualDisplayListener listener);
+
+ /** Unregister a listener for the creation and destruction of virtual displays. */
+ public abstract void unregisterVirtualDisplayListener(
+ @NonNull VirtualDisplayListener listener);
+
+
/**
* Validate the virtual device.
*/
public abstract boolean isValidVirtualDevice(IVirtualDevice virtualDevice);
/**
- * Notify a virtual display is removed.
+ * Notifies that a virtual display is created.
+ *
+ * @param displayId The display id of the created virtual display.
+ */
+ public abstract void onVirtualDisplayCreated(int displayId);
+
+ /**
+ * Notifies that a virtual display is removed.
*
* @param virtualDevice The virtual device where the virtual display located.
- * @param displayId The display id of the removed virtual display.
+ * @param displayId The display id of the removed virtual display.
*/
public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 774fe5b..16a060a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -222,6 +222,11 @@
*/
private static final int VPN_DEFAULT_SCORE = 101;
+ /**
+ * The initial token value of IKE session.
+ */
+ private static final int STARTING_TOKEN = -1;
+
// TODO: create separate trackers for each unique VPN to support
// automated reconnection
@@ -514,12 +519,8 @@
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
@Nullable NetworkProvider provider) {
- return new NetworkAgent(context, looper, logTag, nc, lp, score, config, provider) {
- @Override
- public void onNetworkUnwanted() {
- // We are user controlled, not driven by NetworkRequest.
- }
- };
+ return new VpnNetworkAgentWrapper(
+ context, looper, logTag, nc, lp, score, config, provider);
}
}
@@ -785,7 +786,7 @@
}
}
- private boolean isVpnApp(String packageName) {
+ private static boolean isVpnApp(String packageName) {
return packageName != null && !VpnConfig.LEGACY_VPN.equals(packageName);
}
@@ -1813,7 +1814,7 @@
Log.wtf(TAG, "Failed to add restricted user to owner", e);
}
if (mNetworkAgent != null) {
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
}
}
setVpnForcedLocked(mLockdown);
@@ -1843,7 +1844,7 @@
Log.wtf(TAG, "Failed to remove restricted user to owner", e);
}
if (mNetworkAgent != null) {
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
}
}
setVpnForcedLocked(mLockdown);
@@ -2077,7 +2078,7 @@
return false;
}
boolean success = jniAddAddress(mInterface, address, prefixLength);
- mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ doSendLinkProperties(mNetworkAgent, makeLinkProperties());
return success;
}
@@ -2086,7 +2087,7 @@
return false;
}
boolean success = jniDelAddress(mInterface, address, prefixLength);
- mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ doSendLinkProperties(mNetworkAgent, makeLinkProperties());
return success;
}
@@ -2100,8 +2101,11 @@
// Make defensive copy since the content of array might be altered by the caller.
mConfig.underlyingNetworks =
(networks != null) ? Arrays.copyOf(networks, networks.length) : null;
- mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
- ? Arrays.asList(mConfig.underlyingNetworks) : null);
+ doSetUnderlyingNetworks(
+ mNetworkAgent,
+ (mConfig.underlyingNetworks != null)
+ ? Arrays.asList(mConfig.underlyingNetworks)
+ : null);
return true;
}
@@ -2589,7 +2593,7 @@
}
@Nullable
- protected synchronized NetworkCapabilities getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
+ private synchronized NetworkCapabilities getRedactedNetworkCapabilities(
NetworkCapabilities nc) {
if (nc == null) return null;
return mConnectivityManager.getRedactedNetworkCapabilitiesForPackage(
@@ -2597,8 +2601,7 @@
}
@Nullable
- protected synchronized LinkProperties getRedactedLinkPropertiesOfUnderlyingNetwork(
- LinkProperties lp) {
+ private synchronized LinkProperties getRedactedLinkProperties(LinkProperties lp) {
if (lp == null) return null;
return mConnectivityManager.getRedactedLinkPropertiesForPackage(lp, mOwnerUID, mPackage);
}
@@ -2712,11 +2715,13 @@
private boolean mIsRunning = true;
/**
- * The token used by the primary/current/active IKE session.
+ * The token that identifies the most recently created IKE session.
*
- * <p>This token MUST be updated when the VPN switches to use a new IKE session.
+ * <p>This token is monotonically increasing and will never be reset in the lifetime of this
+ * Ikev2VpnRunner, but it does get reset across runs. It also MUST be accessed on the
+ * executor thread and updated when a new IKE session is created.
*/
- private int mCurrentToken = -1;
+ private int mCurrentToken = STARTING_TOKEN;
@Nullable private IpSecTunnelInterface mTunnelIface;
@Nullable private Network mActiveNetwork;
@@ -2910,7 +2915,7 @@
return; // Link properties are already sent.
} else {
// Underlying networks also set in agentConnect()
- networkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+ doSetUnderlyingNetworks(networkAgent, Collections.singletonList(network));
mNetworkCapabilities =
new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUnderlyingNetworks(Collections.singletonList(network))
@@ -2920,7 +2925,7 @@
lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
}
- networkAgent.sendLinkProperties(lp);
+ doSendLinkProperties(networkAgent, lp);
} catch (Exception e) {
Log.d(TAG, "Error in ChildOpened for token " + token, e);
onSessionLost(token, e);
@@ -2987,7 +2992,7 @@
new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUnderlyingNetworks(Collections.singletonList(network))
.build();
- mNetworkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+ doSetUnderlyingNetworks(mNetworkAgent, Collections.singletonList(network));
}
mTunnelIface.setUnderlyingNetwork(network);
@@ -3208,7 +3213,7 @@
mExecutor.schedule(
() -> {
if (isActiveToken(token)) {
- handleSessionLost(null, network);
+ handleSessionLost(null /* exception */, network);
} else {
Log.d(
TAG,
@@ -3225,7 +3230,7 @@
TimeUnit.MILLISECONDS);
} else {
Log.d(TAG, "Call handleSessionLost for losing network " + network);
- handleSessionLost(null, network);
+ handleSessionLost(null /* exception */, network);
}
}
@@ -3293,70 +3298,69 @@
// already terminated due to other failures.
cancelHandleNetworkLostTimeout();
- synchronized (Vpn.this) {
- String category = null;
- int errorClass = -1;
- int errorCode = -1;
- if (exception instanceof IkeProtocolException) {
- final IkeProtocolException ikeException = (IkeProtocolException) exception;
- category = VpnManager.CATEGORY_EVENT_IKE_ERROR;
- errorCode = ikeException.getErrorType();
+ String category = null;
+ int errorClass = -1;
+ int errorCode = -1;
+ if (exception instanceof IllegalArgumentException) {
+ // Failed to build IKE/ChildSessionParams; fatal profile configuration error
+ markFailedAndDisconnect(exception);
+ return;
+ }
- switch (ikeException.getErrorType()) {
- case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
- case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
- // All the above failures are configuration errors, and are terminal
- errorClass = VpnManager.ERROR_CLASS_NOT_RECOVERABLE;
- break;
- // All other cases possibly recoverable.
- default:
- // All the above failures are configuration errors, and are terminal
- errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
- }
- } else if (exception instanceof IllegalArgumentException) {
- // Failed to build IKE/ChildSessionParams; fatal profile configuration error
- markFailedAndDisconnect(exception);
- return;
- } else if (exception instanceof IkeNetworkLostException) {
- category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
- errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
- errorCode = VpnManager.ERROR_CODE_NETWORK_LOST;
- } else if (exception instanceof IkeNonProtocolException) {
- category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
- errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
- if (exception.getCause() instanceof UnknownHostException) {
- errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
- } else if (exception.getCause() instanceof IkeTimeoutException) {
- errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
- } else if (exception.getCause() instanceof IOException) {
- errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
- }
- } else if (exception != null) {
- Log.wtf(TAG, "onSessionLost: exception = " + exception);
+ if (exception instanceof IkeProtocolException) {
+ final IkeProtocolException ikeException = (IkeProtocolException) exception;
+ category = VpnManager.CATEGORY_EVENT_IKE_ERROR;
+ errorCode = ikeException.getErrorType();
+
+ switch (ikeException.getErrorType()) {
+ case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
+ // All the above failures are configuration errors, and are terminal
+ errorClass = VpnManager.ERROR_CLASS_NOT_RECOVERABLE;
+ break;
+ // All other cases possibly recoverable.
+ default:
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
}
+ } else if (exception instanceof IkeNetworkLostException) {
+ category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
+ errorCode = VpnManager.ERROR_CODE_NETWORK_LOST;
+ } else if (exception instanceof IkeNonProtocolException) {
+ category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
+ if (exception.getCause() instanceof UnknownHostException) {
+ errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
+ } else if (exception.getCause() instanceof IkeTimeoutException) {
+ errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
+ } else if (exception.getCause() instanceof IOException) {
+ errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
+ }
+ } else if (exception != null) {
+ Log.wtf(TAG, "onSessionLost: exception = " + exception);
+ }
+ synchronized (Vpn.this) {
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(category, errorClass, errorCode,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
+ getRedactedNetworkCapabilities(mUnderlyingNetworkCapabilities),
+ getRedactedLinkProperties(mUnderlyingLinkProperties));
}
+ }
- if (errorClass == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
- markFailedAndDisconnect(exception);
- return;
- } else {
- scheduleRetryNewIkeSession();
- }
+ if (errorClass == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+ markFailedAndDisconnect(exception);
+ return;
+ } else {
+ scheduleRetryNewIkeSession();
}
mUnderlyingNetworkCapabilities = null;
@@ -3384,7 +3388,7 @@
null /*gateway*/, null /*iface*/, RTN_UNREACHABLE));
}
if (mNetworkAgent != null) {
- mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ doSendLinkProperties(mNetworkAgent, makeLinkProperties());
}
}
}
@@ -4121,7 +4125,7 @@
.setUids(createUserAndRestrictedProfilesRanges(
mUserId, null /* allowedApplications */, excludedApps))
.build();
- mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
}
}
}
@@ -4198,6 +4202,85 @@
return isCurrentIkev2VpnLocked(packageName) ? makeVpnProfileStateLocked() : null;
}
+ /** Proxy to allow different testing setups */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+ // NetworkAgent#sendLinkProperties can be un-finalized.
+ private static void doSendLinkProperties(
+ @NonNull NetworkAgent agent, @NonNull LinkProperties lp) {
+ if (agent instanceof VpnNetworkAgentWrapper) {
+ ((VpnNetworkAgentWrapper) agent).doSendLinkProperties(lp);
+ } else {
+ agent.sendLinkProperties(lp);
+ }
+ }
+
+ /** Proxy to allow different testing setups */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+ // NetworkAgent#sendNetworkCapabilities can be un-finalized.
+ private static void doSendNetworkCapabilities(
+ @NonNull NetworkAgent agent, @NonNull NetworkCapabilities nc) {
+ if (agent instanceof VpnNetworkAgentWrapper) {
+ ((VpnNetworkAgentWrapper) agent).doSendNetworkCapabilities(nc);
+ } else {
+ agent.sendNetworkCapabilities(nc);
+ }
+ }
+
+ /** Proxy to allow different testing setups */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper and this method when
+ // NetworkAgent#setUnderlyingNetworks can be un-finalized.
+ private static void doSetUnderlyingNetworks(
+ @NonNull NetworkAgent agent, @NonNull List<Network> networks) {
+ if (agent instanceof VpnNetworkAgentWrapper) {
+ ((VpnNetworkAgentWrapper) agent).doSetUnderlyingNetworks(networks);
+ } else {
+ agent.setUnderlyingNetworks(networks);
+ }
+ }
+
+ /**
+ * Proxy to allow testing
+ *
+ * @hide
+ */
+ // TODO: b/240492694 Remove VpnNetworkAgentWrapper when NetworkAgent's methods can be
+ // un-finalized.
+ @VisibleForTesting
+ public static class VpnNetworkAgentWrapper extends NetworkAgent {
+ /** Create an VpnNetworkAgentWrapper */
+ public VpnNetworkAgentWrapper(
+ @NonNull Context context,
+ @NonNull Looper looper,
+ @NonNull String logTag,
+ @NonNull NetworkCapabilities nc,
+ @NonNull LinkProperties lp,
+ @NonNull NetworkScore score,
+ @NonNull NetworkAgentConfig config,
+ @Nullable NetworkProvider provider) {
+ super(context, looper, logTag, nc, lp, score, config, provider);
+ }
+
+ /** Update the LinkProperties */
+ public void doSendLinkProperties(@NonNull LinkProperties lp) {
+ sendLinkProperties(lp);
+ }
+
+ /** Update the NetworkCapabilities */
+ public void doSendNetworkCapabilities(@NonNull NetworkCapabilities nc) {
+ sendNetworkCapabilities(nc);
+ }
+
+ /** Set the underlying networks */
+ public void doSetUnderlyingNetworks(@NonNull List<Network> networks) {
+ setUnderlyingNetworks(networks);
+ }
+
+ @Override
+ public void onNetworkUnwanted() {
+ // We are user controlled, not driven by NetworkRequest.
+ }
+ }
+
/**
* Proxy to allow testing
*
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 114690a..a817cea 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -345,7 +345,9 @@
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
| (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
- ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+ ? BrightnessEvent.FLAG_DOZE_SCALE : 0)
+ | (mCurrentBrightnessMapper.isForIdleMode()
+ ? BrightnessEvent.FLAG_IDLE_CURVE : 0));
}
if (!mAmbientLuxValid) {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 76d71e2..e53aef7 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -30,8 +30,8 @@
import java.io.PrintWriter;
/**
- * Represents a physical display device such as the built-in display
- * an external monitor, or a WiFi display.
+ * Represents a display device such as the built-in display, an external monitor, a WiFi display,
+ * or a {@link android.hardware.display.VirtualDisplay}.
* <p>
* Display devices are guarded by the {@link DisplayManagerService.SyncRoot} lock.
* </p>
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b343480..2dd3864 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2583,8 +2583,8 @@
final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
display, mSyncRoot);
final DisplayPowerController displayPowerController = new DisplayPowerController(
- mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager,
- mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
+ mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
+ mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
() -> handleBrightnessChange(display));
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 4e33fd0..6150b49 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -48,6 +48,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.FloatProperty;
import android.util.Log;
import android.util.MathUtils;
import android.util.MutableFloat;
@@ -58,6 +59,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
@@ -97,7 +99,7 @@
* blocker as long as the display is not ready. So most of the work done here
* does not need to worry about holding a suspend blocker unless it happens
* independently of the display ready signal.
- *
+ *
* For debugging, you can make the color fade and brightness animations run
* slower by changing the "animator duration scale" option in Development Settings.
*/
@@ -256,6 +258,9 @@
// to reach the final state.
private final boolean mBrightnessBucketsInDozeConfig;
+ private final Clock mClock;
+ private final Injector mInjector;
+
// Maximum time a ramp animation can take.
private long mBrightnessRampIncreaseMaxTimeMillis;
private long mBrightnessRampDecreaseMaxTimeMillis;
@@ -495,20 +500,22 @@
/**
* Creates the display power controller.
*/
- public DisplayPowerController(Context context,
+ DisplayPowerController(Context context, Injector injector,
DisplayPowerCallbacks callbacks, Handler handler,
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
Runnable onBrightnessChangeRunnable) {
+
+ mInjector = injector != null ? injector : new Injector();
+ mClock = mInjector.getClock();
mLogicalDisplay = logicalDisplay;
mDisplayId = mLogicalDisplay.getDisplayIdLocked();
- final String displayIdStr = "[" + mDisplayId + "]";
- TAG = "DisplayPowerController" + displayIdStr;
- mSuspendBlockerIdUnfinishedBusiness = displayIdStr + "unfinished business";
- mSuspendBlockerIdOnStateChanged = displayIdStr + "on state changed";
- mSuspendBlockerIdProxPositive = displayIdStr + "prox positive";
- mSuspendBlockerIdProxNegative = displayIdStr + "prox negative";
- mSuspendBlockerIdProxDebounce = displayIdStr + "prox debounce";
+ TAG = "DisplayPowerController[" + mDisplayId + "]";
+ mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
+ mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
+ mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
+ mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId);
+ mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId);
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
@@ -681,7 +688,8 @@
/**
* Get the {@link BrightnessChangeEvent}s for the specified user.
- * @param userId userId to fetch data for
+ *
+ * @param userId userId to fetch data for
* @param includePackage if false will null out the package name in events
*/
@Nullable
@@ -723,10 +731,11 @@
* The controller makes a copy of the provided object and then
* begins adjusting the power state to match what was requested.
*
- * @param request The requested power state.
+ * @param request The requested power state.
* @param waitForNegativeProximity If true, issues a request to wait for
- * negative proximity before turning the screen back on, assuming the screen
- * was turned off by the proximity sensor.
+ * negative proximity before turning the screen back on,
+ * assuming the screen
+ * was turned off by the proximity sensor.
* @return True if display is ready, false if there are important changes that must
* be made asynchronously (such as turning the screen on), in which case the caller
* should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
@@ -834,7 +843,7 @@
synchronized (mLock) {
mStopped = true;
Message msg = mHandler.obtainMessage(MSG_STOP);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
if (mDisplayWhiteBalanceController != null) {
mDisplayWhiteBalanceController.setEnabled(false);
@@ -888,12 +897,12 @@
if (!mStopped && !mPendingUpdatePowerStateLocked) {
mPendingUpdatePowerStateLocked = true;
Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
}
private void initialize(int displayState) {
- mPowerState = new DisplayPowerState(mBlanker,
+ mPowerState = mInjector.getDisplayPowerState(mBlanker,
mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
if (mColorFadeEnabled) {
@@ -908,7 +917,7 @@
mColorFadeOffAnimator.addListener(mAnimatorListener);
}
- mScreenBrightnessRampAnimator = new DualRampAnimator<>(mPowerState,
+ mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState,
DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
mScreenBrightnessRampAnimator.setAnimationTimeLimits(
@@ -1077,13 +1086,16 @@
@Override
public void onAnimationStart(Animator animation) {
}
+
@Override
public void onAnimationEnd(Animator animation) {
sendUpdatePowerState();
}
+
@Override
public void onAnimationRepeat(Animator animation) {
}
+
@Override
public void onAnimationCancel(Animator animation) {
}
@@ -1124,8 +1136,8 @@
mOnProximityNegativeMessages = 0;
final float brightness = mPowerState != null
- ? mPowerState.getScreenBrightness()
- : PowerManager.BRIGHTNESS_MIN;
+ ? mPowerState.getScreenBrightness()
+ : PowerManager.BRIGHTNESS_MIN;
reportStats(brightness);
if (mPowerState != null) {
@@ -1203,7 +1215,7 @@
state = Display.STATE_ON;
break;
}
- assert(state != Display.STATE_UNKNOWN);
+ assert (state != Display.STATE_UNKNOWN);
// Apply the proximity sensor.
if (mProximitySensor != null) {
@@ -1290,16 +1302,16 @@
final boolean autoBrightnessEnabledInDoze =
mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
- && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
- && Float.isNaN(brightnessState)
- && mAutomaticBrightnessController != null;
+ && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
+ && Float.isNaN(brightnessState)
+ && mAutomaticBrightnessController != null;
final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
- && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
final int autoBrightnessState = autoBrightnessEnabled
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: autoBrightnessDisabledDueToDisplayOff
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
- : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
@@ -1501,6 +1513,8 @@
// Animate the screen brightness when the screen is on or dozing.
// Skip the animation when the screen is off or suspended or transition to/from VR.
boolean brightnessAdjusted = false;
+ final boolean brightnessIsTemporary =
+ mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
if (state == Display.STATE_ON) {
@@ -1533,8 +1547,6 @@
// level without it being a noticeable jump since any actual content isn't yet visible.
final boolean isDisplayContentVisible =
mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
- final boolean brightnessIsTemporary =
- mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
// We only want to animate the brightness if it is between 0.0f and 1.0f.
// brightnessState can contain the values -1.0f and NaN, which we do not want to
// animate to. To avoid this, we check the value first.
@@ -1589,9 +1601,9 @@
// AND if we are not in idle screen brightness mode.
if (!brightnessIsTemporary
&& (mAutomaticBrightnessController != null
- && !mAutomaticBrightnessController.isInIdleMode())) {
+ && !mAutomaticBrightnessController.isInIdleMode())) {
if (userInitiatedChange && (mAutomaticBrightnessController == null
- || !mAutomaticBrightnessController.hasValidAmbientLux())) {
+ || !mAutomaticBrightnessController.hasValidAmbientLux())) {
// If we don't have a valid lux reading we can't report a valid
// slider event so notify as if the system changed the brightness.
userInitiatedChange = false;
@@ -1607,7 +1619,8 @@
brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
}
- if (brightnessAdjusted) {
+ // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
+ if (brightnessAdjusted && !brightnessIsTemporary) {
postBrightnessChangeRunnable();
}
@@ -1781,26 +1794,26 @@
boolean changed = false;
changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
- brightness);
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
+ brightness);
changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
- adjustedBrightness);
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
+ adjustedBrightness);
changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
- minBrightness);
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
+ minBrightness);
changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
- maxBrightness);
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
+ maxBrightness);
changed |=
- mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
- mHbmController.getHighBrightnessMode());
+ mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
+ mHbmController.getHighBrightnessMode());
changed |=
- mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
- mHbmController.getTransitionPoint());
+ mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
+ mHbmController.getTransitionPoint());
changed |=
- mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
- mBrightnessThrottler.getBrightnessMaxReason());
+ mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
+ mBrightnessThrottler.getBrightnessMaxReason());
return changed;
}
@@ -1942,7 +1955,7 @@
}
if (!isOff
&& (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF
- || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) {
+ || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON);
if (mPowerState.getColorFadeLevel() == 0.0f) {
blockScreenOn();
@@ -2070,7 +2083,7 @@
} else if (mPowerState.prepareColorFade(mContext,
mColorFadeFadesConfig ?
ColorFade.MODE_FADE :
- ColorFade.MODE_WARM_UP)) {
+ ColorFade.MODE_WARM_UP)) {
mColorFadeOnAnimator.start();
} else {
mColorFadeOnAnimator.end();
@@ -2172,8 +2185,8 @@
mPowerState.dismissColorFadeResources();
} else if (performScreenOffTransition
&& mPowerState.prepareColorFade(mContext,
- mColorFadeFadesConfig ?
- ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN)
+ mColorFadeFadesConfig
+ ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN)
&& mPowerState.getScreenState() != Display.STATE_OFF) {
// Perform the screen off animation.
mColorFadeOffAnimator.start();
@@ -2244,7 +2257,7 @@
if (mProximitySensorEnabled
&& mPendingProximity != PROXIMITY_UNKNOWN
&& mPendingProximityDebounceTime >= 0) {
- final long now = SystemClock.uptimeMillis();
+ final long now = mClock.uptimeMillis();
if (mPendingProximityDebounceTime <= now) {
if (mProximity != mPendingProximity) {
// if the status of the sensor changed, stop ignoring.
@@ -2522,7 +2535,7 @@
pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" +
mCachedBrightnessInfo.hbmTransitionPoint.value);
pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" +
- mCachedBrightnessInfo.brightnessMaxReason .value);
+ mCachedBrightnessInfo.brightnessMaxReason.value);
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2705,7 +2718,7 @@
}
float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
- synchronized(mCachedBrightnessInfo) {
+ synchronized (mCachedBrightnessInfo) {
if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
return;
}
@@ -2739,8 +2752,6 @@
}
}
-
-
private final class DisplayControllerHandler extends Handler {
public DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -2805,7 +2816,7 @@
break;
case MSG_BRIGHTNESS_RAMP_DONE:
- if (mPowerState != null) {
+ if (mPowerState != null) {
final float brightness = mPowerState.getScreenBrightness();
reportStats(brightness);
}
@@ -2822,7 +2833,7 @@
@Override
public void onSensorChanged(SensorEvent event) {
if (mProximitySensorEnabled) {
- final long time = SystemClock.uptimeMillis();
+ final long time = mClock.uptimeMillis();
final float distance = event.values[0];
boolean positive = distance >= 0.0f && distance < mProximityThreshold;
handleProximitySensorEvent(time, positive);
@@ -2891,19 +2902,68 @@
}
}
+ @VisibleForTesting
+ String getSuspendBlockerUnfinishedBusinessId(int displayId) {
+ return "[" + displayId + "]unfinished business";
+ }
+
+ String getSuspendBlockerOnStateChangedId(int displayId) {
+ return "[" + displayId + "]on state changed";
+ }
+
+ String getSuspendBlockerProxPositiveId(int displayId) {
+ return "[" + displayId + "]prox positive";
+ }
+
+ String getSuspendBlockerProxNegativeId(int displayId) {
+ return "[" + displayId + "]prox negative";
+ }
+
+ @VisibleForTesting
+ String getSuspendBlockerProxDebounceId(int displayId) {
+ return "[" + displayId + "]prox debounce";
+ }
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ Clock getClock() {
+ return SystemClock::uptimeMillis;
+ }
+
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+ }
+
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return new DualRampAnimator(dps, firstProperty, secondProperty);
+ }
+ }
+
static class CachedBrightnessInfo {
public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
public MutableFloat adjustedBrightness =
- new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
public MutableFloat brightnessMin =
- new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
public MutableFloat brightnessMax =
- new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
public MutableFloat hbmTransitionPoint =
- new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
+ new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
public MutableInt brightnessMaxReason =
- new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+ new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
public boolean checkAndSetFloat(MutableFloat mf, float f) {
if (mf.value != f) {
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 6698612..d831dbd 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -27,8 +27,9 @@
public final class BrightnessEvent {
public static final int FLAG_RBC = 0x1;
public static final int FLAG_INVALID_LUX = 0x2;
- public static final int FLAG_DOZE_SCALE = 0x3;
- public static final int FLAG_USER_SET = 0x4;
+ public static final int FLAG_DOZE_SCALE = 0x4;
+ public static final int FLAG_USER_SET = 0x8;
+ public static final int FLAG_IDLE_CURVE = 0x16;
private BrightnessReason mReason = new BrightnessReason();
private int mDisplayId;
@@ -257,6 +258,7 @@
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_DOZE_SCALE) != 0 ? "doze_scale " : "")
+ + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "");
}
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 6c75dbf..eb73234 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -223,6 +223,17 @@
}
@AnyThread
+ boolean updateEditorToolType(int toolType) {
+ try {
+ mTarget.updateEditorToolType(toolType);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ return false;
+ }
+ return true;
+ }
+
+ @AnyThread
void changeInputMethodSubtype(InputMethodSubtype subtype) {
try {
mTarget.changeInputMethodSubtype(subtype);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index bbf9410..d48acb1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -132,6 +132,7 @@
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputDevice;
+import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
@@ -1765,8 +1766,7 @@
mLastSwitchUserId = userId;
// mSettings should be created before buildInputMethodListLocked
- mSettings = new InputMethodSettings(
- mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady);
+ mSettings = new InputMethodSettings(mContext, mMethodMap, userId, !mSystemReady);
updateCurrentProfileIds();
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
@@ -2017,49 +2017,6 @@
}
}
- // ---------------------------------------------------------------------------------------
- // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
- // 1) it comes from the system process
- // 2) the calling process' user id is identical to the current user id IMMS thinks.
- @GuardedBy("ImfLock.class")
- private boolean calledFromValidUserLocked() {
- final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
- if (DEBUG) {
- Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
- + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
- + " calling userId = " + userId + ", foreground user id = "
- + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
- + InputMethodUtils.getApiCallStack());
- }
- if (uid == Process.SYSTEM_UID) {
- return true;
- }
- if (userId == mSettings.getCurrentUserId()) {
- return true;
- }
-
- // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
- // foreground user, not for the user of that process. Accordingly InputMethodManagerService
- // must not manage background users' states in any functions.
- // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
- // by a token.
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- == PackageManager.PERMISSION_GRANTED) {
- if (DEBUG) {
- Slog.d(TAG, "--- Access granted because the calling process has "
- + "the INTERACT_ACROSS_USERS_FULL permission");
- }
- return true;
- }
- // TODO(b/34886274): The semantics of this verification is actually not well-defined.
- Slog.w(TAG, "--- IPC called from background users. Ignore. callers="
- + Debug.getCallers(10));
- return false;
- }
-
-
/**
* Returns true iff the caller is identified to be the current input method with the token.
* @param token The window token given to the input method when it was started.
@@ -2138,8 +2095,8 @@
//TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
//TODO(b/210039666): use cache.
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
- mContext.getContentResolver(), methodMap, userId, true);
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
+ userId, true);
final InputMethodInfo imi = methodMap.get(settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting();
}
@@ -2171,8 +2128,8 @@
return mSettings.getEnabledInputMethodListLocked();
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
- mContext.getContentResolver(), methodMap, userId, true);
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
+ true);
return settings.getEnabledInputMethodListLocked();
}
@@ -2237,17 +2194,17 @@
return Collections.emptyList();
}
return mSettings.getEnabledInputMethodSubtypeListLocked(
- mContext, imi, allowsImplicitlySelectedSubtypes);
+ imi, allowsImplicitlySelectedSubtypes);
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
final InputMethodInfo imi = methodMap.get(imiId);
if (imi == null) {
return Collections.emptyList();
}
- final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
- mContext.getContentResolver(), methodMap, userId, true);
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
+ true);
return settings.getEnabledInputMethodSubtypeListLocked(
- mContext, imi, allowsImplicitlySelectedSubtypes);
+ imi, allowsImplicitlySelectedSubtypes);
}
/**
@@ -3066,7 +3023,7 @@
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = imes.get(i);
final List<InputMethodSubtype> subtypes =
- mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
+ mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
final int subtypeCount = subtypes.size();
if (subtypeCount == 0) {
++nonAuxCount;
@@ -3201,8 +3158,12 @@
// Used to load label
final CharSequence title = mRes.getText(
com.android.internal.R.string.select_input_method);
+ final int currentUserId = mSettings.getCurrentUserId();
+ final Context userAwareContext = mContext.getUserId() == currentUserId
+ ? mContext
+ : mContext.createContextAsUser(UserHandle.of(currentUserId), 0 /* flags */);
final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
- mContext, imi, mCurrentSubtype);
+ userAwareContext, imi, mCurrentSubtype);
mImeSwitcherNotification.setContentTitle(title)
.setContentText(summary)
.setContentIntent(mImeSwitchPendingIntent);
@@ -3360,7 +3321,8 @@
@Override
public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ int lastClickTooType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
int uid = Binder.getCallingUid();
ImeTracing.getInstance().triggerManagerServiceDump(
@@ -3372,7 +3334,8 @@
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
- return showCurrentInputLocked(windowToken, flags, resultReceiver, reason);
+ return showCurrentInputLocked(
+ windowToken, flags, lastClickTooType, resultReceiver, reason);
} finally {
Binder.restoreCallingIdentity(ident);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3444,8 +3407,15 @@
}
@GuardedBy("ImfLock.class")
- boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ boolean showCurrentInputLocked(IBinder windowToken, int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ return showCurrentInputLocked(
+ windowToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean showCurrentInputLocked(IBinder windowToken, int flags, int lastClickToolType,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
mShowRequested = true;
if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) {
return false;
@@ -3474,6 +3444,10 @@
+ ", " + showFlags + ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
+
+ if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+ curMethod.updateEditorToolType(lastClickToolType);
+ }
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
onShowHideSoftInputRequested(true /* show */, windowToken, reason);
@@ -4155,15 +4129,19 @@
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(
- mContext.getResources(), mContext.getContentResolver(), methodMap,
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
userId, false);
return settings.getLastInputMethodSubtypeLocked();
}
}
@Override
- public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ }
+
// By this IPC call, only a process which shares the same uid with the IME can add
// additional input method subtypes to the IME.
if (TextUtils.isEmpty(imiId) || subtypes == null) return;
@@ -4177,42 +4155,32 @@
}
}
synchronized (ImfLock.class) {
- if (!calledFromValidUserLocked()) {
- return;
- }
if (!mSystemReady) {
return;
}
- final InputMethodInfo imi = mMethodMap.get(imiId);
- if (imi == null) return;
- final String[] packageInfos;
- try {
- packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get package infos");
+
+ if (mSettings.getCurrentUserId() == userId) {
+ if (!mSettings.setAdditionalInputMethodSubtypes(imiId, toBeAdded,
+ mAdditionalSubtypeMap, mIPackageManager)) {
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
return;
}
- if (packageInfos != null) {
- final int packageNum = packageInfos.length;
- for (int i = 0; i < packageNum; ++i) {
- if (packageInfos[i].equals(imi.getPackageName())) {
- if (subtypes.length > 0) {
- mAdditionalSubtypeMap.put(imi.getId(), toBeAdded);
- } else {
- mAdditionalSubtypeMap.remove(imi.getId());
- }
- AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
- mSettings.getCurrentUserId());
- final long ident = Binder.clearCallingIdentity();
- try {
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return;
- }
- }
- }
+
+ final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
+ userId, false);
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
+ mIPackageManager);
}
}
@@ -4362,7 +4330,8 @@
im.registerInputDeviceListener(new InputManager.InputDeviceListener() {
@Override
public void onInputDeviceAdded(int deviceId) {
- if (isStylusDevice(im.getInputDevice(deviceId))) {
+ InputDevice device = im.getInputDevice(deviceId);
+ if (device != null && isStylusDevice(device)) {
add(deviceId);
}
}
@@ -4374,7 +4343,11 @@
@Override
public void onInputDeviceChanged(int deviceId) {
- if (isStylusDevice(im.getInputDevice(deviceId))) {
+ InputDevice device = im.getInputDevice(deviceId);
+ if (device == null) {
+ return;
+ }
+ if (isStylusDevice(device)) {
add(deviceId);
} else {
remove(deviceId);
@@ -4473,8 +4446,8 @@
if (!im.isInputDeviceEnabled(id)) {
continue;
}
- InputDevice inputDevice = im.getInputDevice(id);
- if (isStylusDevice(inputDevice)) {
+ InputDevice device = im.getInputDevice(id);
+ if (device != null && isStylusDevice(device)) {
stylusIds.add(id);
}
}
@@ -5286,18 +5259,38 @@
/**
* Gets the current subtype of this input method.
+ *
+ * @param userId User ID to be queried about.
+ * @return The current {@link InputMethodSubtype} for the specified user.
*/
+ @Nullable
@Override
- public InputMethodSubtype getCurrentInputMethodSubtype() {
+ public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ }
synchronized (ImfLock.class) {
- // TODO: Make this work even for non-current users?
- if (!calledFromValidUserLocked()) {
- return null;
+ if (mSettings.getCurrentUserId() == userId) {
+ return getCurrentInputMethodSubtypeLocked();
}
- return getCurrentInputMethodSubtypeLocked();
+
+ final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
+ userId, false);
+ return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
+ /**
+ * Returns the current {@link InputMethodSubtype} for the current user.
+ *
+ * <p>CAVEATS: You must also update
+ * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}
+ * when you update the algorithm of this method.</p>
+ *
+ * <p>TODO: Address code duplication between this and
+ * {@link InputMethodSettings#getCurrentInputMethodSubtypeForNonCurrentUsers()}.</p>
+ */
@GuardedBy("ImfLock.class")
InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
String selectedMethodId = getSelectedMethodIdLocked();
@@ -5317,7 +5310,7 @@
// the most applicable subtype from explicitly or implicitly enabled
// subtypes.
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
+ mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
// If there is only one explicitly or implicitly enabled subtype,
// just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
@@ -5361,9 +5354,8 @@
return true;
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(
- mContext.getResources(), mContext.getContentResolver(), methodMap,
- userId, false);
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
+ false);
if (!methodMap.containsKey(imeId)
|| !settings.getEnabledInputMethodListLocked().contains(methodMap.get(imeId))) {
return false; // IME is not found or not enabled.
@@ -5435,8 +5427,7 @@
return true;
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(
- mContext.getResources(), mContext.getContentResolver(), methodMap,
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
userId, false);
if (!methodMap.containsKey(imeId)) {
return false; // IME is not found.
@@ -6138,8 +6129,8 @@
}
} else {
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
- final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
- mContext.getContentResolver(), methodMap, userId, false);
+ final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
+ userId, false);
if (enabled) {
if (!methodMap.containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6275,9 +6266,8 @@
AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
methodMap, methodList, DirectBootAwareness.AUTO);
- final InputMethodSettings settings = new InputMethodSettings(
- mContext.getResources(), mContext.getContentResolver(), methodMap,
- userId, false);
+ final InputMethodSettings settings = new InputMethodSettings(mContext,
+ methodMap, userId, false);
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
methodList);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index a643226..c83a969 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -200,7 +200,7 @@
continue;
}
final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
- mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
+ mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index e37a8e0..1747b5c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -24,9 +24,12 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.os.Binder;
import android.os.Build;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -40,6 +43,7 @@
import android.view.textservice.SpellCheckerInfo;
import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.textservices.TextServicesManagerInternal;
@@ -233,6 +237,7 @@
* TODO: Move all putters and getters of settings to this class.
* TODO(b/235661780): Make the setting supports multi-users.
*/
+ @UserHandleAware
public static class InputMethodSettings {
private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
@@ -240,6 +245,8 @@
private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ @NonNull
+ private final Context mUserAwareContext;
private final Resources mRes;
private final ContentResolver mResolver;
private final ArrayMap<String, InputMethodInfo> mMethodMap;
@@ -299,11 +306,14 @@
return imsList;
}
- InputMethodSettings(Resources res, ContentResolver resolver,
+ InputMethodSettings(@NonNull Context context,
ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
boolean copyOnWrite) {
- mRes = res;
- mResolver = resolver;
+ mUserAwareContext = context.getUserId() == userId
+ ? context
+ : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ mRes = mUserAwareContext.getResources();
+ mResolver = mUserAwareContext.getContentResolver();
mMethodMap = methodMap;
switchCurrentUser(userId, copyOnWrite);
}
@@ -405,14 +415,13 @@
}
List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
- Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
+ InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
List<InputMethodSubtype> enabledSubtypes =
getEnabledInputMethodSubtypeListLocked(imi);
if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
- enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
- context.getResources(), imi);
+ enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi);
}
- return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
+ return InputMethodSubtype.sort(imi, enabledSubtypes);
}
List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
@@ -813,6 +822,85 @@
}
}
+ /**
+ * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
+ * non-current users.
+ *
+ * <p>TODO: Address code duplication between this and
+ * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
+ *
+ * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
+ */
+ @Nullable
+ InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
+ final String selectedMethodId = getSelectedInputMethod();
+ if (selectedMethodId == null) {
+ return null;
+ }
+ final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+ if (imi == null || imi.getSubtypeCount() == 0) {
+ return null;
+ }
+
+ final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+ if (subtypeHashCode != InputMethodUtils.NOT_A_SUBTYPE_ID) {
+ final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+ subtypeHashCode);
+ if (subtypeIndex >= 0) {
+ return imi.getSubtypeAt(subtypeIndex);
+ }
+ }
+
+ // If there are no selected subtypes, the framework will try to find the most applicable
+ // subtype from explicitly or implicitly enabled subtypes.
+ final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi, true);
+ // If there is only one explicitly or implicitly enabled subtype, just returns it.
+ if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
+ return null;
+ }
+ if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+ return explicitlyOrImplicitlyEnabledSubtypes.get(0);
+ }
+ final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ mRes, explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
+ null, true);
+ if (subtype != null) {
+ return subtype;
+ }
+ return SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes,
+ explicitlyOrImplicitlyEnabledSubtypes, null, null, true);
+ }
+
+ boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
+ @NonNull ArrayList<InputMethodSubtype> subtypes,
+ @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+ @NonNull IPackageManager packageManager) {
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (imi == null) {
+ return false;
+ }
+ final String[] packageInfos;
+ try {
+ packageInfos = packageManager.getPackagesForUid(Binder.getCallingUid());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get package infos");
+ return false;
+ }
+ if (ArrayUtils.find(packageInfos,
+ packageInfo -> TextUtils.equals(packageInfo, imi.getPackageName())) == null) {
+ return false;
+ }
+
+ if (subtypes.isEmpty()) {
+ additionalSubtypeMap.remove(imi.getId());
+ } else {
+ additionalSubtypeMap.put(imi.getId(), subtypes);
+ }
+ AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
+ return true;
+ }
+
public void dumpLocked(final Printer pw, final String prefix) {
pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 884ae171..b86fa7a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -35,8 +35,6 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
-import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_DECRYPT;
-import static com.android.internal.widget.LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
@@ -122,6 +120,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -147,7 +146,6 @@
import libcore.util.HexEncoding;
-import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -211,6 +209,9 @@
// user's credential must be presented again, e.g. via ConfirmLockPattern/ConfirmLockPassword.
private static final int GK_PW_HANDLE_STORE_DURATION_MS = 10 * 60 * 1000; // 10 minutes
+ private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
+ private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
+
// Order of holding lock: mSeparateChallengeLock -> mSpManager -> this
// Do not call into ActivityManager while holding mSpManager lock.
private final Object mSeparateChallengeLock = new Object();
@@ -234,7 +235,8 @@
protected final UserManager mUserManager;
private final IStorageManager mStorageManager;
private final IActivityManager mActivityManager;
- private final SyntheticPasswordManager mSpManager;
+ @VisibleForTesting
+ protected final SyntheticPasswordManager mSpManager;
private final KeyStore mKeyStore;
private final java.security.KeyStore mJavaKeyStore;
@@ -1625,8 +1627,7 @@
}
onSyntheticPasswordKnown(userId, sp);
- setLockCredentialWithSpLocked(credential, sp, userId);
- mSpManager.destroyLskfBasedProtector(oldProtectorId, userId);
+ setLockCredentialWithSpLocked(credential, sp, userId, isLockTiedToParent);
sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
return true;
}
@@ -1640,15 +1641,15 @@
if (newCredential.isPattern()) {
setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
}
- updatePasswordHistory(newCredential, userHandle);
mContext.getSystemService(TrustManager.class).reportEnabledTrustAgentsChanged(userHandle);
}
/**
- * Store the hash of the *current* password in the password history list, if device policy
- * enforces password history requirement.
+ * Store the hash of the new password in the password history list, if device policy enforces
+ * a password history requirement.
*/
- private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
+ private void updatePasswordHistory(SyntheticPassword sp, LockscreenCredential password,
+ int userHandle, boolean isLockTiedToParent) {
if (password.isNone()) {
return;
}
@@ -1656,8 +1657,11 @@
// Do not keep track of historical patterns
return;
}
- // Add the password to the password history. We assume all
- // password hashes have the same length for simplicity of implementation.
+ if (isLockTiedToParent) {
+ // Do not keep track of historical auto-generated profile passwords
+ return;
+ }
+ // Add the password to the password history.
String passwordHistory = getString(
LockPatternUtils.PASSWORD_HISTORY_KEY, /* defaultValue= */ null, userHandle);
if (passwordHistory == null) {
@@ -1667,13 +1671,9 @@
if (passwordHistoryLength == 0) {
passwordHistory = "";
} else {
- final byte[] hashFactor = getHashFactor(password, userHandle);
+ final byte[] hashFactor = sp.derivePasswordHashFactor();
final byte[] salt = getSalt(userHandle).getBytes();
String hash = password.passwordToHistoryHash(salt, hashFactor);
- if (hash == null) {
- Slog.e(TAG, "Compute new style password hash failed, fallback to legacy style");
- hash = password.legacyPasswordToHash(salt);
- }
if (TextUtils.isEmpty(passwordHistory)) {
passwordHistory = hash;
} else {
@@ -1848,8 +1848,8 @@
@VisibleForTesting /** Note: this method is overridden in unit tests */
protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
- byte[] encryptionResult;
- byte[] iv;
+ final byte[] iv;
+ final byte[] ciphertext;
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
keyGenerator.init(new SecureRandom());
@@ -1878,7 +1878,7 @@
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
+ KeyProperties.ENCRYPTION_PADDING_NONE);
cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
- encryptionResult = cipher.doFinal(password.getCredential());
+ ciphertext = cipher.doFinal(password.getCredential());
iv = cipher.getIV();
} finally {
// The original key can now be discarded.
@@ -1889,17 +1889,10 @@
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException("Failed to encrypt key", e);
}
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- try {
- if (iv.length != PROFILE_KEY_IV_SIZE) {
- throw new IllegalArgumentException("Invalid iv length: " + iv.length);
- }
- outputStream.write(iv);
- outputStream.write(encryptionResult);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to concatenate byte arrays", e);
+ if (iv.length != PROFILE_KEY_IV_SIZE) {
+ throw new IllegalArgumentException("Invalid iv length: " + iv.length);
}
- mStorage.writeChildProfileLock(userId, outputStream.toByteArray());
+ mStorage.writeChildProfileLock(userId, ArrayUtils.concat(iv, ciphertext));
}
private void setUserKeyProtection(int userId, byte[] key) {
@@ -2642,8 +2635,7 @@
/**
* Changes the user's LSKF by creating an LSKF-based protector that uses the new LSKF (which may
- * be empty) and setting the new protector as the user's current LSKF-based protector. The old
- * LSKF-based protector is not destroyed, and the SP itself is not changed.
+ * be empty) and replacing the old LSKF-based protector with it. The SP itself is not changed.
*
* Also maintains the invariants described in {@link SyntheticPasswordManager} by
* setting/clearing the protection (by the SP) on the user's file-based encryption key and
@@ -2652,9 +2644,10 @@
*/
@GuardedBy("mSpManager")
private long setLockCredentialWithSpLocked(LockscreenCredential credential,
- SyntheticPassword sp, int userId) {
+ SyntheticPassword sp, int userId, boolean isLockTiedToParent) {
if (DEBUG) Slog.d(TAG, "setLockCredentialWithSpLocked: user=" + userId);
final int savedCredentialType = getCredentialTypeInternal(userId);
+ final long oldProtectorId = getCurrentLskfBasedProtectorId(userId);
final long newProtectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
credential, sp, userId);
final Map<Integer, LockscreenCredential> profilePasswords;
@@ -2689,6 +2682,7 @@
LockPatternUtils.invalidateCredentialTypeCache();
synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+ updatePasswordHistory(sp, credential, userId, isLockTiedToParent);
setUserPasswordMetrics(credential, userId);
mManagedProfilePasswordCache.removePassword(userId);
if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
@@ -2700,7 +2694,7 @@
entry.getValue().zeroize();
}
}
-
+ mSpManager.destroyLskfBasedProtector(oldProtectorId, userId);
return newProtectorId;
}
@@ -2934,9 +2928,8 @@
return false;
}
onSyntheticPasswordKnown(userId, result.syntheticPassword);
- final long oldProtectorId = getCurrentLskfBasedProtectorId(userId);
- setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId);
- mSpManager.destroyLskfBasedProtector(oldProtectorId, userId);
+ setLockCredentialWithSpLocked(credential, result.syntheticPassword, userId,
+ /* isLockTiedToParent= */ false);
return true;
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 76032f4..1aee345 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -176,12 +176,6 @@
pw.println(" set-password [--old <CREDENTIAL>] [--user USER_ID] <PASSWORD>");
pw.println(" Sets the lock screen as password, using the given PASSOWRD to unlock.");
pw.println("");
- pw.println(" sp [--old <CREDENTIAL>] [--user USER_ID]");
- pw.println(" Gets whether synthetic password is enabled.");
- pw.println("");
- pw.println(" sp [--old <CREDENTIAL>] [--user USER_ID] <1|0>");
- pw.println(" Enables / disables synthetic password.");
- pw.println("");
pw.println(" clear [--old <CREDENTIAL>] [--user USER_ID]");
pw.println(" Clears the lock credentials.");
pw.println("");
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
index 672c3f7..e43d4e8 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
@@ -26,6 +26,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockscreenCredential;
import java.security.GeneralSecurityException;
@@ -117,8 +118,7 @@
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(password.getCredential());
byte[] iv = cipher.getIV();
- byte[] block = Arrays.copyOf(iv, ciphertext.length + iv.length);
- System.arraycopy(ciphertext, 0, block, iv.length, ciphertext.length);
+ byte[] block = ArrayUtils.concat(iv, ciphertext);
mEncryptedPasswords.put(userId, block);
} catch (GeneralSecurityException e) {
Slog.d(TAG, "Cannot encrypt", e);
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index b06af8e..a931844 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -25,7 +25,8 @@
import android.system.keystore2.KeyDescriptor;
import android.util.Slog;
-import java.io.ByteArrayOutputStream;
+import com.android.internal.util.ArrayUtils;
+
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -50,9 +51,9 @@
public class SyntheticPasswordCrypto {
private static final String TAG = "SyntheticPasswordCrypto";
- private static final int PROFILE_KEY_IV_SIZE = 12;
- private static final int DEFAULT_TAG_LENGTH_BITS = 128;
- private static final int AES_KEY_LENGTH = 32; // 256-bit AES key
+ private static final int AES_GCM_KEY_SIZE = 32; // AES-256-GCM
+ private static final int AES_GCM_IV_SIZE = 12;
+ private static final int AES_GCM_TAG_SIZE = 16;
private static final byte[] PROTECTOR_SECRET_PERSONALIZATION = "application-id".getBytes();
// Time between the user credential is verified with GK and the decryption of synthetic password
// under the auth-bound key. This should always happen one after the other, but give it 15
@@ -65,11 +66,11 @@
if (blob == null) {
return null;
}
- byte[] iv = Arrays.copyOfRange(blob, 0, PROFILE_KEY_IV_SIZE);
- byte[] ciphertext = Arrays.copyOfRange(blob, PROFILE_KEY_IV_SIZE, blob.length);
+ byte[] iv = Arrays.copyOfRange(blob, 0, AES_GCM_IV_SIZE);
+ byte[] ciphertext = Arrays.copyOfRange(blob, AES_GCM_IV_SIZE, blob.length);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
- cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(DEFAULT_TAG_LENGTH_BITS, iv));
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(AES_GCM_TAG_SIZE * 8, iv));
return cipher.doFinal(ciphertext);
}
@@ -86,23 +87,20 @@
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(blob);
byte[] iv = cipher.getIV();
- if (iv.length != PROFILE_KEY_IV_SIZE) {
- throw new IllegalArgumentException("Invalid iv length: " + iv.length);
+ if (iv.length != AES_GCM_IV_SIZE) {
+ throw new IllegalArgumentException("Invalid iv length: " + iv.length + " bytes");
}
final GCMParameterSpec spec = cipher.getParameters().getParameterSpec(
GCMParameterSpec.class);
- if (spec.getTLen() != DEFAULT_TAG_LENGTH_BITS) {
- throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen());
+ if (spec.getTLen() != AES_GCM_TAG_SIZE * 8) {
+ throw new IllegalArgumentException("Invalid tag length: " + spec.getTLen() + " bits");
}
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- outputStream.write(iv);
- outputStream.write(ciphertext);
- return outputStream.toByteArray();
+ return ArrayUtils.concat(iv, ciphertext);
}
public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) {
byte[] keyHash = personalizedHash(personalization, keyBytes);
- SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
KeyProperties.KEY_ALGORITHM_AES);
try {
return encrypt(key, message);
@@ -116,7 +114,7 @@
public static byte[] decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext) {
byte[] keyHash = personalizedHash(personalization, keyBytes);
- SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
+ SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_GCM_KEY_SIZE),
KeyProperties.KEY_ALGORITHM_AES);
try {
return decrypt(key, ciphertext);
@@ -129,18 +127,20 @@
}
/**
- * Decrypt a legacy SP blob which did the Keystore and software encryption layers in the wrong
+ * Decrypts a legacy SP blob which did the Keystore and software encryption layers in the wrong
* order.
*/
- public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] protectorSecret) {
+ public static byte[] decryptBlobV1(String protectorKeyAlias, byte[] blob,
+ byte[] protectorSecret) {
try {
KeyStore keyStore = getKeyStore();
- SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
- if (keyStoreKey == null) {
- throw new IllegalStateException("SP key is missing: " + keyAlias);
+ SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
+ if (protectorKey == null) {
+ throw new IllegalStateException("SP protector key is missing: "
+ + protectorKeyAlias);
}
byte[] intermediate = decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, blob);
- return decrypt(keyStoreKey, intermediate);
+ return decrypt(protectorKey, intermediate);
} catch (Exception e) {
Slog.e(TAG, "Failed to decrypt V1 blob", e);
throw new IllegalStateException("Failed to decrypt blob", e);
@@ -165,15 +165,17 @@
/**
* Decrypts an SP blob that was created by {@link #createBlob}.
*/
- public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] protectorSecret) {
+ public static byte[] decryptBlob(String protectorKeyAlias, byte[] blob,
+ byte[] protectorSecret) {
try {
final KeyStore keyStore = getKeyStore();
- SecretKey keyStoreKey = (SecretKey) keyStore.getKey(keyAlias, null);
- if (keyStoreKey == null) {
- throw new IllegalStateException("SP key is missing: " + keyAlias);
+ SecretKey protectorKey = (SecretKey) keyStore.getKey(protectorKeyAlias, null);
+ if (protectorKey == null) {
+ throw new IllegalStateException("SP protector key is missing: "
+ + protectorKeyAlias);
}
- byte[] intermediate = decrypt(keyStoreKey, blob);
+ byte[] intermediate = decrypt(protectorKey, blob);
return decrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
@@ -187,20 +189,21 @@
/**
* Creates a new SP blob by encrypting the given data. Two encryption layers are applied: an
- * inner layer using a hash of protectorSecret as the key, and an outer layer using a new
- * Keystore key with the given alias and optionally bound to a SID.
+ * inner layer using a hash of protectorSecret as the key, and an outer layer using the
+ * protector key, which is a Keystore key that is optionally bound to a SID. This method
+ * creates the protector key and stores it under protectorKeyAlias.
*
* The reason we use a layer of software encryption, instead of using protectorSecret as the
* applicationId of the Keystore key, is to work around buggy KeyMint implementations that don't
* cryptographically bind the applicationId to the key. The Keystore layer has to be the outer
* layer, so that LSKF verification is ratelimited by Gatekeeper when Weaver is unavailable.
*/
- public static byte[] createBlob(String keyAlias, byte[] data, byte[] protectorSecret,
+ public static byte[] createBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
long sid) {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
- keyGenerator.init(AES_KEY_LENGTH * 8, new SecureRandom());
- SecretKey keyStoreKey = keyGenerator.generateKey();
+ keyGenerator.init(AES_GCM_KEY_SIZE * 8, new SecureRandom());
+ SecretKey protectorKey = keyGenerator.generateKey();
final KeyStore keyStore = getKeyStore();
KeyProtection.Builder builder = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -214,9 +217,9 @@
final KeyProtection protNonRollbackResistant = builder.build();
builder.setRollbackResistant(true);
final KeyProtection protRollbackResistant = builder.build();
- final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(keyStoreKey);
+ final KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(protectorKey);
try {
- keyStore.setEntry(keyAlias, entry, protRollbackResistant);
+ keyStore.setEntry(protectorKeyAlias, entry, protRollbackResistant);
Slog.i(TAG, "Using rollback-resistant key");
} catch (KeyStoreException e) {
if (!(e.getCause() instanceof android.security.KeyStoreException)) {
@@ -228,11 +231,11 @@
}
Slog.w(TAG, "Rollback-resistant keys unavailable. Falling back to "
+ "non-rollback-resistant key");
- keyStore.setEntry(keyAlias, entry, protNonRollbackResistant);
+ keyStore.setEntry(protectorKeyAlias, entry, protNonRollbackResistant);
}
byte[] intermediate = encrypt(protectorSecret, PROTECTOR_SECRET_PERSONALIZATION, data);
- return encrypt(keyStoreKey, intermediate);
+ return encrypt(protectorKey, intermediate);
} catch (CertificateException | IOException | BadPaddingException
| IllegalBlockSizeException
| KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -243,15 +246,15 @@
}
}
- public static void destroyBlobKey(String keyAlias) {
+ public static void destroyProtectorKey(String keyAlias) {
KeyStore keyStore;
try {
keyStore = getKeyStore();
keyStore.deleteEntry(keyAlias);
- Slog.i(TAG, "SP key deleted: " + keyAlias);
+ Slog.i(TAG, "Deleted SP protector key " + keyAlias);
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
| IOException e) {
- Slog.e(TAG, "Failed to destroy blob", e);
+ Slog.e(TAG, "Failed to delete SP protector key " + keyAlias, e);
}
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 2d0143a..5b75b6a 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -135,14 +135,16 @@
private static final byte PROTECTOR_TYPE_STRONG_TOKEN_BASED = 1;
private static final byte PROTECTOR_TYPE_WEAK_TOKEN_BASED = 2;
- // 256-bit synthetic password
- private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
+ private static final String PROTECTOR_KEY_ALIAS_PREFIX = "synthetic_password_";
+
+ // The security strength of the synthetic password, in bytes
+ private static final int SYNTHETIC_PASSWORD_SECURITY_STRENGTH = 256 / 8;
private static final int PASSWORD_SCRYPT_LOG_N = 11;
private static final int PASSWORD_SCRYPT_LOG_R = 3;
private static final int PASSWORD_SCRYPT_LOG_P = 1;
private static final int PASSWORD_SALT_LENGTH = 16;
- private static final int PASSWORD_TOKEN_LENGTH = 32;
+ private static final int STRETCHED_LSKF_LENGTH = 32;
private static final String TAG = "SyntheticPasswordManager";
private static final byte[] PERSONALIZATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
@@ -275,8 +277,8 @@
*/
static SyntheticPassword create() {
SyntheticPassword result = new SyntheticPassword(SYNTHETIC_PASSWORD_VERSION_V3);
- byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
- byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_LENGTH);
+ byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+ byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
result.recreate(escrowSplit0, escrowSplit1);
byte[] encrypteEscrowSplit0 = SyntheticPasswordCrypto.encrypt(result.mSyntheticPassword,
PERSONALIZATION_E0, escrowSplit0);
@@ -583,7 +585,7 @@
for (long protectorId : mStorage.listSyntheticPasswordProtectorsForUser(SP_BLOB_NAME,
userId)) {
destroyWeaverSlot(protectorId, userId);
- destroySPBlobKey(getKeyName(protectorId));
+ destroyProtectorKey(getProtectorKeyAlias(protectorId));
}
// Remove potential persistent state (in RPMB), to prevent them from accumulating and
// causing problems.
@@ -770,7 +772,7 @@
LockscreenCredential credential, SyntheticPassword sp, int userId) {
long protectorId = generateProtectorId();
PasswordData pwd = PasswordData.create(credential.getType());
- byte[] pwdToken = computePasswordToken(credential, pwd);
+ byte[] stretchedLskf = stretchLskf(credential, pwd);
final long sid;
final byte[] protectorSecret;
@@ -778,7 +780,7 @@
// Protector uses Weaver to verify the LSKF
int weaverSlot = getNextAvailableWeaverSlot();
Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
- byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken),
+ byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf),
null);
if (weaverSecret == null) {
throw new IllegalStateException(
@@ -791,7 +793,7 @@
pwd.passwordHandle = null;
sid = GateKeeper.INVALID_SECURE_USER_ID;
- protectorSecret = transformUnderWeaverSecret(pwdToken, weaverSecret);
+ protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
} else {
// Protector uses Gatekeeper to verify the LSKF
@@ -805,7 +807,7 @@
GateKeeperResponse response;
try {
response = gatekeeper.enroll(fakeUid(userId), null, null,
- passwordTokenToGkInput(pwdToken));
+ stretchedLskfToGkPassword(stretchedLskf));
} catch (RemoteException e) {
throw new IllegalStateException("Failed to enroll LSKF for new SP protector for "
+ "user " + userId, e);
@@ -816,7 +818,7 @@
}
pwd.passwordHandle = response.getPayload();
sid = sidFromPasswordHandle(pwd.passwordHandle);
- protectorSecret = transformUnderSecdiscardable(pwdToken,
+ protectorSecret = transformUnderSecdiscardable(stretchedLskf,
createSecdiscardable(protectorId, userId));
// No need to pass in quality since the credential type already encodes sufficient info
synchronizeFrpPassword(pwd, 0, userId);
@@ -834,12 +836,13 @@
PersistentData persistentData = mStorage.readPersistentDataBlock();
if (persistentData.type == PersistentData.TYPE_SP) {
PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
- byte[] pwdToken = computePasswordToken(userCredential, pwd);
+ byte[] stretchedLskf = stretchLskf(userCredential, pwd);
GateKeeperResponse response;
try {
response = gatekeeper.verifyChallenge(fakeUid(persistentData.userId),
- 0 /* challenge */, pwd.passwordHandle, passwordTokenToGkInput(pwdToken));
+ 0 /* challenge */, pwd.passwordHandle,
+ stretchedLskfToGkPassword(stretchedLskf));
} catch (RemoteException e) {
Slog.e(TAG, "FRP verifyChallenge failed", e);
return VerifyCredentialResponse.ERROR;
@@ -851,10 +854,10 @@
return VerifyCredentialResponse.ERROR;
}
PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
- byte[] pwdToken = computePasswordToken(userCredential, pwd);
+ byte[] stretchedLskf = stretchLskf(userCredential, pwd);
int weaverSlot = persistentData.userId;
- return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload();
+ return weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)).stripPayload();
} else {
Slog.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+ persistentData.type);
@@ -999,7 +1002,8 @@
} else {
spSecret = sp.getSyntheticPassword();
}
- byte[] content = createSPBlob(getKeyName(protectorId), spSecret, protectorSecret, sid);
+ byte[] content = createSpBlob(getProtectorKeyAlias(protectorId), spSecret, protectorSecret,
+ sid);
/*
* We can upgrade from v1 to v2 because that's just a change in the way that
* the SP is stored. However, we can't upgrade to v3 because that is a change
@@ -1031,7 +1035,7 @@
return result;
}
- byte[] pwdToken = computePasswordToken(credential, pwd);
+ byte[] stretchedLskf = stretchLskf(credential, pwd);
final byte[] protectorSecret;
final long sid;
@@ -1043,20 +1047,20 @@
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
}
- result.gkResponse = weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken));
+ result.gkResponse = weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf));
if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
return result;
}
sid = GateKeeper.INVALID_SECURE_USER_ID;
- protectorSecret = transformUnderWeaverSecret(pwdToken,
+ protectorSecret = transformUnderWeaverSecret(stretchedLskf,
result.gkResponse.getGatekeeperHAT());
} else {
// Protector uses Gatekeeper to verify the LSKF
- byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
+ byte[] gkPassword = stretchedLskfToGkPassword(stretchedLskf);
GateKeeperResponse response;
try {
response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
- pwd.passwordHandle, gkPwdToken);
+ pwd.passwordHandle, gkPassword);
} catch (RemoteException e) {
Slog.e(TAG, "gatekeeper verify failed", e);
result.gkResponse = VerifyCredentialResponse.ERROR;
@@ -1069,7 +1073,7 @@
GateKeeperResponse reenrollResponse;
try {
reenrollResponse = gatekeeper.enroll(fakeUid(userId),
- pwd.passwordHandle, gkPwdToken, gkPwdToken);
+ pwd.passwordHandle, gkPassword, gkPassword);
} catch (RemoteException e) {
Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
reenrollResponse = GateKeeperResponse.ERROR;
@@ -1095,7 +1099,7 @@
return result;
}
sid = sidFromPasswordHandle(pwd.passwordHandle);
- protectorSecret = transformUnderSecdiscardable(pwdToken,
+ protectorSecret = transformUnderSecdiscardable(stretchedLskf,
loadSecdiscardable(protectorId, userId));
}
// Supplied credential passes first stage weaver/gatekeeper check so it should be correct.
@@ -1210,10 +1214,11 @@
}
final byte[] spSecret;
if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
- spSecret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(protectorId), blob.mContent,
- protectorSecret);
+ spSecret = SyntheticPasswordCrypto.decryptBlobV1(getProtectorKeyAlias(protectorId),
+ blob.mContent, protectorSecret);
} else {
- spSecret = decryptSPBlob(getKeyName(protectorId), blob.mContent, protectorSecret);
+ spSecret = decryptSpBlob(getProtectorKeyAlias(protectorId), blob.mContent,
+ protectorSecret);
}
if (spSecret == null) {
Slog.e(TAG, "Fail to decrypt SP for user " + userId);
@@ -1308,7 +1313,6 @@
SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
protectorId, userId));
destroyProtectorCommon(protectorId, userId);
- destroyState(SECDISCARDABLE_NAME, protectorId, userId);
if (blob.mProtectorType == PROTECTOR_TYPE_WEAK_TOKEN_BASED) {
notifyWeakEscrowTokenRemovedListeners(protectorId, userId);
}
@@ -1332,14 +1336,14 @@
*/
public void destroyLskfBasedProtector(long protectorId, int userId) {
destroyProtectorCommon(protectorId, userId);
- destroyState(SECDISCARDABLE_NAME, protectorId, userId);
destroyState(PASSWORD_DATA_NAME, protectorId, userId);
destroyState(PASSWORD_METRICS_NAME, protectorId, userId);
}
private void destroyProtectorCommon(long protectorId, int userId) {
destroyState(SP_BLOB_NAME, protectorId, userId);
- destroySPBlobKey(getKeyName(protectorId));
+ destroyProtectorKey(getProtectorKeyAlias(protectorId));
+ destroyState(SECDISCARDABLE_NAME, protectorId, userId);
if (hasState(WEAVER_SLOT_NAME, protectorId, userId)) {
destroyWeaverSlot(protectorId, userId);
}
@@ -1348,19 +1352,13 @@
private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
byte[] weaverSecret = SyntheticPasswordCrypto.personalizedHash(
PERSONALIZATION_WEAVER_PASSWORD, secret);
- byte[] result = new byte[data.length + weaverSecret.length];
- System.arraycopy(data, 0, result, 0, data.length);
- System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
- return result;
+ return ArrayUtils.concat(data, weaverSecret);
}
private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
byte[] secdiscardable = SyntheticPasswordCrypto.personalizedHash(
PERSONALIZATION_SECDISCARDABLE, rawSecdiscardable);
- byte[] result = new byte[data.length + secdiscardable.length];
- System.arraycopy(data, 0, result, 0, data.length);
- System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
- return result;
+ return ArrayUtils.concat(data, secdiscardable);
}
private byte[] createSecdiscardable(long protectorId, int userId) {
@@ -1429,17 +1427,20 @@
mStorage.deleteSyntheticPasswordState(userId, protectorId, stateName);
}
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
- return SyntheticPasswordCrypto.decryptBlob(blobKeyName, blob, protectorSecret);
+ @VisibleForTesting
+ protected byte[] decryptSpBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret) {
+ return SyntheticPasswordCrypto.decryptBlob(protectorKeyAlias, blob, protectorSecret);
}
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+ @VisibleForTesting
+ protected byte[] createSpBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
long sid) {
- return SyntheticPasswordCrypto.createBlob(blobKeyName, data, protectorSecret, sid);
+ return SyntheticPasswordCrypto.createBlob(protectorKeyAlias, data, protectorSecret, sid);
}
- protected void destroySPBlobKey(String keyAlias) {
- SyntheticPasswordCrypto.destroyBlobKey(keyAlias);
+ @VisibleForTesting
+ protected void destroyProtectorKey(String keyAlias) {
+ SyntheticPasswordCrypto.destroyProtectorKey(keyAlias);
}
public static long generateProtectorId() {
@@ -1464,22 +1465,24 @@
}
}
- private String getKeyName(long protectorId) {
- return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, protectorId);
+ private String getProtectorKeyAlias(long protectorId) {
+ return String.format("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
}
- private byte[] computePasswordToken(LockscreenCredential credential, PasswordData data) {
+ private byte[] stretchLskf(LockscreenCredential credential, PasswordData data) {
final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential();
return scrypt(password, data.salt, 1 << data.scryptLogN, 1 << data.scryptLogR,
- 1 << data.scryptLogP, PASSWORD_TOKEN_LENGTH);
+ 1 << data.scryptLogP, STRETCHED_LSKF_LENGTH);
}
- private byte[] passwordTokenToGkInput(byte[] token) {
- return SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_USER_GK_AUTH, token);
+ private byte[] stretchedLskfToGkPassword(byte[] stretchedLskf) {
+ return SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_USER_GK_AUTH,
+ stretchedLskf);
}
- private byte[] passwordTokenToWeaverKey(byte[] token) {
- byte[] key = SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_WEAVER_KEY, token);
+ private byte[] stretchedLskfToWeaverKey(byte[] stretchedLskf) {
+ byte[] key = SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_WEAVER_KEY,
+ stretchedLskf);
if (key.length < mWeaverConfig.keySize) {
throw new IllegalArgumentException("weaver key length too small");
}
@@ -1518,7 +1521,7 @@
}
/**
- * Migrate all existing SP keystore keys from uid 1000 app domain to LSS selinux domain
+ * Migrates all existing SP protector keys from uid 1000 app domain to LSS selinux domain.
*/
public boolean migrateKeyNamespace() {
boolean success = true;
@@ -1526,7 +1529,8 @@
mStorage.listSyntheticPasswordProtectorsForAllUsers(SP_BLOB_NAME);
for (List<Long> userProtectors : allProtectors.values()) {
for (long protectorId : userProtectors) {
- success &= SyntheticPasswordCrypto.migrateLockSettingsKey(getKeyName(protectorId));
+ success &= SyntheticPasswordCrypto.migrateLockSettingsKey(
+ getProtectorKeyAlias(protectorId));
}
}
return success;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 24d575e..7921619 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -20,6 +20,7 @@
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -88,7 +89,7 @@
) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] encryptedRecoveryKey = locallyEncryptRecoveryKey(lockScreenHash, recoveryKey);
byte[] thmKfHash = calculateThmKfHash(lockScreenHash);
- byte[] header = concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams);
+ byte[] header = ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams);
return SecureBox.encrypt(
/*theirPublicKey=*/ publicKey,
/*sharedSecret=*/ thmKfHash,
@@ -171,7 +172,7 @@
// Note that Android P devices do not have the API to provide the optional metadata,
// so all the keys with non-empty metadata stored on Android Q+ devices cannot be
// recovered on Android P devices.
- header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata);
+ header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, metadata);
}
byte[] encryptedKey = SecureBox.encrypt(
/*theirPublicKey=*/ null,
@@ -218,8 +219,8 @@
return SecureBox.encrypt(
publicKey,
/*sharedSecret=*/ null,
- /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
- /*payload=*/ concat(thmKfHash, keyClaimant));
+ /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+ /*payload=*/ ArrayUtils.concat(thmKfHash, keyClaimant));
}
/**
@@ -240,7 +241,7 @@
return SecureBox.decrypt(
/*ourPrivateKey=*/ null,
/*sharedSecret=*/ keyClaimant,
- /*header=*/ concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*encryptedPayload=*/ encryptedResponse);
}
@@ -280,7 +281,7 @@
if (applicationKeyMetadata == null) {
header = ENCRYPTED_APPLICATION_KEY_HEADER;
} else {
- header = concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata);
+ header = ArrayUtils.concat(ENCRYPTED_APPLICATION_KEY_HEADER, applicationKeyMetadata);
}
return SecureBox.decrypt(
/*ourPrivateKey=*/ null,
@@ -333,26 +334,6 @@
.array();
}
- /**
- * Returns the concatenation of all the given {@code arrays}.
- */
- @VisibleForTesting
- static byte[] concat(byte[]... arrays) {
- int length = 0;
- for (byte[] array : arrays) {
- length += array.length;
- }
-
- byte[] concatenated = new byte[length];
- int pos = 0;
- for (byte[] array : arrays) {
- System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length);
- pos += array.length;
- }
-
- return concatenated;
- }
-
// Statics only
private KeySyncUtils() {}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 807ee03..51a37b3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
@@ -69,7 +70,7 @@
private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
private static final byte[] HKDF_SALT =
- concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+ ArrayUtils.concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
"P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
@@ -199,13 +200,13 @@
}
byte[] randNonce = genRandomNonce();
- byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
if (senderKeyPair == null) {
- return concat(VERSION, randNonce, ciphertext);
+ return ArrayUtils.concat(VERSION, randNonce, ciphertext);
} else {
- return concat(
+ return ArrayUtils.concat(
VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
}
}
@@ -268,7 +269,7 @@
byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
- byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+ byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret);
SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
}
@@ -446,25 +447,6 @@
return nonce;
}
- @VisibleForTesting
- static byte[] concat(byte[]... inputs) {
- int length = 0;
- for (int i = 0; i < inputs.length; i++) {
- if (inputs[i] == null) {
- inputs[i] = EMPTY_BYTE_ARRAY;
- }
- length += inputs[i].length;
- }
-
- byte[] output = new byte[length];
- int outputPos = 0;
- for (byte[] input : inputs) {
- System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
- outputPos += input.length;
- }
- return output;
- }
-
private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
return input == null ? EMPTY_BYTE_ARRAY : input;
}
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 9a19031..c12dc8e 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -32,6 +32,7 @@
import android.os.PowerWhitelistManager;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
@@ -117,6 +118,12 @@
int componentType = getComponentType(pendingIntent);
ComponentName componentName = getComponentName(pendingIntent, componentType);
if (componentName != null) {
+ if (!TextUtils.equals(componentName.getPackageName(), sessionPackageName)) {
+ EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet Logging.
+ throw new IllegalArgumentException("ComponentName does not belong to "
+ + "sessionPackageName. sessionPackageName = " + sessionPackageName
+ + ", ComponentName pkg = " + componentName.getPackageName());
+ }
return new MediaButtonReceiverHolder(userId, pendingIntent, componentName,
componentType);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 34cd6a0..4f8771a 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -50,6 +50,8 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
@@ -912,8 +914,7 @@
}
@Override
- public void setMediaButtonReceiver(PendingIntent pi, String sessionPackageName)
- throws RemoteException {
+ public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
final long token = Binder.clearCallingIdentity();
try {
if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
@@ -921,7 +922,7 @@
return;
}
mMediaButtonReceiverHolder =
- MediaButtonReceiverHolder.create(mContext, mUserId, pi, sessionPackageName);
+ MediaButtonReceiverHolder.create(mContext, mUserId, pi, mPackageName);
mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
} finally {
Binder.restoreCallingIdentity(token);
@@ -932,6 +933,15 @@
public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
final long token = Binder.clearCallingIdentity();
try {
+ //mPackageName has been verified in MediaSessionService.enforcePackageName().
+ if (receiver != null && !TextUtils.equals(
+ mPackageName, receiver.getPackageName())) {
+ EventLog.writeEvent(0x534e4554, "238177121", -1, ""); // SafetyNet Logging.
+ throw new IllegalArgumentException("receiver does not belong to "
+ + "package name provided to MediaSessionRecord. Pkg = " + mPackageName
+ + ", Receiver Pkg = " + receiver.getPackageName());
+ }
+
if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
!= 0) {
return;
diff --git a/services/core/java/com/android/server/pm/ApexPackageInfo.java b/services/core/java/com/android/server/pm/ApexPackageInfo.java
index f959a52..73cb0ad 100644
--- a/services/core/java/com/android/server/pm/ApexPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ApexPackageInfo.java
@@ -22,10 +22,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
@@ -33,8 +32,9 @@
import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
+import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import java.io.File;
@@ -59,16 +59,17 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
- private List<PackageInfo> mAllPackagesCache;
+ private List<Pair<ApexInfo, AndroidPackage>> mAllPackagesCache;
- /**
- * Whether an APEX package is active or not.
- *
- * @param packageInfo the package to check
- * @return {@code true} if this package is active, {@code false} otherwise.
- */
- private static boolean isActive(PackageInfo packageInfo) {
- return packageInfo.isActiveApex;
+ @Nullable
+ private final PackageManagerService mPackageManager;
+
+ ApexPackageInfo() {
+ mPackageManager = null;
+ }
+
+ ApexPackageInfo(@NonNull PackageManagerService pms) {
+ mPackageManager = pms;
}
/**
@@ -105,20 +106,23 @@
* is not found.
*/
@Nullable
- PackageInfo getPackageInfo(String packageName, @ApexManager.PackageInfoFlags int flags) {
+ Pair<ApexInfo, AndroidPackage> getPackageInfo(String packageName,
+ @ApexManager.PackageInfoFlags int flags) {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
- final PackageInfo packageInfo = mAllPackagesCache.get(i);
- if (!packageInfo.packageName.equals(packageName)) {
+ final Pair<ApexInfo, AndroidPackage> pair = mAllPackagesCache.get(i);
+ var apexInfo = pair.first;
+ var pkg = pair.second;
+ if (!pkg.getPackageName().equals(packageName)) {
continue;
}
- if ((matchActive && isActive(packageInfo))
- || (matchFactory && isFactory(packageInfo))) {
- return packageInfo;
+ if ((matchActive && apexInfo.isActive)
+ || (matchFactory && apexInfo.isFactory)) {
+ return pair;
}
}
return null;
@@ -128,18 +132,18 @@
/**
* Retrieves information about all active APEX packages.
*
- * @return a List of PackageInfo object, each one containing information about a different
- * active package.
+ * @return list containing information about different active packages.
*/
- List<PackageInfo> getActivePackages() {
+ @NonNull
+ List<Pair<ApexInfo, AndroidPackage>> getActivePackages() {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
- final List<PackageInfo> activePackages = new ArrayList<>();
+ final List<Pair<ApexInfo, AndroidPackage>> activePackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
- final PackageInfo packageInfo = mAllPackagesCache.get(i);
- if (isActive(packageInfo)) {
- activePackages.add(packageInfo);
+ final var pair = mAllPackagesCache.get(i);
+ if (pair.first.isActive) {
+ activePackages.add(pair);
}
}
return activePackages;
@@ -147,20 +151,20 @@
}
/**
- * Retrieves information about all active pre-installed APEX packages.
+ * Retrieves information about all pre-installed APEX packages.
*
- * @return a List of PackageInfo object, each one containing information about a different
- * active pre-installed package.
+ * @return list containing information about different pre-installed packages.
*/
- List<PackageInfo> getFactoryPackages() {
+ @NonNull
+ List<Pair<ApexInfo, AndroidPackage>> getFactoryPackages() {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
- final List<PackageInfo> factoryPackages = new ArrayList<>();
+ final List<Pair<ApexInfo, AndroidPackage>> factoryPackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
- final PackageInfo packageInfo = mAllPackagesCache.get(i);
- if (isFactory(packageInfo)) {
- factoryPackages.add(packageInfo);
+ final var pair = mAllPackagesCache.get(i);
+ if (pair.first.isFactory) {
+ factoryPackages.add(pair);
}
}
return factoryPackages;
@@ -170,18 +174,18 @@
/**
* Retrieves information about all inactive APEX packages.
*
- * @return a List of PackageInfo object, each one containing information about a different
- * inactive package.
+ * @return list containing information about different inactive packages.
*/
- List<PackageInfo> getInactivePackages() {
+ @NonNull
+ List<Pair<ApexInfo, AndroidPackage>> getInactivePackages() {
synchronized (mLock) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
- final List<PackageInfo> inactivePackages = new ArrayList<>();
+ final List<Pair<ApexInfo, AndroidPackage>> inactivePackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
- final PackageInfo packageInfo = mAllPackagesCache.get(i);
- if (!isActive(packageInfo)) {
- inactivePackages.add(packageInfo);
+ final var pair = mAllPackagesCache.get(i);
+ if (!pair.first.isActive) {
+ inactivePackages.add(pair);
}
}
return inactivePackages;
@@ -199,8 +203,8 @@
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
- final PackageInfo packageInfo = mAllPackagesCache.get(i);
- if (packageInfo.packageName.equals(packageName)) {
+ final var pair = mAllPackagesCache.get(i);
+ if (pair.second.getPackageName().equals(packageName)) {
return true;
}
}
@@ -222,21 +226,18 @@
}
void notifyPackageInstalled(ApexInfo apexInfo, AndroidPackage pkg) {
- final int flags = PackageManager.GET_META_DATA
- | PackageManager.GET_SIGNING_CERTIFICATES
- | PackageManager.GET_SIGNATURES;
- final PackageInfo newApexPkg = PackageInfoWithoutStateUtils.generate(
- pkg, apexInfo, flags);
- final String packageName = newApexPkg.packageName;
+ final String packageName = pkg.getPackageName();
synchronized (mLock) {
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
- PackageInfo oldApexPkg = mAllPackagesCache.get(i);
- if (oldApexPkg.isActiveApex && oldApexPkg.packageName.equals(packageName)) {
- if (isFactory(oldApexPkg)) {
- oldApexPkg.isActiveApex = false;
- mAllPackagesCache.add(newApexPkg);
+ var pair = mAllPackagesCache.get(i);
+ var oldApexInfo = pair.first;
+ var oldApexPkg = pair.second;
+ if (oldApexInfo.isActive && oldApexPkg.getPackageName().equals(packageName)) {
+ if (oldApexInfo.isFactory) {
+ oldApexInfo.isActive = false;
+ mAllPackagesCache.add(Pair.create(apexInfo, pkg));
} else {
- mAllPackagesCache.set(i, newApexPkg);
+ mAllPackagesCache.set(i, Pair.create(apexInfo, pkg));
}
break;
}
@@ -245,16 +246,6 @@
}
/**
- * Whether the APEX package is pre-installed or not.
- *
- * @param packageInfo the package to check
- * @return {@code true} if this package is pre-installed, {@code false} otherwise.
- */
- private static boolean isFactory(@NonNull PackageInfo packageInfo) {
- return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0;
- }
-
- /**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -288,33 +279,25 @@
HashSet<String> factoryPackagesSet = new HashSet<>();
for (ApexManager.ScanResult result : scanResults) {
ApexInfo ai = result.apexInfo;
-
- final PackageInfo packageInfo = PackageInfoWithoutStateUtils.generate(
- result.pkg, ai, flags);
- if (packageInfo == null) {
- throw new IllegalStateException("Unable to generate package info: "
- + ai.modulePath);
- }
- if (!packageInfo.packageName.equals(result.packageName)) {
+ String packageName = result.pkg.getPackageName();
+ if (!packageName.equals(result.packageName)) {
throw new IllegalStateException("Unmatched package name: "
- + result.packageName + " != " + packageInfo.packageName
+ + result.packageName + " != " + packageName
+ ", path=" + ai.modulePath);
}
- mAllPackagesCache.add(packageInfo);
+ mAllPackagesCache.add(Pair.create(ai, result.pkg));
if (ai.isActive) {
- if (!activePackagesSet.add(packageInfo.packageName)) {
+ if (!activePackagesSet.add(packageName)) {
throw new IllegalStateException(
- "Two active packages have the same name: "
- + packageInfo.packageName);
+ "Two active packages have the same name: " + packageName);
}
}
if (ai.isFactory) {
// Don't throw when the duplicating APEX is VNDK APEX
- if (!factoryPackagesSet.add(packageInfo.packageName)
+ if (!factoryPackagesSet.add(packageName)
&& !ai.moduleName.startsWith(VNDK_APEX_MODULE_NAME_PREFIX)) {
throw new IllegalStateException(
- "Two factory packages have the same name: "
- + packageInfo.packageName);
+ "Two factory packages have the same name: " + packageName);
}
}
}
@@ -348,6 +331,13 @@
if (throwable == null) {
// Calling hideAsFinal to assign derived fields for the app info flags.
parseResult.parsedPackage.hideAsFinal();
+
+ // TODO: When ENABLE_FEATURE_SCAN_APEX is finalized, remove this and the entire
+ // calling path code
+ ScanPackageUtils.applyPolicy(parseResult.parsedPackage,
+ PackageManagerService.SCAN_AS_SYSTEM,
+ mPackageManager == null ? null : mPackageManager.getPlatformPackage(),
+ false);
results.add(new ApexManager.ScanResult(
ai, parseResult.parsedPackage, parseResult.parsedPackage.getPackageName()));
} else if (throwable instanceof PackageManagerException) {
@@ -363,34 +353,69 @@
}
/**
- * Dump information about the packages contained in a particular cache
- * @param packagesCache the cache to print information about.
- * @param packageName a {@link String} containing a package name, or {@code null}. If set,
- * only information about that specific package will be dumped.
- * @param ipw the {@link IndentingPrintWriter} object to send information to.
+ * @see #dumpPackages(List, String, IndentingPrintWriter)
*/
- static void dumpPackages(List<PackageInfo> packagesCache,
+ static void dumpPackageStates(List<PackageStateInternal> packageStates, boolean isActive,
@Nullable String packageName, IndentingPrintWriter ipw) {
ipw.println();
ipw.increaseIndent();
- for (int i = 0, size = packagesCache.size(); i < size; i++) {
- final PackageInfo pi = packagesCache.get(i);
- if (packageName != null && !packageName.equals(pi.packageName)) {
+ for (int i = 0, size = packageStates.size(); i < size; i++) {
+ final var packageState = packageStates.get(i);
+ var pkg = packageState.getPkg();
+ if (packageName != null && !packageName.equals(pkg.getPackageName())) {
continue;
}
- ipw.println(pi.packageName);
+ ipw.println(pkg.getPackageName());
ipw.increaseIndent();
- ipw.println("Version: " + pi.versionCode);
- ipw.println("Path: " + pi.applicationInfo.sourceDir);
- ipw.println("IsActive: " + isActive(pi));
- ipw.println("IsFactory: " + isFactory(pi));
+ ipw.println("Version: " + pkg.getLongVersionCode());
+ ipw.println("Path: " + pkg.getBaseApkPath());
+ ipw.println("IsActive: " + isActive);
+ ipw.println("IsFactory: " + !packageState.isUpdatedSystemApp());
ipw.println("ApplicationInfo: ");
ipw.increaseIndent();
- pi.applicationInfo.dump(new PrintWriterPrinter(ipw), "");
+ // TODO: Dump the package manually
+ AndroidPackageUtils.generateAppInfoWithoutState(pkg)
+ .dump(new PrintWriterPrinter(ipw), "");
ipw.decreaseIndent();
ipw.decreaseIndent();
}
ipw.decreaseIndent();
ipw.println();
}
-}
+
+ /**
+ * Dump information about the packages contained in a particular cache
+ * @param packagesCache the cache to print information about.
+ * @param packageName a {@link String} containing a package name, or {@code null}. If set,
+ * only information about that specific package will be dumped.
+ * @param ipw the {@link IndentingPrintWriter} object to send information to.
+ */
+ static void dumpPackages(List<Pair<ApexInfo, AndroidPackage>> packagesCache,
+ @Nullable String packageName, IndentingPrintWriter ipw) {
+ ipw.println();
+ ipw.increaseIndent();
+ for (int i = 0, size = packagesCache.size(); i < size; i++) {
+ final var pair = packagesCache.get(i);
+ var apexInfo = pair.first;
+ var pkg = pair.second;
+ if (packageName != null && !packageName.equals(pkg.getPackageName())) {
+ continue;
+ }
+ ipw.println(pkg.getPackageName());
+ ipw.increaseIndent();
+ ipw.println("Version: " + pkg.getLongVersionCode());
+ ipw.println("Path: " + pkg.getBaseApkPath());
+ ipw.println("IsActive: " + apexInfo.isActive);
+ ipw.println("IsFactory: " + apexInfo.isFactory);
+ ipw.println("ApplicationInfo: ");
+ ipw.increaseIndent();
+ // TODO: Dump the package manually
+ AndroidPackageUtils.generateAppInfoWithoutState(pkg)
+ .dump(new PrintWriterPrinter(ipw), "");
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ ipw.println();
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 63c25ea..8ec3d2b 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -65,6 +65,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.apex.ApexInfo;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -1018,11 +1019,12 @@
if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
apexFlags = ApexManager.MATCH_FACTORY_PACKAGE;
}
- final PackageInfo pi = mApexPackageInfo.getPackageInfo(packageName, apexFlags);
- if (pi == null) {
+ final var pair = mApexPackageInfo.getPackageInfo(packageName, apexFlags);
+ if (pair == null) {
return null;
}
- return pi.applicationInfo;
+ return PackageInfoUtils.generateApplicationInfo(pair.second, flags,
+ PackageUserStateInternal.DEFAULT, userId, null);
}
}
if ("android".equals(packageName) || "system".equals(packageName)) {
@@ -1718,8 +1720,12 @@
// Instant app filtering for APEX modules is ignored
if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
if (matchApex) {
- return mApexPackageInfo.getPackageInfo(packageName,
+ final var pair = mApexPackageInfo.getPackageInfo(packageName,
ApexManager.MATCH_FACTORY_PACKAGE);
+ if (pair == null) {
+ return null;
+ }
+ return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId);
}
}
final PackageStateInternal ps = mSettings.getDisabledSystemPkg(packageName);
@@ -1775,8 +1781,12 @@
}
if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
if (matchApex) {
- return mApexPackageInfo.getPackageInfo(packageName,
+ final var pair = mApexPackageInfo.getPackageInfo(packageName,
ApexManager.MATCH_ACTIVE_PACKAGE);
+ if (pair == null) {
+ return null;
+ }
+ return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId);
}
}
return null;
@@ -1883,10 +1893,17 @@
}
if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
if (listApex) {
+ List<Pair<ApexInfo, AndroidPackage>> pairs;
if (listFactory) {
- list.addAll(mApexPackageInfo.getFactoryPackages());
+ pairs = mApexPackageInfo.getFactoryPackages();
} else {
- list.addAll(mApexPackageInfo.getActivePackages());
+ pairs = mApexPackageInfo.getActivePackages();
+ }
+
+ for (int index = 0; index < pairs.size(); index++) {
+ var pair = pairs.get(index);
+ list.add(PackageInfoUtils.generate(pair.second, pair.first, flags, null,
+ userId));
}
}
}
@@ -3404,29 +3421,23 @@
} // switch
}
- private void generateApexPackageInfo(List<PackageInfo> activePackages,
- List<PackageInfo> inactivePackages, List<PackageInfo> factoryPackages) {
+ private void generateApexPackageInfo(@NonNull List<PackageStateInternal> activePackages,
+ @NonNull List<PackageStateInternal> inactivePackages,
+ @NonNull List<PackageStateInternal> factoryActivePackages,
+ @NonNull List<PackageStateInternal> factoryInactivePackages) {
for (AndroidPackage p : mPackages.values()) {
final String packageName = p.getPackageName();
PackageStateInternal ps = mSettings.getPackage(packageName);
if (!p.isApex() || ps == null) {
continue;
}
- PackageInfo pi = generatePackageInfo(ps, 0, 0);
- if (pi == null) {
- continue;
- }
- pi.isActiveApex = true;
- activePackages.add(pi);
+ activePackages.add(ps);
if (!ps.isUpdatedSystemApp()) {
- factoryPackages.add(pi);
+ factoryActivePackages.add(ps);
} else {
PackageStateInternal psDisabled = mSettings.getDisabledSystemPkg(packageName);
- pi = generatePackageInfo(psDisabled, 0, 0);
- if (pi != null) {
- factoryPackages.add(pi);
- inactivePackages.add(pi);
- }
+ factoryInactivePackages.add(psDisabled);
+ inactivePackages.add(psDisabled);
}
}
}
@@ -3434,16 +3445,19 @@
private void dumpApex(PrintWriter pw, String packageName) {
if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
- List<PackageInfo> activePackages = new ArrayList<>();
- List<PackageInfo> inactivePackages = new ArrayList<>();
- List<PackageInfo> factoryPackages = new ArrayList<>();
- generateApexPackageInfo(activePackages, inactivePackages, factoryPackages);
+ List<PackageStateInternal> activePackages = new ArrayList<>();
+ List<PackageStateInternal> inactivePackages = new ArrayList<>();
+ List<PackageStateInternal> factoryActivePackages = new ArrayList<>();
+ List<PackageStateInternal> factoryInactivePackages = new ArrayList<>();
+ generateApexPackageInfo(activePackages, inactivePackages, factoryActivePackages,
+ factoryInactivePackages);
ipw.println("Active APEX packages:");
- ApexPackageInfo.dumpPackages(activePackages, packageName, ipw);
+ ApexPackageInfo.dumpPackageStates(activePackages, true, packageName, ipw);
ipw.println("Inactive APEX packages:");
- ApexPackageInfo.dumpPackages(inactivePackages, packageName, ipw);
+ ApexPackageInfo.dumpPackageStates(inactivePackages, false, packageName, ipw);
ipw.println("Factory APEX packages:");
- ApexPackageInfo.dumpPackages(factoryPackages, packageName, ipw);
+ ApexPackageInfo.dumpPackageStates(factoryActivePackages, true, packageName, ipw);
+ ApexPackageInfo.dumpPackageStates(factoryInactivePackages, false, packageName, ipw);
} else {
mApexPackageInfo.dump(pw, packageName);
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index d50e55e..1ea4d62 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -146,37 +146,9 @@
}
final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
+ VerificationUtils.processVerificationResponse(verificationId, state, response,
+ "Verification timed out", mPm);
- final VerifyingSession verifyingSession = state.getVerifyingSession();
- final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
-
- String errorMsg = "Verification timed out for " + originUri;
- Slog.i(TAG, errorMsg);
-
- final UserHandle user = verifyingSession.getUser();
- if (response.code != PackageManager.VERIFICATION_REJECT) {
- Slog.i(TAG, "Continuing with installation of " + originUri);
- state.setVerifierResponse(response.callerUid, response.code);
- VerificationUtils.broadcastPackageVerified(verificationId, originUri,
- PackageManager.VERIFICATION_ALLOW, null,
- verifyingSession.mDataLoaderType, user, mPm.mContext);
- } else {
- VerificationUtils.broadcastPackageVerified(verificationId, originUri,
- PackageManager.VERIFICATION_REJECT, null,
- verifyingSession.mDataLoaderType, user, mPm.mContext);
- verifyingSession.setReturnCode(
- PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
- state.setVerifierResponse(response.callerUid, response.code);
- }
-
- if (state.areAllVerificationsComplete()) {
- mPm.mPendingVerification.remove(verificationId);
- }
-
- Trace.asyncTraceEnd(
- TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
-
- verifyingSession.handleVerificationFinished();
break;
}
case CHECK_PENDING_INTEGRITY_VERIFICATION: {
@@ -231,31 +203,8 @@
}
final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
- state.setVerifierResponse(response.callerUid, response.code);
-
- if (state.isVerificationComplete()) {
- final VerifyingSession verifyingSession = state.getVerifyingSession();
- final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
-
- if (state.isInstallAllowed()) {
- VerificationUtils.broadcastPackageVerified(verificationId, originUri,
- response.code, null, verifyingSession.mDataLoaderType,
- verifyingSession.getUser(), mPm.mContext);
- } else {
- verifyingSession.setReturnCode(
- PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
- "Install not allowed");
- }
-
- if (state.areAllVerificationsComplete()) {
- mPm.mPendingVerification.remove(verificationId);
- }
-
- Trace.asyncTraceEnd(
- TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
-
- verifyingSession.handleVerificationFinished();
- }
+ VerificationUtils.processVerificationResponse(verificationId, state, response,
+ "Install not allowed", mPm);
break;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2bdf62bd..32b3e6a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1611,7 +1611,7 @@
mSharedLibraries = injector.getSharedLibrariesImpl();
mApexManager = testParams.apexManager;
- mApexPackageInfo = new ApexPackageInfo();
+ mApexPackageInfo = new ApexPackageInfo(this);
mArtManagerService = testParams.artManagerService;
mAvailableFeatures = testParams.availableFeatures;
mBackgroundDexOptService = testParams.backgroundDexOptService;
@@ -1811,7 +1811,7 @@
mProtectedPackages = new ProtectedPackages(mContext);
mApexManager = injector.getApexManager();
- mApexPackageInfo = new ApexPackageInfo();
+ mApexPackageInfo = new ApexPackageInfo(this);
mAppsFilter = mInjector.getAppsFilter();
mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
index 445b3a4..d105fc4 100644
--- a/services/core/java/com/android/server/pm/PackageVerificationState.java
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -137,15 +137,15 @@
* @return {@code true} if installation should be allowed
*/
boolean isInstallAllowed() {
- if (!mRequiredVerificationPassed) {
+ if (!mRequiredVerificationComplete) {
return false;
}
if (mSufficientVerificationComplete) {
- return mSufficientVerificationPassed;
+ return mRequiredVerificationPassed && mSufficientVerificationPassed;
}
- return true;
+ return mRequiredVerificationPassed;
}
/** Extend the timeout for this Package to be verified. */
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0e57c91..86affdd 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -823,8 +823,8 @@
* ideally be static, but, it requires locks to read system state.
*/
public static void applyPolicy(ParsedPackage parsedPackage,
- final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,
- boolean isUpdatedSystemApp) {
+ final @PackageManagerService.ScanFlags int scanFlags,
+ @Nullable AndroidPackage platformPkg, boolean isUpdatedSystemApp) {
if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
parsedPackage.setSystem(true);
// TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b7a5f70..e022f15 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -89,6 +89,7 @@
import android.provider.Settings;
import android.security.GateKeeper;
import android.service.gatekeeper.IGateKeeperService;
+import android.service.voice.VoiceInteractionManagerInternal;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -4366,6 +4367,11 @@
Binder.withCleanCallingIdentity(() -> {
mPm.onNewUserCreated(preCreatedUser.id, /* convertedFromPreCreated= */ true);
dispatchUserAdded(preCreatedUser, token);
+ VoiceInteractionManagerInternal vimi = LocalServices
+ .getService(VoiceInteractionManagerInternal.class);
+ if (vimi != null) {
+ vimi.onPreCreatedUserConversion(preCreatedUser.id);
+ }
});
return preCreatedUser;
}
diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java
index c132028..e1026b4 100644
--- a/services/core/java/com/android/server/pm/VerificationUtils.java
+++ b/services/core/java/com/android/server/pm/VerificationUtils.java
@@ -16,7 +16,10 @@
package com.android.server.pm;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
import static com.android.server.pm.PackageManagerService.PACKAGE_MIME_TYPE;
+import static com.android.server.pm.PackageManagerService.TAG;
import android.annotation.Nullable;
import android.content.Context;
@@ -24,8 +27,10 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Slog;
final class VerificationUtils {
/**
@@ -91,4 +96,40 @@
context.sendBroadcastAsUser(intent, user,
android.Manifest.permission.PACKAGE_VERIFICATION_AGENT);
}
+
+ static void processVerificationResponse(int verificationId, PackageVerificationState state,
+ PackageVerificationResponse response, String failureReason, PackageManagerService pms) {
+ state.setVerifierResponse(response.callerUid, response.code);
+ if (!state.isVerificationComplete()) {
+ return;
+ }
+
+ final VerifyingSession verifyingSession = state.getVerifyingSession();
+ final Uri originUri = Uri.fromFile(verifyingSession.mOriginInfo.mResolvedFile);
+
+ final int verificationCode =
+ state.isInstallAllowed() ? response.code : PackageManager.VERIFICATION_REJECT;
+
+ VerificationUtils.broadcastPackageVerified(verificationId, originUri,
+ verificationCode, null,
+ verifyingSession.mDataLoaderType, verifyingSession.getUser(),
+ pms.mContext);
+
+ if (state.isInstallAllowed()) {
+ Slog.i(TAG, "Continuing with installation of " + originUri);
+ } else {
+ String errorMsg = failureReason + " for " + originUri;
+ Slog.i(TAG, errorMsg);
+ verifyingSession.setReturnCode(
+ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
+ }
+
+ if (state.areAllVerificationsComplete()) {
+ pms.mPendingVerification.remove(verificationId);
+ }
+
+ Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
+
+ verifyingSession.handleVerificationFinished();
+ }
}
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index d49227d..905bcf9 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -64,7 +64,7 @@
COMPILATION_REASON_MAP.put(PackageManagerService.REASON_FIRST_BOOT, ArtStatsLog.
ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_FIRST_BOOT);
COMPILATION_REASON_MAP.put(PackageManagerService.REASON_BOOT_AFTER_OTA, ArtStatsLog.
- ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT);
+ ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT_AFTER_OTA);
COMPILATION_REASON_MAP.put(PackageManagerService.REASON_POST_BOOT, ArtStatsLog.
ART_DATUM_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_POST_BOOT);
COMPILATION_REASON_MAP.put(PackageManagerService.REASON_INSTALL, ArtStatsLog.
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 9c620c4..a44def8 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -93,12 +93,14 @@
/**
* @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
+ * @deprecated Once ENABLE_FEATURE_SCAN_APEX is removed, this should also be removed.
*/
+ @Deprecated
@Nullable
- public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, int flags,
- @Nullable PackageStateInternal pkgSetting) {
+ public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, long flags,
+ @Nullable PackageStateInternal pkgSetting, @UserIdInt int userId) {
return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
- PackageUserStateInternal.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
+ PackageUserStateInternal.DEFAULT, userId, apexInfo, pkgSetting);
}
/**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 7d4cfdf..164445c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -1698,7 +1698,8 @@
mDefaultPermissionCallback.onInstallPermissionGranted();
}
- public void onPermissionRevoked(int uid, int userId, String reason) {
+ public void onPermissionRevoked(int uid, int userId, String reason,
+ boolean overrideKill, @Nullable String permissionName) {
revokedPermissions.add(IntPair.of(uid, userId));
syncUpdatedUsers.add(userId);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 988b4eb..2a1748c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5788,6 +5788,17 @@
}
@Override // Binder call
+ public boolean areAutoPowerSaveModesEnabled() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableAutoPowerModes);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public boolean isPowerSaveMode() {
final long ident = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index d2a00af..cceb58d 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1020,6 +1020,7 @@
public boolean showAssistFromActivity(IBinder token, Bundle args) {
final long ident = Binder.clearCallingIdentity();
try {
+ final String callingAttributionTag;
synchronized (mGlobalLock) {
final ActivityRecord caller = ActivityRecord.forTokenLocked(token);
final Task topRootTask = mService.getTopDisplayFocusedRootTask();
@@ -1035,9 +1036,10 @@
+ " is not visible");
return false;
}
+ callingAttributionTag = top.launchedFromFeatureId;
}
return mAssistUtils.showSessionForActiveService(args, SHOW_SOURCE_APPLICATION,
- null /* showCallback */, token);
+ callingAttributionTag, null /* showCallback */, token);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1054,6 +1056,7 @@
@Override
public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) {
Slog.i(TAG, "Activity tried to startLocalVoiceInteraction");
+ final String callingAttributionTag;
synchronized (mGlobalLock) {
final Task topRootTask = mService.getTopDisplayFocusedRootTask();
final ActivityRecord activity = topRootTask != null
@@ -1071,9 +1074,10 @@
return;
}
activity.pendingVoiceInteractionStart = true;
+ callingAttributionTag = activity.launchedFromFeatureId;
}
LocalServices.getService(VoiceInteractionManagerInternal.class)
- .startLocalVoiceInteraction(callingActivity, options);
+ .startLocalVoiceInteraction(callingActivity, callingAttributionTag, options);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 69bbff3..135cf5d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -272,7 +272,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ConstrainDisplayApisConfig;
import android.content.pm.PackageManager;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -478,7 +477,6 @@
private int mLastReportedDisplayId;
boolean mLastReportedMultiWindowMode;
boolean mLastReportedPictureInPictureMode;
- CompatibilityInfo compat;// last used compatibility mode
ActivityRecord resultTo; // who started this entry, so will get our reply
final String resultWho; // additional identifier for use by resultTo.
final int requestCode; // code given by requester (resultTo)
@@ -1022,7 +1020,8 @@
if (rootVoiceInteraction) {
pw.print(prefix); pw.print("rootVoiceInteraction="); pw.println(rootVoiceInteraction);
}
- pw.print(prefix); pw.print("compat="); pw.print(compat);
+ pw.print(prefix); pw.print("compat=");
+ pw.print(mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo));
pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes));
pw.print(" icon=0x"); pw.print(Integer.toHexString(icon));
pw.print(" theme=0x"); pw.println(Integer.toHexString(theme));
@@ -2612,7 +2611,7 @@
// either way, abort and reset the sequence.
if (parcelable == null
|| mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
- || mStartingWindow == null
+ || mStartingWindow == null || mStartingWindow.mRemoved
|| finishing) {
if (parcelable != null) {
parcelable.clearIfNeeded();
@@ -4957,8 +4956,12 @@
ActivityOptions takeOptions() {
if (DEBUG_TRANSITION) Slog.i(TAG, "Taking options for " + this + " callers="
+ Debug.getCallers(6));
+ if (mPendingOptions == null) return null;
final ActivityOptions opts = mPendingOptions;
mPendingOptions = null;
+ // Strip sensitive information from options before sending it to app.
+ opts.setRemoteTransition(null);
+ opts.setRemoteAnimationAdapter(null);
return opts;
}
@@ -5334,7 +5337,11 @@
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
if (!delayed) {
+ // We aren't delayed anything, but exiting windows rely on the animation finished
+ // callback being called in case the ActivityRecord was pretending to be delayed,
+ // which we might have done because we were in closing/opening apps list.
if (!usingShellTransitions) {
+ onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
if (visible) {
// The token was made immediately visible, there will be no entrance animation.
// We need to inform the client the enter animation was finished.
@@ -9741,6 +9748,7 @@
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
target.setShowBackdrop(record.mShowBackdrop);
+ target.setWillShowImeOnTarget(mStartingData != null && mStartingData.hasImeSurface());
target.hasAnimatingParent = record.hasAnimatingParent();
return target;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4066fe1..e062c38 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -83,6 +83,7 @@
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -1658,9 +1659,6 @@
&& transitionController.getTransitionPlayer() != null)
? transitionController.createTransition(TRANSIT_OPEN) : null;
RemoteTransition remoteTransition = r.takeRemoteTransition();
- if (newTransition != null && remoteTransition != null) {
- newTransition.setRemoteTransition(remoteTransition);
- }
transitionController.collect(r);
try {
mService.deferWindowLayout();
@@ -2971,6 +2969,11 @@
errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
break;
}
+ case EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT: {
+ errMsg = "Cannot embed activity across TaskFragments for result, resultTo: "
+ + mStartActivity.resultTo;
+ break;
+ }
default:
errMsg = "Unhandled embed result:" + result;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 35e65a5..848e9a9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -222,6 +222,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationRunner;
+import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
@@ -1027,7 +1028,7 @@
mTaskSupervisor.setWindowManager(wm);
mRootWindowContainer.setWindowManager(wm);
if (mBackNavigationController != null) {
- mBackNavigationController.setTaskSnapshotController(wm.mTaskSnapshotController);
+ mBackNavigationController.setWindowManager(wm);
}
}
}
@@ -1833,13 +1834,14 @@
}
@Override
- public BackNavigationInfo startBackNavigation(boolean requestAnimation) {
+ public BackNavigationInfo startBackNavigation(boolean requestAnimation,
+ IWindowFocusObserver observer) {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startBackNavigation()");
if (mBackNavigationController == null) {
return null;
}
- return mBackNavigationController.startBackNavigation(mWindowManager, requestAnimation);
+ return mBackNavigationController.startBackNavigation(requestAnimation, observer);
}
/**
@@ -2566,6 +2568,9 @@
}
task = r.getTask();
}
+ // If {@code isSystemCaller} is {@code true}, it means the user intends to stop
+ // pinned mode through UI; otherwise, it's called by an app and we need to stop
+ // locked or pinned mode, subject to checks.
getLockTaskController().stopLockTaskMode(task, isSystemCaller, callingUid);
}
// Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
@@ -2984,7 +2989,7 @@
@Override
public boolean requestAssistDataForTask(IAssistDataReceiver receiver, int taskId,
- String callingPackageName) {
+ String callingPackageName, @Nullable String callingAttributionTag) {
mAmInternal.enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
"requestAssistDataForTask()");
final long callingId = Binder.clearCallingIdentity();
@@ -3011,7 +3016,7 @@
requester.requestAssistData(topActivityToken, true /* fetchData */,
false /* fetchScreenshot */, false /* fetchStructure */, true /* allowFetchData */,
false /* allowFetchScreenshot*/, true /* ignoreFocusCheck */,
- Binder.getCallingUid(), callingPackageName);
+ Binder.getCallingUid(), callingPackageName, callingAttributionTag);
return true;
}
@@ -5322,7 +5327,13 @@
}
@Override
- public void setRunningRemoteTransitionDelegate(IApplicationThread caller) {
+ public void setRunningRemoteTransitionDelegate(IApplicationThread delegate) {
+ final TransitionController controller = getTransitionController();
+ // A quick path without entering WM lock.
+ if (delegate != null && controller.mRemotePlayer.reportRunning(delegate)) {
+ // The delegate was known as running remote transition.
+ return;
+ }
mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
"setRunningRemoteTransition");
final int callingPid = Binder.getCallingPid();
@@ -5339,13 +5350,12 @@
Slog.e(TAG, msg);
throw new SecurityException(msg);
}
- final WindowProcessController wpc = getProcessController(caller);
+ final WindowProcessController wpc = getProcessController(delegate);
if (wpc == null) {
- Slog.w(TAG, "Unable to find process for application " + caller);
+ Slog.w(TAG, "setRunningRemoteTransition: no process for " + delegate);
return;
}
- wpc.setRunningRemoteAnimation(true /* running */);
- callingProc.addRemoteAnimationDelegate(wpc);
+ controller.mRemotePlayer.update(wpc, true /* running */, false /* predict */);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 8474068..345ec11 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -875,7 +875,6 @@
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
r.forceNewConfig = false;
mService.getAppWarningsLocked().onStartActivity(r);
- r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
// Because we could be starting an Activity in the system process this may not go
// across a Binder interface which would create a new Configuration. Consequently
@@ -905,7 +904,7 @@
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
- mergedConfiguration.getOverrideConfiguration(), r.compat,
+ mergedConfiguration.getOverrideConfiguration(),
r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
results, newIntents, r.takeOptions(), isTransitionForward,
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 1898cc6..219092b 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -204,11 +204,8 @@
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
final WindowToken token = mTargetWindowTokens.keyAt(i);
for (int j = token.getChildCount() - 1; j >= 0; j--) {
- // TODO(b/234585256): The consumer should be handleFinishDrawing(). And check why
- // the local window might easily time out.
- final WindowState w = token.getChildAt(j);
- if (w.isClientLocal()) continue;
- w.applyWithNextDraw(t -> {});
+ // TODO(b/234585256): The consumer should be handleFinishDrawing().
+ token.getChildAt(j).applyWithNextDraw(t -> {});
}
}
mIsSyncDrawRequested = true;
@@ -484,7 +481,16 @@
if (op == null) return false;
if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
if (op.mDrawTransaction == null) {
- op.mDrawTransaction = postDrawTransaction;
+ if (w.isClientLocal()) {
+ // Use a new transaction to merge the draw transaction of local window because the
+ // same instance will be cleared (Transaction#clear()) after reporting draw.
+ op.mDrawTransaction = mService.mTransactionFactory.get();
+ op.mDrawTransaction.merge(postDrawTransaction);
+ } else {
+ // The transaction read from parcel (the client is in a different process) is
+ // already a copy, so just reference it directly.
+ op.mDrawTransaction = postDrawTransaction;
+ }
} else {
op.mDrawTransaction.merge(postDrawTransaction);
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 9295c18..d9ab971 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -31,6 +31,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Slog;
+import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackNavigationInfo;
@@ -45,10 +46,9 @@
* Controller to handle actions related to the back gesture on the server side.
*/
class BackNavigationController {
-
private static final String TAG = "BackNavigationController";
- @Nullable
- private TaskSnapshotController mTaskSnapshotController;
+ private WindowManagerService mWindowManagerService;
+ private IWindowFocusObserver mFocusObserver;
/**
* Returns true if the back predictability feature is enabled
@@ -69,26 +69,13 @@
* for the animation, or null if we don't know how to animate the current window and need to
* fallback on dispatching the key event.
*/
- @Nullable
- BackNavigationInfo startBackNavigation(@NonNull WindowManagerService wmService,
- boolean requestAnimation) {
- return startBackNavigation(wmService, null, requestAnimation);
- }
-
- /**
- * @param tx, a transaction to be used for the attaching the animation leash.
- * This is used in tests. If null, the object will be initialized with a new {@link
- * SurfaceControl.Transaction}
- * @see #startBackNavigation(WindowManagerService, boolean)
- */
@VisibleForTesting
@Nullable
- BackNavigationInfo startBackNavigation(WindowManagerService wmService,
- @Nullable SurfaceControl.Transaction tx, boolean requestAnimation) {
-
- if (tx == null) {
- tx = new SurfaceControl.Transaction();
- }
+ BackNavigationInfo startBackNavigation(boolean requestAnimation,
+ IWindowFocusObserver observer) {
+ final WindowManagerService wmService = mWindowManagerService;
+ final SurfaceControl.Transaction tx = wmService.mTransactionFactory.get();
+ mFocusObserver = observer;
int backType = BackNavigationInfo.TYPE_UNDEFINED;
@@ -110,13 +97,14 @@
SurfaceControl animationLeashParent = null;
HardwareBuffer screenshotBuffer = null;
RemoteAnimationTarget topAppTarget = null;
+ WindowState window;
+
int prevTaskId;
int prevUserId;
boolean prepareAnimation;
BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder();
synchronized (wmService.mGlobalLock) {
- WindowState window;
WindowConfiguration taskWindowConfiguration;
WindowManagerInternal windowManagerInternal =
LocalServices.getService(WindowManagerInternal.class);
@@ -143,7 +131,6 @@
"Focused window found using getFocusedWindowToken");
}
- OnBackInvokedCallbackInfo overrideCallbackInfo = null;
if (window != null) {
// This is needed to bridge the old and new back behavior with recents. While in
// Overview with live tile enabled, the previous app is technically focused but we
@@ -152,15 +139,18 @@
// the right window to consume back while in overview, so we need to route it to
// launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing
// compat callback in VRI only works when the window is focused.
+ // This symptom also happen while shell transition enabled, we can check that by
+ // isTransientLaunch to know whether the focus window is point to live tile.
final RecentsAnimationController recentsAnimationController =
wmService.getRecentsAnimationController();
- if (recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(
- window.getActivityRecord())) {
- window = recentsAnimationController.getTargetAppMainWindow();
- overrideCallbackInfo = recentsAnimationController.getBackInvokedInfo();
+ final ActivityRecord ar = window.mActivityRecord;
+ if ((ar != null && ar.isActivityTypeHomeOrRecents()
+ && ar.mTransitionController.isTransientLaunch(ar))
+ || (recentsAnimationController != null
+ && recentsAnimationController.shouldApplyInputConsumer(ar))) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by "
+ "recents. Overriding back callback to recents controller callback.");
+ return null;
}
}
@@ -178,9 +168,7 @@
if (window != null) {
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
- callbackInfo = overrideCallbackInfo != null
- ? overrideCallbackInfo
- : window.getOnBackInvokedCallbackInfo();
+ callbackInfo = window.getOnBackInvokedCallbackInfo();
if (callbackInfo == null) {
Slog.e(TAG, "No callback registered, returning null.");
return null;
@@ -189,6 +177,9 @@
backType = BackNavigationInfo.TYPE_CALLBACK;
}
infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
+ if (mFocusObserver != null) {
+ window.registerFocusObserver(mFocusObserver);
+ }
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
@@ -209,17 +200,23 @@
|| currentActivity == null
|| currentTask == null
|| currentActivity.isActivityTypeHome()) {
- return infoBuilder
- .setType(backType)
- .build();
+ infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK);
+ final WindowState finalFocusedWindow = window;
+ infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
+ onBackNavigationDone(result, finalFocusedWindow, finalFocusedWindow,
+ BackNavigationInfo.TYPE_CALLBACK, null, null, false)));
+
+ return infoBuilder.setType(backType).build();
}
// We don't have an application callback, let's find the destination of the back gesture
Task finalTask = currentTask;
prevActivity = currentTask.getActivity(
(r) -> !r.finishing && r.getTask() == finalTask && !r.isTopRunningActivity());
- if (window.getParent().getChildCount() > 1 && window.getParent().getChildAt(0)
- != window) {
+ // TODO Dialog window does not need to attach on activity, check
+ // window.mAttrs.type != TYPE_BASE_APPLICATION
+ if ((window.getParent().getChildCount() > 1
+ && window.getParent().getChildAt(0) != window)) {
// Are we the top window of our parent? If not, we are a window on top of the
// activity, we won't close the activity.
backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
@@ -345,11 +342,12 @@
}
int finalBackType = backType;
- ActivityRecord finalprevActivity = prevActivity;
- Task finalTask = currentTask;
+ final ActivityRecord finalprevActivity = prevActivity;
+ final Task finalTask = currentTask;
+ final WindowState finalFocusedWindow = window;
RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
- result, finalRemovedWindowContainer, finalBackType, finalTask,
- finalprevActivity, prepareAnimation));
+ result, finalFocusedWindow, finalRemovedWindowContainer, finalBackType,
+ finalTask, finalprevActivity, prepareAnimation));
infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
}
@@ -382,8 +380,9 @@
}
private void onBackNavigationDone(
- Bundle result, WindowContainer<?> windowContainer, int backType,
- Task task, ActivityRecord prevActivity, boolean prepareAnimation) {
+ Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer,
+ int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity,
+ boolean prepareAnimation) {
SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
boolean triggerBack = result != null && result.getBoolean(
BackNavigationInfo.KEY_TRIGGER_BACK);
@@ -408,10 +407,15 @@
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
prevActivity);
}
- } else {
+ } else if (task != null) {
task.mBackGestureStarted = false;
}
resetSurfaces(windowContainer);
+
+ if (mFocusObserver != null) {
+ focusedWindow.unregisterFocusObserver(mFocusObserver);
+ mFocusObserver = null;
+ }
}
private HardwareBuffer getActivitySnapshot(@NonNull Task task,
@@ -425,10 +429,10 @@
}
private HardwareBuffer getTaskSnapshot(int taskId, int userId) {
- if (mTaskSnapshotController == null) {
+ if (mWindowManagerService.mTaskSnapshotController == null) {
return null;
}
- TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(taskId,
+ TaskSnapshot snapshot = mWindowManagerService.mTaskSnapshotController.getSnapshot(taskId,
userId, true /* restoreFromDisk */, false /* isLowResolution */);
return snapshot != null ? snapshot.getHardwareBuffer() : null;
}
@@ -458,7 +462,7 @@
}
}
- void setTaskSnapshotController(@Nullable TaskSnapshotController taskSnapshotController) {
- mTaskSnapshotController = taskSnapshotController;
+ void setWindowManager(WindowManagerService wm) {
+ mWindowManagerService = wm;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 863782a..0422906 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -207,6 +207,23 @@
return false;
}
+ @Override
+ public void setAlwaysOnTop(boolean alwaysOnTop) {
+ if (isAlwaysOnTop() == alwaysOnTop) {
+ return;
+ }
+ super.setAlwaysOnTop(alwaysOnTop);
+ // positionChildAtTop() must be called even when always on top gets turned off because
+ // we need to make sure that the display area is moved from among always on top containers
+ // to below other always on top containers. Since the position the display area should be
+ // inserted into is calculated properly in {@link DisplayContent#getTopInsertPosition()}
+ // in both cases, we can just request that the root task is put at top here.
+ if (getParent().asDisplayArea() != null) {
+ getParent().asDisplayArea().positionChildAt(POSITION_TOP, this,
+ false /* includingParents */);
+ }
+ }
+
boolean getIgnoreOrientationRequest() {
// Adding an exception for when ignoreOrientationRequest is overridden at runtime for all
// DisplayArea-s. For example, this is needed for the Kids Mode since many Kids apps aren't
@@ -234,6 +251,18 @@
// The min possible position we can insert the child at.
int minPosition = findMinPositionForChildDisplayArea(child);
+ // Place all non-always-on-top containers below always-on-top ones.
+ int alwaysOnTopCount = 0;
+ for (int i = minPosition; i <= maxPosition; i++) {
+ if (mChildren.get(i).isAlwaysOnTop()) {
+ alwaysOnTopCount++;
+ }
+ }
+ if (child.isAlwaysOnTop()) {
+ minPosition = maxPosition - alwaysOnTopCount + 1;
+ } else {
+ maxPosition -= alwaysOnTopCount;
+ }
return Math.max(Math.min(requestPosition, maxPosition), minPosition);
}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaGroup.java b/services/core/java/com/android/server/wm/DisplayAreaGroup.java
index 8488314..705327d 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaGroup.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaGroup.java
@@ -25,7 +25,10 @@
import android.content.res.Configuration;
import android.graphics.Rect;
+import com.android.internal.annotations.Keep;
+
/** The root of a partition of the logical display. */
+@Keep
class DisplayAreaGroup extends RootDisplayArea {
DisplayAreaGroup(WindowManagerService wms, String name, int featureId) {
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 8e21d96..f49e64e 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -38,6 +38,7 @@
import android.window.DisplayAreaOrganizer;
import android.window.WindowContainerToken;
+import com.android.internal.annotations.Keep;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
@@ -142,6 +143,7 @@
* the window to the corresponding {@link DisplayArea.Tokens} under the returned root
* {@link RootDisplayArea}.
*/
+@Keep
class DisplayAreaPolicyBuilder {
@Nullable private HierarchyBuilder mRootHierarchyBuilder;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 14e2241..25d187f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -318,12 +318,19 @@
*/
private final ArrayList<WindowState> mStatusBarBackgroundWindows = new ArrayList<>();
+ /**
+ * A collection of {@link LetterboxDetails} of all visible activities to be sent to SysUI in
+ * order to determine status bar appearance
+ */
+ private final ArrayList<LetterboxDetails> mLetterboxDetails = new ArrayList<>();
+
private String mFocusedApp;
private int mLastDisableFlags;
private int mLastAppearance;
private int mLastBehavior;
private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
+ private LetterboxDetails[] mLastLetterboxDetails;
/** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */
private final Rect mStatusBarColorCheckedBounds = new Rect();
@@ -1638,6 +1645,7 @@
mNavBarColorWindowCandidate = null;
mNavBarBackgroundWindow = null;
mStatusBarAppearanceRegionList.clear();
+ mLetterboxDetails.clear();
mStatusBarBackgroundWindows.clear();
mStatusBarColorCheckedBounds.setEmpty();
mStatusBarBackgroundCheckedBounds.setEmpty();
@@ -1717,6 +1725,16 @@
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
+ // Check if current activity is letterboxed in order create a LetterboxDetails
+ // component to be passed to SysUI for status bar treatment
+ final ActivityRecord currentActivity = win.getActivityRecord();
+ if (currentActivity != null) {
+ final LetterboxDetails currentLetterboxDetails = currentActivity
+ .mLetterboxUiController.getLetterboxDetails();
+ if (currentLetterboxDetails != null) {
+ mLetterboxDetails.add(currentLetterboxDetails);
+ }
+ }
}
}
@@ -1882,8 +1900,10 @@
/**
* Called when the resource overlays change.
*/
- public void onOverlayChangedLw() {
+ void onOverlayChanged() {
updateCurrentUserResources();
+ // Update the latest display size, cutout.
+ mDisplayContent.updateDisplayInfo();
onConfigurationChanged();
mSystemGestures.onConfigurationChanged();
}
@@ -2404,12 +2424,15 @@
callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
cause));
}
+ final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()];
+ mLetterboxDetails.toArray(letterboxDetails);
if (mLastAppearance == appearance
&& mLastBehavior == behavior
&& mRequestedVisibilities.equals(win.getRequestedVisibilities())
&& Objects.equals(mFocusedApp, focusedApp)
&& mLastFocusIsFullscreen == isFullscreen
- && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) {
+ && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)
+ && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) {
return;
}
if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
@@ -2425,9 +2448,10 @@
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
+ mLastLetterboxDetails = letterboxDetails;
callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
- requestedVisibilities, focusedApp, new LetterboxDetails[]{}));
+ requestedVisibilities, focusedApp, letterboxDetails));
}
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
@@ -2748,8 +2772,6 @@
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
mScreenshotHelper.takeScreenshot(screenshotType,
- getStatusBar() != null && getStatusBar().isVisible(),
- getNavigationBar() != null && getNavigationBar().isVisible(),
source, mHandler, null /* completionConsumer */);
}
}
@@ -2843,6 +2865,12 @@
pw.print(prefixInner); pw.println(mLastStatusBarAppearanceRegions[i]);
}
}
+ if (mLastLetterboxDetails != null) {
+ pw.print(prefix); pw.println("mLastLetterboxDetails=");
+ for (int i = mLastLetterboxDetails.length - 1; i >= 0; i--) {
+ pw.print(prefixInner); pw.println(mLastLetterboxDetails[i]);
+ }
+ }
if (!mStatusBarBackgroundWindows.isEmpty()) {
pw.print(prefix); pw.println("mStatusBarBackgroundWindows=");
for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
@@ -2907,7 +2935,10 @@
return;
}
- mDisplayContent.unregisterPointerEventListener(mPointerLocationView);
+ if (!mDisplayContent.isRemoved()) {
+ mDisplayContent.unregisterPointerEventListener(mPointerLocationView);
+ }
+
final WindowManager wm = mContext.getSystemService(WindowManager.class);
wm.removeView(mPointerLocationView);
mPointerLocationView = null;
@@ -2932,6 +2963,9 @@
mHandler.post(mGestureNavigationSettingsObserver::unregister);
mHandler.post(mForceShowNavBarSettingsObserver::unregister);
mImmersiveModeConfirmation.release();
+ if (mService.mPointerLocationEnabled) {
+ setPointerLocationEnabled(false);
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 86a73c9..bf4b65d 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -125,7 +125,8 @@
mDisplayContent = displayContent;
mStateController = stateController;
mFakeControl = new InsetsSourceControl(
- source.getType(), null /* leash */, new Point(), InsetsSourceControl.INVALID_HINTS);
+ source.getType(), null /* leash */, false /* initialVisible */, new Point(),
+ Insets.NONE);
mControllable = InsetsPolicy.isInsetsTypeControllable(source.getType());
}
@@ -468,7 +469,8 @@
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
updateVisibility();
- mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition, mInsetsHint);
+ mControl = new InsetsSourceControl(mSource.getType(), leash, mClientVisible,
+ surfacePosition, mInsetsHint);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
@@ -553,7 +555,8 @@
// to the client in case that the client applies its transaction sooner than ours
// that we could unexpectedly overwrite the surface state.
return new InsetsSourceControl(mControl.getType(), null /* leash */,
- mControl.getSurfacePosition(), mControl.getInsetsHint());
+ mControl.isInitiallyVisible(), mControl.getSurfacePosition(),
+ mControl.getInsetsHint());
}
return mControl;
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index d76f6be..1567fa7 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -209,8 +209,6 @@
if (keyguardChanged) {
// Irrelevant to AOD.
- dismissMultiWindowModeForTaskIfNeeded(displayId,
- null /* currentTaskControllsingOcclusion */);
state.mKeyguardGoingAway = false;
if (keyguardShowing) {
state.mDismissalRequested = false;
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index b5eff41..df3109a 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -138,6 +138,11 @@
return mInner;
}
+ /** @return The frame that contains the inner frame and the insets. */
+ Rect getOuterFrame() {
+ return mOuter;
+ }
+
/**
* Returns {@code true} if the letterbox does not overlap with the bar, or the letterbox can
* fully cover the window frame.
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 91b2fb6..57c60f4 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -300,7 +300,7 @@
/**
* Gets 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.
*/
Color getLetterboxBackgroundColor() {
if (mLetterboxBackgroundColorOverride != null) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d652767..ec9ee29 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -68,6 +68,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import java.io.PrintWriter;
@@ -141,6 +142,15 @@
}
}
+ /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */
+ private void getLetterboxOuterBounds(Rect outBounds) {
+ if (mLetterbox != null) {
+ outBounds.set(mLetterbox.getOuterFrame());
+ } else {
+ outBounds.setEmpty();
+ }
+ }
+
/**
* @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
* when the current activity is displayed.
@@ -683,4 +693,26 @@
mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
.logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
}
+
+ @Nullable
+ LetterboxDetails getLetterboxDetails() {
+ final WindowState w = mActivityRecord.findMainWindow();
+ if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) {
+ return null;
+ }
+ Rect letterboxInnerBounds = new Rect();
+ Rect letterboxOuterBounds = new Rect();
+ getLetterboxInnerBounds(letterboxInnerBounds);
+ getLetterboxOuterBounds(letterboxOuterBounds);
+
+ if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) {
+ return null;
+ }
+
+ return new LetterboxDetails(
+ letterboxInnerBounds,
+ letterboxOuterBounds,
+ w.mAttrs.insetsFlags.appearance
+ );
+ }
}
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 7a055d2..f11c2a7 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -481,27 +481,24 @@
*
* @param task the task that requested the end of lock task mode ({@code null} for quitting app
* pinning mode)
- * @param isSystemCaller indicates whether this request comes from the system via
- * {@link ActivityTaskManagerService#stopSystemLockTaskMode()}. If
- * {@code true}, it means the user intends to stop pinned mode through UI;
- * otherwise, it's called by an app and we need to stop locked or pinned
- * mode, subject to checks.
+ * @param stopAppPinning indicates whether to stop app pinning mode or to stop a task from
+ * being locked.
* @param callingUid the caller that requested the end of lock task mode.
* @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in
* foreground)
* @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if
* they differ from the one that launched lock task mode.
*/
- void stopLockTaskMode(@Nullable Task task, boolean isSystemCaller, int callingUid) {
+ void stopLockTaskMode(@Nullable Task task, boolean stopAppPinning, int callingUid) {
if (mLockTaskModeState == LOCK_TASK_MODE_NONE) {
return;
}
- if (isSystemCaller) {
+ if (stopAppPinning) {
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
clearLockedTasks("stopAppPinning");
} else {
- Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true");
+ Slog.e(TAG_LOCKTASK, "Attempted to stop app pinning while fully locked");
showLockTaskToast();
}
@@ -642,6 +639,10 @@
* @param callingUid the caller that requested the launch of lock task mode.
*/
void startLockTaskMode(@NonNull Task task, boolean isSystemCaller, int callingUid) {
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
+ ProtoLog.w(WM_DEBUG_LOCKTASK, "startLockTaskMode: Can't lock due to auth");
+ return;
+ }
if (!isSystemCaller) {
task.mLockTaskUid = callingUid;
if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
@@ -654,6 +655,11 @@
statusBarManager.showScreenPinningRequest(task.mTaskId);
}
return;
+ } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ // startLockTask() called by app, and app is part of lock task allowlist.
+ // Deactivate the currently pinned task before doing so.
+ Slog.i(TAG, "Stop app pinning before entering full lock task mode");
+ stopLockTaskMode(/* task= */ null, /* stopAppPinning= */ true, callingUid);
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 53f1fe6..5b702ea 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -19,12 +19,10 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -41,7 +39,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
@@ -54,18 +51,12 @@
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
-import android.view.InputDevice;
import android.view.InputWindowHandle;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.WindowInsets.Type;
-import android.window.BackEvent;
-import android.window.IOnBackInvokedCallback;
-import android.window.OnBackInvokedCallbackInfo;
import android.window.PictureInPictureSurfaceTransaction;
import android.window.TaskSnapshot;
@@ -195,46 +186,6 @@
}
};
- /**
- * Back invoked callback for legacy recents transition with the new back dispatch system.
- */
- final IOnBackInvokedCallback mBackCallback = new IOnBackInvokedCallback.Stub() {
- @Override
- public void onBackStarted() {
- // Do nothing
- }
-
- @Override
- public void onBackProgressed(BackEvent backEvent) {
- // Do nothing
- }
-
- @Override
- public void onBackCancelled() {
- // Do nothing
- }
-
- @Override
- public void onBackInvoked() {
- sendBackEvent(KeyEvent.ACTION_DOWN);
- sendBackEvent(KeyEvent.ACTION_UP);
- }
-
- private void sendBackEvent(int action) {
- if (mTargetActivityRecord == null) {
- return;
- }
- long when = SystemClock.uptimeMillis();
- final KeyEvent ev = new KeyEvent(when, when, action,
- KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
- KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
- InputDevice.SOURCE_KEYBOARD);
- ev.setDisplayId(mTargetActivityRecord.getDisplayId());
- InputManager.getInstance().injectInputEvent(ev, INJECT_INPUT_EVENT_MODE_ASYNC);
- }
- };
-
public interface RecentsAnimationCallbacks {
/** Callback when recents animation is finished. */
void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
@@ -1112,10 +1063,6 @@
return mTargetActivityRecord.findMainWindow();
}
- OnBackInvokedCallbackInfo getBackInvokedInfo() {
- return new OnBackInvokedCallbackInfo(mBackCallback, PRIORITY_DEFAULT);
- }
-
DisplayArea getTargetAppDisplayArea() {
if (mTargetActivityRecord == null) {
return null;
@@ -1236,6 +1183,12 @@
mLocalBounds, mBounds, mTask.getWindowConfiguration(),
mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(),
topApp.checkEnterPictureInPictureAppOpsState());
+
+ final ActivityRecord topActivity = mTask.getTopNonFinishingActivity();
+ if (topActivity != null && topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ mTarget.setWillShowImeOnTarget(true);
+ }
return mTarget;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 5a9058b..d8c3795 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -210,10 +210,6 @@
// Map from the PID to the top most app which has a focused window of the process.
final ArrayMap<Integer, ActivityRecord> mTopFocusedAppByProcess = new ArrayMap<>();
- // Only a separate transaction until we separate the apply surface changes
- // transaction from the global transaction.
- private final SurfaceControl.Transaction mDisplayTransaction;
-
// The tag for the token to put root tasks on the displays to sleep.
private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
@@ -458,7 +454,6 @@
RootWindowContainer(WindowManagerService service) {
super(service);
- mDisplayTransaction = service.mTransactionFactory.get();
mHandler = new MyHandler(service.mH.getLooper());
mService = service.mAtmService;
mTaskSupervisor = mService.mTaskSupervisor;
@@ -777,6 +772,10 @@
return leakedSurface || killedApps;
}
+ /**
+ * This method should only be called from {@link WindowSurfacePlacer}. Otherwise the recursion
+ * check and {@link WindowSurfacePlacer#isInLayout()} won't take effect.
+ */
void performSurfacePlacement() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
try {
@@ -999,19 +998,20 @@
mObscuringWindow = null;
// TODO(multi-display): Support these features on secondary screens.
- final DisplayContent defaultDc = mWmService.getDefaultDisplayContentLocked();
+ final DisplayContent defaultDc = mDefaultDisplay;
final DisplayInfo defaultInfo = defaultDc.getDisplayInfo();
final int defaultDw = defaultInfo.logicalWidth;
final int defaultDh = defaultInfo.logicalHeight;
+ final SurfaceControl.Transaction t = defaultDc.getSyncTransaction();
if (mWmService.mWatermark != null) {
- mWmService.mWatermark.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
+ mWmService.mWatermark.positionSurface(defaultDw, defaultDh, t);
}
if (mWmService.mStrictModeFlash != null) {
- mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
+ mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh, t);
}
if (mWmService.mEmulatorDisplayOverlay != null) {
mWmService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh,
- mWmService.getDefaultDisplayRotation(), mDisplayTransaction);
+ defaultDc.getRotation(), t);
}
final int count = mChildren.size();
@@ -1022,8 +1022,10 @@
// Give the display manager a chance to adjust properties like display rotation if it needs
// to.
- mWmService.mDisplayManagerInternal.performTraversal(mDisplayTransaction);
- SurfaceControl.mergeToGlobalTransaction(mDisplayTransaction);
+ mWmService.mDisplayManagerInternal.performTraversal(t);
+ if (t != defaultDc.mSyncTransaction) {
+ SurfaceControl.mergeToGlobalTransaction(t);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 3577545..0128c18 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -198,28 +198,31 @@
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
- outActiveControls, outAttachedFrame);
+ outActiveControls, outAttachedFrame, outSizeCompatScale);
}
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
- outAttachedFrame);
+ outAttachedFrame, outSizeCompatScale);
}
@Override
public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsState outInsetsState, Rect outAttachedFrame) {
+ int viewVisibility, int displayId, InsetsState outInsetsState, Rect outAttachedFrame,
+ float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */,
- outInsetsState, mDummyControls, outAttachedFrame);
+ outInsetsState, mDummyControls, outAttachedFrame, outSizeCompatScale);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e65f0bb..74e1fe4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5319,7 +5319,23 @@
parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK ||
parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP ||
(destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
- parent.deliverNewIntentLocked(callingUid, destIntent, destGrants, srec.packageName);
+ boolean abort;
+ try {
+ abort = !mTaskSupervisor.checkStartAnyActivityPermission(destIntent,
+ parent.info, null /* resultWho */, -1 /* requestCode */, srec.getPid(),
+ callingUid, srec.info.packageName, null /* callingFeatureId */,
+ false /* ignoreTargetSecurity */, false /* launchingInTask */, srec.app,
+ null /* resultRecord */, null /* resultRootTask */);
+ } catch (SecurityException e) {
+ abort = true;
+ }
+ if (abort) {
+ Slog.e(TAG, "Cannot navigateUpTo, intent =" + destIntent);
+ foundParentInTask = false;
+ } else {
+ parent.deliverNewIntentLocked(callingUid, destIntent, destGrants,
+ srec.packageName);
+ }
} else {
try {
ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1c20fb3..589618e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -165,6 +165,13 @@
* indicate that an Activity can't be embedded because the Activity is started on a new task.
*/
static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
+ /**
+ * An embedding check result of
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because the Activity is started on a new
+ * TaskFragment, e.g. start an Activity on a new TaskFragment for result.
+ */
+ static final int EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT = 4;
/**
* Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
@@ -175,6 +182,7 @@
EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
EMBEDDING_DISALLOWED_NEW_TASK,
+ EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
})
@interface EmbeddingCheckResult {}
@@ -393,6 +401,10 @@
mTaskFragmentOrganizerProcessName = processName;
}
+ void onTaskFragmentOrganizerRemoved() {
+ mTaskFragmentOrganizer = null;
+ }
+
/** Whether this TaskFragment is organized by the given {@code organizer}. */
boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
return organizer != null && mTaskFragmentOrganizer != null
@@ -567,9 +579,17 @@
if (!isAllowedToEmbedActivityInUntrustedMode(a)
&& !isAllowedToEmbedActivityInTrustedMode(a, uid)) {
return EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
- } else if (smallerThanMinDimension(a)) {
+ }
+
+ if (smallerThanMinDimension(a)) {
return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
}
+
+ // Cannot embed activity across TaskFragments for activity result.
+ if (a.resultTo != null && a.resultTo.getTaskFragment() != this) {
+ return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
+ }
+
return EMBEDDING_ALLOWED;
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index d4551be..a4d6f70 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -17,6 +17,12 @@
package com.android.server.wm;
import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_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;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
+import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -38,6 +44,7 @@
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentTransaction;
import com.android.internal.protolog.common.ProtoLog;
@@ -68,6 +75,11 @@
private final ArrayList<PendingTaskFragmentEvent> mPendingTaskFragmentEvents =
new ArrayList<>();
+ /** Map from {@link ITaskFragmentOrganizer} to {@link TaskFragmentTransaction}. */
+ private final ArrayMap<IBinder, TaskFragmentTransaction> mTmpOrganizerToTransactionMap =
+ new ArrayMap<>();
+ private final ArrayList<ITaskFragmentOrganizer> mTmpOrganizerList = new ArrayList<>();
+
TaskFragmentOrganizerController(ActivityTaskManagerService atm) {
mAtmService = atm;
mGlobalLock = atm.mGlobalLock;
@@ -136,113 +148,147 @@
void dispose() {
while (!mOrganizedTaskFragments.isEmpty()) {
final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
+ // Cleanup before remove to prevent it from sending any additional event, such as
+ // #onTaskFragmentVanished, to the removed organizer.
+ taskFragment.onTaskFragmentOrganizerRemoved();
taskFragment.removeImmediately();
mOrganizedTaskFragments.remove(taskFragment);
}
mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
}
- void onTaskFragmentAppeared(TaskFragment tf) {
+ @NonNull
+ TaskFragmentTransaction.Change prepareTaskFragmentAppeared(@NonNull TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
- try {
- mOrganizer.onTaskFragmentAppeared(info);
- mLastSentTaskFragmentInfos.put(tf, info);
- tf.mTaskFragmentAppearedSent = true;
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentAppeared callback", e);
+ tf.mTaskFragmentAppearedSent = true;
+ mLastSentTaskFragmentInfos.put(tf, info);
+ final TaskFragmentTransaction.Change change =
+ new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_APPEARED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskFragmentInfo(info);
+ if (shouldSendTaskFragmentParentInfoChanged(tf)) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task
+ final Task task = tf.getTask();
+ mLastSentTaskFragmentParentConfigs
+ .put(tf, new Configuration(task.getConfiguration()));
+ change.setTaskId(task.mTaskId)
+ .setTaskConfiguration(task.getConfiguration());
}
- onTaskFragmentParentInfoChanged(tf);
+ return change;
}
- void onTaskFragmentVanished(TaskFragment tf) {
+ @NonNull
+ TaskFragmentTransaction.Change prepareTaskFragmentVanished(@NonNull TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName());
- try {
- mOrganizer.onTaskFragmentVanished(tf.getTaskFragmentInfo());
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentVanished callback", e);
- }
tf.mTaskFragmentAppearedSent = false;
mLastSentTaskFragmentInfos.remove(tf);
mLastSentTaskFragmentParentConfigs.remove(tf);
+ return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_VANISHED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskFragmentInfo(tf.getTaskFragmentInfo());
}
- void onTaskFragmentInfoChanged(TaskFragment tf) {
- // Parent config may have changed. The controller will check if there is any important
- // config change for the organizer.
- onTaskFragmentParentInfoChanged(tf);
-
+ @Nullable
+ TaskFragmentTransaction.Change prepareTaskFragmentInfoChanged(
+ @NonNull TaskFragment tf) {
// Check if the info is different from the last reported info.
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
info.getConfiguration(), lastInfo.getConfiguration())) {
- return;
+ // Parent config may have changed. The controller will check if there is any
+ // important config change for the organizer.
+ return prepareTaskFragmentParentInfoChanged(tf);
}
+
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s",
tf.getName());
- try {
- mOrganizer.onTaskFragmentInfoChanged(info);
- mLastSentTaskFragmentInfos.put(tf, info);
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentInfoChanged callback", e);
+ mLastSentTaskFragmentInfos.put(tf, info);
+ final TaskFragmentTransaction.Change change =
+ new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_INFO_CHANGED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskFragmentInfo(info);
+ if (shouldSendTaskFragmentParentInfoChanged(tf)) {
+ // TODO(b/240519866): convert to pass TaskConfiguration for all TFs in the same Task
+ // at once.
+ // Parent config may have changed. The controller will check if there is any
+ // important config change for the organizer.
+ final Task task = tf.getTask();
+ mLastSentTaskFragmentParentConfigs
+ .put(tf, new Configuration(task.getConfiguration()));
+ change.setTaskId(task.mTaskId)
+ .setTaskConfiguration(task.getConfiguration());
}
+ return change;
}
- void onTaskFragmentParentInfoChanged(TaskFragment tf) {
- // Check if the parent info is different from the last reported parent info.
- if (tf.getParent() == null || tf.getParent().asTask() == null) {
- mLastSentTaskFragmentParentConfigs.remove(tf);
- return;
+ @Nullable
+ TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(
+ @NonNull TaskFragment tf) {
+ if (!shouldSendTaskFragmentParentInfoChanged(tf)) {
+ return null;
}
- final Task parent = tf.getParent().asTask();
+
+ final Task parent = tf.getTask();
final Configuration parentConfig = parent.getConfiguration();
- final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf);
- if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)
- && parentConfig.windowConfiguration.getWindowingMode()
- == lastParentConfig.windowConfiguration.getWindowingMode()) {
- return;
- }
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"TaskFragment parent info changed name=%s parentTaskId=%d",
tf.getName(), parent.mTaskId);
- try {
- mOrganizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig);
- mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig));
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e);
- }
+ mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig));
+ return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
+ .setTaskFragmentToken(tf.getFragmentToken())
+ .setTaskId(parent.mTaskId)
+ .setTaskConfiguration(parent.getConfiguration());
}
- void onTaskFragmentError(IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
- int opType, Throwable exception) {
+ /** Whether the system should report TaskFragment parent info changed to the organizer. */
+ private boolean shouldSendTaskFragmentParentInfoChanged(@NonNull TaskFragment tf) {
+ final Task parent = tf.getTask();
+ if (parent == null) {
+ // The TaskFragment is not attached.
+ mLastSentTaskFragmentParentConfigs.remove(tf);
+ return false;
+ }
+ // Check if the parent info is different from the last reported parent info.
+ final Configuration parentConfig = parent.getConfiguration();
+ final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf);
+ return !configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)
+ || parentConfig.windowConfiguration.getWindowingMode()
+ != lastParentConfig.windowConfiguration.getWindowingMode();
+ }
+
+ @NonNull
+ TaskFragmentTransaction.Change prepareTaskFragmentError(
+ @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
+ int opType, @NonNull Throwable exception) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Sending TaskFragment error exception=%s", exception.toString());
final TaskFragmentInfo info =
taskFragment != null ? taskFragment.getTaskFragmentInfo() : null;
final Bundle errorBundle = putErrorInfoInBundle(exception, info, opType);
- try {
- mOrganizer.onTaskFragmentError(errorCallbackToken, errorBundle);
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onTaskFragmentError callback", e);
- }
+ return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_ERROR)
+ .setErrorCallbackToken(errorCallbackToken)
+ .setErrorBundle(errorBundle);
}
- void onActivityReparentToTask(ActivityRecord activity) {
+ @Nullable
+ TaskFragmentTransaction.Change prepareActivityReparentToTask(
+ @NonNull ActivityRecord activity) {
if (activity.finishing) {
Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
- return;
+ return null;
}
final Task task = activity.getTask();
if (task == null || task.effectiveUid != mOrganizerUid) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not in a task belong to the organizer app.");
- return;
+ return null;
}
if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not allowed to be embedded.");
- return;
+ return null;
}
final IBinder activityToken;
@@ -265,11 +311,10 @@
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
activity.token, task.mTaskId);
- try {
- mOrganizer.onActivityReparentToTask(task.mTaskId, activity.intent, activityToken);
- } catch (RemoteException e) {
- Slog.d(TAG, "Exception sending onActivityReparentToTask callback", e);
- }
+ return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENT_TO_TASK)
+ .setTaskId(task.mTaskId)
+ .setActivityIntent(activity.intent)
+ .setActivityToken(activityToken);
}
}
@@ -372,7 +417,7 @@
*/
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
- ITaskFragmentOrganizer organizer, int taskId) {
+ @NonNull ITaskFragmentOrganizer organizer, int taskId) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
@@ -382,12 +427,13 @@
}
}
- int getTaskFragmentOrganizerUid(ITaskFragmentOrganizer organizer) {
+ int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
return state.mOrganizerUid;
}
- void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ void onTaskFragmentAppeared(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
if (!state.addTaskFragment(taskFragment)) {
return;
@@ -403,19 +449,20 @@
}
}
- void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ void onTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
handleTaskFragmentInfoChanged(organizer, taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
}
- void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer,
- TaskFragment taskFragment) {
+ void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
handleTaskFragmentInfoChanged(organizer, taskFragment,
PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED);
}
- private void handleTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer,
- TaskFragment taskFragment, int eventType) {
+ private void handleTaskFragmentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment, int eventType) {
validateAndGetState(organizer);
if (!taskFragment.mTaskFragmentAppearedSent) {
// Skip if TaskFragment still not appeared.
@@ -441,7 +488,8 @@
mPendingTaskFragmentEvents.add(pendingEvent);
}
- void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ void onTaskFragmentVanished(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragment taskFragment) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
@@ -464,8 +512,9 @@
state.removeTaskFragment(taskFragment);
}
- void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken,
- TaskFragment taskFragment, int opType, Throwable exception) {
+ void onTaskFragmentError(@NonNull ITaskFragmentOrganizer organizer,
+ @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
+ int opType, @NonNull Throwable exception) {
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder(
@@ -480,7 +529,7 @@
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
- void onActivityReparentToTask(ActivityRecord activity) {
+ void onActivityReparentToTask(@NonNull ActivityRecord activity) {
final ITaskFragmentOrganizer organizer;
if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
// If the activity is previously embedded in an organized TaskFragment.
@@ -512,10 +561,21 @@
mPendingTaskFragmentEvents.add(pendingEvent);
}
- private void removeOrganizer(ITaskFragmentOrganizer organizer) {
+ boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
+ return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
+ }
+
+ private void removeOrganizer(@NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state = validateAndGetState(organizer);
// remove all of the children of the organized TaskFragment
state.dispose();
+ // Remove any pending event of this organizer.
+ for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
+ final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
+ if (event.mTaskFragmentOrg.asBinder().equals(organizer.asBinder())) {
+ mPendingTaskFragmentEvents.remove(i);
+ }
+ }
mTaskFragmentOrganizerState.remove(organizer.asBinder());
}
@@ -525,7 +585,9 @@
* we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the
* {@link TaskFragment} after the organizer process died.
*/
- private TaskFragmentOrganizerState validateAndGetState(ITaskFragmentOrganizer organizer) {
+ @NonNull
+ private TaskFragmentOrganizerState validateAndGetState(
+ @NonNull ITaskFragmentOrganizer organizer) {
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(organizer.asBinder());
if (state == null) {
@@ -658,7 +720,7 @@
}
@Nullable
- private PendingTaskFragmentEvent getLastPendingLifecycleEvent(TaskFragment tf) {
+ private PendingTaskFragmentEvent getLastPendingLifecycleEvent(@NonNull TaskFragment tf) {
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
if (tf == entry.mTaskFragment && entry.isLifecycleEvent()) {
@@ -669,7 +731,7 @@
}
@Nullable
- private PendingTaskFragmentEvent getPendingTaskFragmentEvent(TaskFragment taskFragment,
+ private PendingTaskFragmentEvent getPendingTaskFragmentEvent(@NonNull TaskFragment taskFragment,
int type) {
for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
@@ -717,16 +779,36 @@
candidateEvents.add(event);
}
final int numEvents = candidateEvents.size();
+ if (numEvents == 0) {
+ return;
+ }
+
+ mTmpOrganizerToTransactionMap.clear();
+ mTmpOrganizerList.clear();
for (int i = 0; i < numEvents; i++) {
- dispatchEvent(candidateEvents.get(i));
+ final PendingTaskFragmentEvent event = candidateEvents.get(i);
+ if (!mTmpOrganizerToTransactionMap.containsKey(event.mTaskFragmentOrg.asBinder())) {
+ mTmpOrganizerToTransactionMap.put(event.mTaskFragmentOrg.asBinder(),
+ new TaskFragmentTransaction());
+ mTmpOrganizerList.add(event.mTaskFragmentOrg);
+ }
+ mTmpOrganizerToTransactionMap.get(event.mTaskFragmentOrg.asBinder())
+ .addChange(prepareChange(event));
}
- if (numEvents > 0) {
- mPendingTaskFragmentEvents.removeAll(candidateEvents);
+ final int numOrganizers = mTmpOrganizerList.size();
+ for (int i = 0; i < numOrganizers; i++) {
+ final ITaskFragmentOrganizer organizer = mTmpOrganizerList.get(i);
+ dispatchTransactionInfo(organizer,
+ mTmpOrganizerToTransactionMap.get(organizer.asBinder()));
}
+ mPendingTaskFragmentEvents.removeAll(candidateEvents);
+ mTmpOrganizerToTransactionMap.clear();
+ mTmpOrganizerList.clear();
}
- private static boolean isTaskVisible(Task task, ArrayList<Task> knownVisibleTasks,
- ArrayList<Task> knownInvisibleTasks) {
+ private static boolean isTaskVisible(@NonNull Task task,
+ @NonNull ArrayList<Task> knownVisibleTasks,
+ @NonNull ArrayList<Task> knownInvisibleTasks) {
if (knownVisibleTasks.contains(task)) {
return true;
}
@@ -742,44 +824,57 @@
}
}
- void dispatchPendingInfoChangedEvent(TaskFragment taskFragment) {
- PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
+ void dispatchPendingInfoChangedEvent(@NonNull TaskFragment taskFragment) {
+ final PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
if (event == null) {
return;
}
- dispatchEvent(event);
+ final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
+ transaction.addChange(prepareChange(event));
+ dispatchTransactionInfo(event.mTaskFragmentOrg, transaction);
mPendingTaskFragmentEvents.remove(event);
}
- private void dispatchEvent(PendingTaskFragmentEvent event) {
+ private void dispatchTransactionInfo(@NonNull ITaskFragmentOrganizer organizer,
+ @NonNull TaskFragmentTransaction transaction) {
+ if (transaction.isEmpty()) {
+ return;
+ }
+ try {
+ organizer.onTransactionReady(transaction);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Exception sending TaskFragmentTransaction", e);
+ }
+ }
+
+ @Nullable
+ private TaskFragmentTransaction.Change prepareChange(
+ @NonNull PendingTaskFragmentEvent event) {
final ITaskFragmentOrganizer taskFragmentOrg = event.mTaskFragmentOrg;
final TaskFragment taskFragment = event.mTaskFragment;
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(taskFragmentOrg.asBinder());
if (state == null) {
- return;
+ return null;
}
switch (event.mEventType) {
case PendingTaskFragmentEvent.EVENT_APPEARED:
- state.onTaskFragmentAppeared(taskFragment);
- break;
+ return state.prepareTaskFragmentAppeared(taskFragment);
case PendingTaskFragmentEvent.EVENT_VANISHED:
- state.onTaskFragmentVanished(taskFragment);
- break;
+ return state.prepareTaskFragmentVanished(taskFragment);
case PendingTaskFragmentEvent.EVENT_INFO_CHANGED:
- state.onTaskFragmentInfoChanged(taskFragment);
- break;
+ return state.prepareTaskFragmentInfoChanged(taskFragment);
case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED:
- state.onTaskFragmentParentInfoChanged(taskFragment);
- break;
+ return state.prepareTaskFragmentParentInfoChanged(taskFragment);
case PendingTaskFragmentEvent.EVENT_ERROR:
- state.onTaskFragmentError(event.mErrorCallbackToken, taskFragment, event.mOpType,
- event.mException);
- break;
+ return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
+ event.mOpType, event.mException);
case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
- state.onActivityReparentToTask(event.mActivity);
+ return state.prepareActivityReparentToTask(event.mActivity);
+ default:
+ throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 8b71a34..0a2e877 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -632,6 +632,11 @@
final Rect mainFrame = window.getRelativeFrame();
final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
+ if (adaptor.mAnimationLeash == null) {
+ Slog.e(TAG, "Cannot start starting window animation, the window " + window
+ + " was removed");
+ return null;
+ }
t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
return adaptor.mAnimationLeash;
}
@@ -679,13 +684,15 @@
if (topActivity != null) {
removalInfo.deferRemoveForIme = topActivity.mDisplayContent
.mayImeShowOnLaunchingActivity(topActivity);
- if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
- final WindowState mainWindow =
- topActivity.findMainWindow(false/* includeStartingApp */);
- if (mainWindow != null) {
- removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
- removalInfo.mainFrame = mainWindow.getRelativeFrame();
- }
+ final WindowState mainWindow =
+ topActivity.findMainWindow(false/* includeStartingApp */);
+ // No app window for this activity, app might be crashed.
+ // Remove starting window immediately without playing reveal animation.
+ if (mainWindow == null || mainWindow.mRemoved) {
+ removalInfo.playRevealAnimation = false;
+ } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+ removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
+ removalInfo.mainFrame = mainWindow.getRelativeFrame();
}
}
try {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 31d8eb8..bbc95a1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -54,6 +54,7 @@
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
@@ -1728,6 +1729,13 @@
if (task != null && task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
+ if (task != null) {
+ final ActivityRecord topActivity = task.getTopNonFinishingActivity();
+ if (topActivity != null && topActivity.mStartingData != null
+ && topActivity.mStartingData.hasImeSurface()) {
+ flags |= FLAG_WILL_IME_SHOWN;
+ }
+ }
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
if (record.mUseTransferredAnimation) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index a02be25..4f324f2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -45,6 +45,7 @@
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
@@ -77,9 +78,10 @@
final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
final TransitionTracer mTransitionTracer;
- private IApplicationThread mTransitionPlayerThread;
+ private WindowProcessController mTransitionPlayerProc;
final ActivityTaskManagerService mAtm;
final TaskSnapshotController mTaskSnapshotController;
+ final RemotePlayer mRemotePlayer;
private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
new ArrayList<>();
@@ -112,6 +114,7 @@
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
mAtm = atm;
+ mRemotePlayer = new RemotePlayer(atm);
mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
mTaskSnapshotController = taskSnapshotController;
mTransitionTracer = transitionTracer;
@@ -123,6 +126,8 @@
}
mPlayingTransitions.clear();
mTransitionPlayer = null;
+ mTransitionPlayerProc = null;
+ mRemotePlayer.clear();
mRunningLock.doNotifyLocked();
}
};
@@ -169,7 +174,7 @@
}
void registerTransitionPlayer(@Nullable ITransitionPlayer player,
- @Nullable IApplicationThread appThread) {
+ @Nullable WindowProcessController playerProc) {
try {
// Note: asBinder() can be null if player is same process (likely in a test).
if (mTransitionPlayer != null) {
@@ -182,7 +187,7 @@
player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
}
mTransitionPlayer = player;
- mTransitionPlayerThread = appThread;
+ mTransitionPlayerProc = playerProc;
} catch (RemoteException e) {
throw new RuntimeException("Unable to set transition player");
}
@@ -421,6 +426,7 @@
}
mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
transition.mType, info, remoteTransition, displayChange));
+ transition.setRemoteTransition(remoteTransition);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
transition.start();
@@ -473,9 +479,10 @@
// Collect all visible non-app windows which need to be drawn before the animation starts.
final DisplayContent dc = wc.asDisplayContent();
if (dc != null) {
+ final boolean noAsyncRotation = dc.getAsyncRotationController() == null;
wc.forAllWindows(w -> {
if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
- && dc.shouldSyncRotationChange(w)) {
+ && (noAsyncRotation || !AsyncRotationController.canBeAsync(w.mToken))) {
transition.collect(w.mToken);
}
}, true /* traverseTopToBottom */);
@@ -537,9 +544,7 @@
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
mPlayingTransitions.remove(record);
- if (mPlayingTransitions.isEmpty()) {
- setAnimationRunning(false /* running */);
- }
+ updateRunningRemoteAnimation(record, false /* isPlaying */);
record.finishTransition();
mRunningLock.doNotifyLocked();
}
@@ -549,21 +554,27 @@
throw new IllegalStateException("Trying to move non-collecting transition to playing");
}
mCollectingTransition = null;
- if (mPlayingTransitions.isEmpty()) {
- setAnimationRunning(true /* running */);
- }
mPlayingTransitions.add(transition);
+ updateRunningRemoteAnimation(transition, true /* isPlaying */);
mTransitionTracer.logState(transition);
}
- private void setAnimationRunning(boolean running) {
- if (mTransitionPlayerThread == null) return;
- final WindowProcessController wpc = mAtm.getProcessController(mTransitionPlayerThread);
- if (wpc == null) {
- Slog.w(TAG, "Unable to find process for player thread=" + mTransitionPlayerThread);
+ /** Updates the process state of animation player. */
+ private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
+ if (mTransitionPlayerProc == null) return;
+ if (isPlaying) {
+ mTransitionPlayerProc.setRunningRemoteAnimation(true);
+ } else if (mPlayingTransitions.isEmpty()) {
+ mTransitionPlayerProc.setRunningRemoteAnimation(false);
+ mRemotePlayer.clear();
return;
}
- wpc.setRunningRemoteAnimation(running);
+ final RemoteTransition remote = transition.getRemoteTransition();
+ if (remote == null) return;
+ final IApplicationThread appThread = remote.getAppThread();
+ final WindowProcessController delegate = mAtm.getProcessController(appThread);
+ if (delegate == null) return;
+ mRemotePlayer.update(delegate, isPlaying, true /* predict */);
}
void abort(Transition transition) {
@@ -666,6 +677,95 @@
proto.end(token);
}
+ /**
+ * This manages the animating state of processes that are running remote animations for
+ * {@link #mTransitionPlayerProc}.
+ */
+ static class RemotePlayer {
+ private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100;
+ @GuardedBy("itself")
+ private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>();
+ private final ActivityTaskManagerService mAtm;
+
+ private class DelegateProcess implements Runnable {
+ final WindowProcessController mProc;
+ /** Requires {@link RemotePlayer#reportRunning} to confirm it is really running. */
+ boolean mNeedReport;
+
+ DelegateProcess(WindowProcessController proc) {
+ mProc = proc;
+ }
+
+ /** This runs when the remote player doesn't report running in time. */
+ @Override
+ public void run() {
+ synchronized (mAtm.mGlobalLockWithoutBoost) {
+ update(mProc, false /* running */, false /* predict */);
+ }
+ }
+ }
+
+ RemotePlayer(ActivityTaskManagerService atm) {
+ mAtm = atm;
+ }
+
+ void update(@NonNull WindowProcessController delegate, boolean running, boolean predict) {
+ if (!running) {
+ synchronized (mDelegateProcesses) {
+ boolean removed = false;
+ for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
+ if (mDelegateProcesses.valueAt(i).mProc == delegate) {
+ mDelegateProcesses.removeAt(i);
+ removed = true;
+ break;
+ }
+ }
+ if (!removed) return;
+ }
+ delegate.setRunningRemoteAnimation(false);
+ return;
+ }
+ if (delegate.isRunningRemoteTransition() || !delegate.hasThread()) return;
+ delegate.setRunningRemoteAnimation(true);
+ final DelegateProcess delegateProc = new DelegateProcess(delegate);
+ // If "predict" is true, that means the remote animation is set from
+ // ActivityOptions#makeRemoteAnimation(). But it is still up to shell side to decide
+ // whether to use the remote animation, so there is a timeout to cancel the prediction
+ // if the remote animation doesn't happen.
+ if (predict) {
+ delegateProc.mNeedReport = true;
+ mAtm.mH.postDelayed(delegateProc, REPORT_RUNNING_GRACE_PERIOD_MS);
+ }
+ synchronized (mDelegateProcesses) {
+ mDelegateProcesses.put(delegate.getThread().asBinder(), delegateProc);
+ }
+ }
+
+ void clear() {
+ synchronized (mDelegateProcesses) {
+ for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) {
+ mDelegateProcesses.valueAt(i).mProc.setRunningRemoteAnimation(false);
+ }
+ mDelegateProcesses.clear();
+ }
+ }
+
+ /** Returns {@code true} if the app is known to be running remote transition. */
+ boolean reportRunning(@NonNull IApplicationThread appThread) {
+ final DelegateProcess delegate;
+ synchronized (mDelegateProcesses) {
+ delegate = mDelegateProcesses.get(appThread.asBinder());
+ if (delegate != null && delegate.mNeedReport) {
+ // It was predicted to run remote transition. Now it is really requesting so
+ // remove the timeout of restoration.
+ delegate.mNeedReport = false;
+ mAtm.mH.removeCallbacks(delegate);
+ }
+ }
+ return delegate != null;
+ }
+ }
+
static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bce131b..adb8bf6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3769,9 +3769,9 @@
// Check if this is changing displays. If so, mark the old display as "ready" for
// transitions. This is to work around the problem where setting readiness against this
// container will only set the new display as ready and leave the old display as unready.
- if (mSyncState != SYNC_STATE_NONE && oldParent != null
- && oldParent.getDisplayContent() != null && (newParent == null
- || oldParent.getDisplayContent() != newParent.getDisplayContent())) {
+ if (mSyncState != SYNC_STATE_NONE && oldParent != null && newParent != null
+ && oldParent.getDisplayContent() != null && newParent.getDisplayContent() != null
+ && oldParent.getDisplayContent() != newParent.getDisplayContent()) {
mTransitionController.setReady(oldParent.getDisplayContent());
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 440a91a..79037ab 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -117,6 +117,10 @@
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
@@ -1446,7 +1450,8 @@
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ float[] outSizeCompatScale) {
Arrays.fill(outActiveControls, null);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags
@@ -1863,11 +1868,15 @@
getInsetsSourceControls(win, outActiveControls);
if (win.mLayoutAttached) {
- outAttachedFrame.set(win.getParentWindow().getCompatFrame());
+ outAttachedFrame.set(win.getParentWindow().getFrame());
+ if (win.mInvGlobalScale != 1f) {
+ outAttachedFrame.scale(win.mInvGlobalScale);
+ }
} else {
// Make this invalid which indicates a null attached frame.
outAttachedFrame.set(0, 0, -1, -1);
}
+ outSizeCompatScale[0] = win.getSizeCompatScale();
}
Binder.restoreCallingIdentity(origId);
@@ -5605,7 +5614,7 @@
mWindowsInsetsChanged = 0;
// We need to update resizing windows and dispatch the new insets state
// to them.
- mRoot.performSurfacePlacement();
+ mWindowPlacerLocked.performSurfacePlacement();
}
}
break;
@@ -7032,13 +7041,14 @@
}
public void onOverlayChanged() {
- synchronized (mGlobalLock) {
- mRoot.forAllDisplays(displayContent -> {
- displayContent.getDisplayPolicy().onOverlayChangedLw();
- displayContent.updateDisplayInfo();
- });
- requestTraversal();
- }
+ // Post to display thread so it can get the latest display info.
+ mH.post(() -> {
+ synchronized (mGlobalLock) {
+ mAtmService.deferWindowLayout();
+ mRoot.forAllDisplays(dc -> dc.getDisplayPolicy().onOverlayChanged());
+ mAtmService.continueWindowLayout();
+ }
+ });
}
@Override
@@ -9282,4 +9292,33 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ /**
+ * Gets the background color of the letterbox. Considered invalid if the background has
+ * multiple colors {@link #isLetterboxBackgroundMultiColored}
+ */
+ @Override
+ public int getLetterboxBackgroundColorInArgb() {
+ return mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb();
+ }
+
+ /**
+ * Whether the outer area of the letterbox has multiple colors (e.g. blurred background).
+ */
+ @Override
+ public boolean isLetterboxBackgroundMultiColored() {
+ @LetterboxConfiguration.LetterboxBackgroundType int letterboxBackgroundType =
+ mLetterboxConfiguration.getLetterboxBackgroundType();
+ switch (letterboxBackgroundType) {
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+ case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+ case LETTERBOX_BACKGROUND_WALLPAPER:
+ return true;
+ case LETTERBOX_BACKGROUND_SOLID_COLOR:
+ return false;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox background type: " + letterboxBackgroundType);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0f5655c..95db746 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -37,6 +37,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -55,7 +56,6 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.IApplicationThread;
import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.Intent;
@@ -386,6 +386,12 @@
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller,
@Nullable Transition finishTransition) {
+ if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
+ .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
+ Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
+ + " is no longer registered");
+ return;
+ }
int effects = 0;
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
@@ -1068,6 +1074,18 @@
WindowContainer.fromBinder(hop.getContainer())
.removeLocalInsetsSourceProvider(hop.getInsetsTypes());
break;
+ case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ if (container == null || container.asDisplayArea() == null
+ || !container.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached display area: "
+ + container);
+ break;
+ }
+ container.setAlwaysOnTop(hop.isAlwaysOnTop());
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ break;
+
}
return effects;
}
@@ -1449,11 +1467,7 @@
synchronized (mGlobalLock) {
final WindowProcessController wpc =
mService.getProcessController(callerPid, callerUid);
- IApplicationThread appThread = null;
- if (wpc != null) {
- appThread = wpc.getThread();
- }
- mTransitionController.registerTransitionPlayer(player, appThread);
+ mTransitionController.registerTransitionPlayer(player, wpc);
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b39b3e4b..95b0645 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -79,7 +79,6 @@
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -229,10 +228,6 @@
/** Whether our process is currently running a {@link IRemoteAnimationRunner} */
private boolean mRunningRemoteAnimation;
- /** List of "chained" processes that are running remote animations for this process */
- private final ArrayList<WeakReference<WindowProcessController>> mRemoteAnimationDelegates =
- new ArrayList<>();
-
// The bits used for mActivityStateFlags.
private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -1677,30 +1672,7 @@
updateRunningRemoteOrRecentsAnimation();
}
- /**
- * Marks another process as a "delegate" animator. This means that process is doing some part
- * of a remote animation on behalf of this process.
- */
- void addRemoteAnimationDelegate(WindowProcessController delegate) {
- if (!isRunningRemoteTransition()) {
- throw new IllegalStateException("Can't add a delegate to a process which isn't itself"
- + " running a remote animation");
- }
- mRemoteAnimationDelegates.add(new WeakReference<>(delegate));
- }
-
void updateRunningRemoteOrRecentsAnimation() {
- if (!isRunningRemoteTransition()) {
- // Clean-up any delegates
- for (int i = 0; i < mRemoteAnimationDelegates.size(); ++i) {
- final WindowProcessController delegate = mRemoteAnimationDelegates.get(i).get();
- if (delegate == null) continue;
- delegate.setRunningRemoteAnimation(false);
- delegate.setRunningRecentsAnimation(false);
- }
- mRemoteAnimationDelegates.clear();
- }
-
// Posting on handler so WM lock isn't held when we call into AM.
mAtm.mH.sendMessage(PooledLambda.obtainMessage(
WindowProcessListener::setRunningRemoteAnimation, mListener,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 366ca3b..283010e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -471,11 +471,12 @@
int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
// Current transformation being applied.
- float mGlobalScale=1;
- float mInvGlobalScale=1;
+ float mGlobalScale = 1f;
+ float mInvGlobalScale = 1f;
+ float mSizeCompatScale = 1f;
final float mOverrideScale;
- float mHScale=1, mVScale=1;
- float mLastHScale=1, mLastVScale=1;
+ float mHScale = 1f, mVScale = 1f;
+ float mLastHScale = 1f, mLastVScale = 1f;
// An offset in pixel of the surface contents from the window position. Used for Wallpaper
// to provide the effect of scrolling within a large surface. We just use these values as
@@ -1251,18 +1252,19 @@
void updateGlobalScale() {
if (hasCompatScale()) {
- if (mOverrideScale != 1f) {
- mGlobalScale = mToken.hasSizeCompatBounds()
- ? mToken.getSizeCompatScale() * mOverrideScale
- : mOverrideScale;
- } else {
- mGlobalScale = mToken.getSizeCompatScale();
- }
+ mSizeCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
+ ? mToken.getSizeCompatScale()
+ : 1f;
+ mGlobalScale = mSizeCompatScale * mOverrideScale;
mInvGlobalScale = 1f / mGlobalScale;
return;
}
- mGlobalScale = mInvGlobalScale = 1f;
+ mGlobalScale = mInvGlobalScale = mSizeCompatScale = 1f;
+ }
+
+ float getSizeCompatScale() {
+ return mSizeCompatScale;
}
/**
@@ -1353,7 +1355,7 @@
windowFrames.mFrame.set(clientWindowFrames.frame);
windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
- if (hasCompatScale()) {
+ if (mGlobalScale != 1f) {
// The frames sent from the client need to be adjusted to the real coordinate space.
windowFrames.mFrame.scale(mGlobalScale);
windowFrames.mDisplayFrame.scale(mGlobalScale);
@@ -1365,7 +1367,7 @@
windowFrames.mFrame.set(clientWindowFrames.frame);
windowFrames.mCompatFrame.set(windowFrames.mFrame);
- if (hasCompatScale()) {
+ if (mInvGlobalScale != 1f) {
// Also, the scaled frame that we report to the app needs to be adjusted to be in
// its coordinate space.
windowFrames.mCompatFrame.scale(mInvGlobalScale);
@@ -1470,10 +1472,6 @@
return mWindowFrames.mParentFrame;
}
- Rect getCompatFrame() {
- return mWindowFrames.mCompatFrame;
- }
-
WindowManager.LayoutParams getAttrs() {
return mAttrs;
}
@@ -1727,7 +1725,7 @@
*/
InsetsState getCompatInsetsState() {
InsetsState state = getInsetsState();
- if (hasCompatScale()) {
+ if (mInvGlobalScale != 1f) {
state = new InsetsState(state, true);
state.scale(mInvGlobalScale);
}
@@ -2454,8 +2452,7 @@
dc.setImeLayeringTarget(null);
dc.computeImeTarget(true /* updateImeTarget */);
}
- if (dc.getImeInputTarget() == this
- && (mActivityRecord == null || !mActivityRecord.isRelaunching())) {
+ if (dc.getImeInputTarget() == this && !inRelaunchingActivity()) {
dc.updateImeInputAndControlTarget(null);
}
@@ -2582,7 +2579,10 @@
// usually unnoticeable (e.g. covered by rotation animation) and the animation
// bounds could be inconsistent, such as depending on when the window applies
// its draw transaction with new rotation.
- final boolean allowExitAnimation = !getDisplayContent().inTransition();
+ final boolean allowExitAnimation = !getDisplayContent().inTransition()
+ // There will be a new window so the exit animation may not be visible or
+ // look weird if its orientation is changed.
+ && !inRelaunchingActivity();
if (wasVisible) {
final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
@@ -3836,15 +3836,19 @@
boolean relayoutVisible) {
outFrames.frame.set(mWindowFrames.mCompatFrame);
outFrames.displayFrame.set(mWindowFrames.mDisplayFrame);
- if (mInvGlobalScale != 1.0f && hasCompatScale()) {
+ if (mInvGlobalScale != 1f) {
outFrames.displayFrame.scale(mInvGlobalScale);
}
if (mLayoutAttached) {
if (outFrames.attachedFrame == null) {
outFrames.attachedFrame = new Rect();
}
- outFrames.attachedFrame.set(getParentWindow().getCompatFrame());
+ outFrames.attachedFrame.set(getParentWindow().getFrame());
+ if (mInvGlobalScale != 1f) {
+ outFrames.attachedFrame.scale(mInvGlobalScale);
+ }
}
+ outFrames.sizeCompatScale = mSizeCompatScale;
// Note: in the cases where the window is tied to an activity, we should not send a
// configuration update when the window has requested to be hidden. Doing so can lead to
@@ -3868,7 +3872,7 @@
// If the activity is scheduled to relaunch, skip sending the resized to ViewRootImpl now
// since it will be destroyed anyway. This also prevents the client from receiving
// windowing mode change before it is destroyed.
- if (mActivityRecord != null && mActivityRecord.isRelaunching()) {
+ if (inRelaunchingActivity()) {
return;
}
// If this is an activity or wallpaper and is invisible or going invisible, don't report
@@ -3954,6 +3958,10 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ boolean inRelaunchingActivity() {
+ return mActivityRecord != null && mActivityRecord.isRelaunching();
+ }
+
boolean isClientLocal() {
return mClient instanceof IWindow.Stub;
}
@@ -4344,7 +4352,7 @@
pw.println(prefix + "mHasSurface=" + mHasSurface
+ " isReadyForDisplay()=" + isReadyForDisplay()
+ " mWindowRemovalAllowed=" + mWindowRemovalAllowed);
- if (hasCompatScale()) {
+ if (mInvGlobalScale != 1f) {
pw.println(prefix + "mCompatFrame=" + mWindowFrames.mCompatFrame.toShortString(sTmpSB));
}
if (dumpAll) {
@@ -4556,7 +4564,7 @@
float translateToWindowX(float x) {
float winX = x - mWindowFrames.mFrame.left;
- if (hasCompatScale()) {
+ if (mGlobalScale != 1f) {
winX *= mGlobalScale;
}
return winX;
@@ -4564,7 +4572,7 @@
float translateToWindowY(float y) {
float winY = y - mWindowFrames.mFrame.top;
- if (hasCompatScale()) {
+ if (mGlobalScale != 1f) {
winY *= mGlobalScale;
}
return winY;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index bbb21f8..72e7e65 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -46,6 +46,7 @@
import android.view.InsetsState;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
import android.window.WindowContext;
@@ -558,6 +559,12 @@
// The window may be detached or detaching.
return;
}
+ if (mTransitionController.isShellTransitionsEnabled()
+ && asActivityRecord() != null && isVisible()) {
+ // Trigger an activity level rotation transition.
+ mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
+ mTransitionController.setReady(this);
+ }
final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
onCancelFixedRotationTransform(originalRotation);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 74ed3df..891bc0f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -462,10 +462,10 @@
mInputManager->getReader().dump(dump);
dump += "\n";
- mInputManager->getUnwantedInteractionBlocker().dump(dump);
+ mInputManager->getBlocker().dump(dump);
dump += "\n";
- mInputManager->getClassifier().dump(dump);
+ mInputManager->getProcessor().dump(dump);
dump += "\n";
mInputManager->getDispatcher().dump(dump);
@@ -702,7 +702,7 @@
void NativeInputManager::notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) {
ATRACE_CALL();
- mInputManager->getUnwantedInteractionBlocker().notifyInputDevicesChanged(inputDevices);
+ mInputManager->getBlocker().notifyInputDevicesChanged(inputDevices);
JNIEnv* env = jniEnv();
size_t count = inputDevices.size();
@@ -1483,7 +1483,7 @@
}
void NativeInputManager::setMotionClassifierEnabled(bool enabled) {
- mInputManager->getClassifier().setMotionClassifierEnabled(enabled);
+ mInputManager->getProcessor().setMotionClassifierEnabled(enabled);
}
bool NativeInputManager::isPerDisplayTouchModeEnabled() {
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index e44ce3c..31c49b3 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -34,5 +34,10 @@
],
platform_apis: true,
test_suites: ["device-tests"],
- data: [":AppEnumerationSyncProviderTestApp"],
+ data: [
+ ":AppEnumerationCrossUserPackageVisibilityTestApp",
+ ":AppEnumerationHasAppOpPermissionTestApp",
+ ":AppEnumerationSharedUserTestApp",
+ ":AppEnumerationSyncProviderTestApp",
+ ],
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 102d17c..f9a60b6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -254,6 +254,7 @@
private Injector mInjector;
private volatile long mNowElapsedTest;
private volatile long mNowRtcTest;
+ private volatile int mTestCallingUid = TEST_CALLING_UID;
@GuardedBy("mTestTimer")
private TestTimer mTestTimer = new TestTimer();
@@ -328,7 +329,7 @@
@Override
int getCallingUid() {
- return TEST_CALLING_UID;
+ return mTestCallingUid;
}
@Override
@@ -1395,7 +1396,7 @@
setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent());
}
assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
- mService.removeLocked(TEST_CALLING_PACKAGE);
+ mService.removeLocked(TEST_CALLING_PACKAGE, REMOVE_REASON_UNDEFINED);
assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
}
@@ -2683,6 +2684,59 @@
}
@Test
+ public void removeAllBinderCall() throws RemoteException {
+ for (int i = 0; i < 10; i++) {
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, getNewMockPendingIntent());
+ }
+
+ final String otherUidPackage1 = "other.uid.package1";
+ final String otherUidPackage2 = "other.uid.package2";
+ final int otherUid = 1243;
+
+ registerAppIds(
+ new String[]{TEST_CALLING_PACKAGE, otherUidPackage1, otherUidPackage2},
+ new Integer[]{TEST_CALLING_UID, otherUid, otherUid}
+ );
+
+ for (int i = 0; i < 9; i++) {
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 11, 0,
+ getNewMockPendingIntent(otherUid, otherUidPackage1), 0, 0, otherUid,
+ otherUidPackage1, null);
+ }
+
+ for (int i = 0; i < 8; i++) {
+ setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 20, 0,
+ getNewMockPendingIntent(otherUid, otherUidPackage2), 0, 0, otherUid,
+ otherUidPackage2, null);
+ }
+
+ assertEquals(27, mService.mAlarmStore.size());
+
+ try {
+ mBinder.removeAll(otherUidPackage1);
+ fail("removeAll() for wrong package did not throw SecurityException");
+ } catch (SecurityException se) {
+ // Expected
+ }
+
+ try {
+ mBinder.removeAll(otherUidPackage2);
+ fail("removeAll() for wrong package did not throw SecurityException");
+ } catch (SecurityException se) {
+ // Expected
+ }
+
+ mBinder.removeAll(TEST_CALLING_PACKAGE);
+ assertEquals(17, mService.mAlarmStore.size());
+ assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(TEST_CALLING_PACKAGE)));
+
+ mTestCallingUid = otherUid;
+ mBinder.removeAll(otherUidPackage1);
+ assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage1)));
+ assertEquals(8, mService.mAlarmStore.getCount(a -> a.matches(otherUidPackage2)));
+ }
+
+ @Test
public void minWindowChangeEnabled() {
mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true);
final int minWindow = 73;
@@ -3577,7 +3631,7 @@
getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
// refresh the state.
- mService.removeLocked(TEST_CALLING_PACKAGE);
+ mService.removeLocked(TEST_CALLING_PACKAGE, REMOVE_REASON_UNDEFINED);
mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
@@ -3586,7 +3640,7 @@
getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
// refresh the state.
- mService.removeLocked(TEST_CALLING_PACKAGE);
+ mService.removeLocked(TEST_CALLING_PACKAGE, REMOVE_REASON_UNDEFINED);
mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
new file mode 100644
index 0000000..6a18dc1
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+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;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerControllerTest {
+ private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
+ private static final int DISPLAY_ID = 42;
+
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private Handler mHandler;
+ private DisplayPowerController.Injector mInjector;
+ private Context mContextSpy;
+
+ @Mock
+ private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+ @Mock
+ private SensorManager mSensorManagerMock;
+ @Mock
+ private DisplayBlanker mDisplayBlankerMock;
+ @Mock
+ private LogicalDisplay mLogicalDisplayMock;
+ @Mock
+ private DisplayDevice mDisplayDeviceMock;
+ @Mock
+ private BrightnessTracker mBrightnessTrackerMock;
+ @Mock
+ private BrightnessSetting mBrightnessSettingMock;
+ @Mock
+ private WindowManagerPolicy mWindowManagerPolicyMock;
+ @Mock
+ private PowerManager mPowerManagerMock;
+ @Mock
+ private Resources mResourcesMock;
+ @Mock
+ private DisplayDeviceConfig mDisplayDeviceConfigMock;
+ @Mock
+ private DisplayPowerState mDisplayPowerStateMock;
+ @Mock
+ private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
+
+ @Captor
+ private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ mHandler = new Handler(mTestLooper.getLooper());
+ mInjector = new DisplayPowerController.Injector() {
+ @Override
+ DisplayPowerController.Clock getClock() {
+ return mClock::now;
+ }
+
+ @Override
+ DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+ int displayId, int displayState) {
+ return mDisplayPowerStateMock;
+ }
+
+ @Override
+ DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+ FloatProperty<DisplayPowerState> firstProperty,
+ FloatProperty<DisplayPowerState> secondProperty) {
+ return mDualRampAnimatorMock;
+ }
+ };
+
+ addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+
+ when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
+ when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ }
+
+ @Test
+ public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+ setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+
+ Sensor proxSensor = setUpProxSensor();
+
+ DisplayPowerController dpc = new DisplayPowerController(
+ mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+ mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+ mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+ });
+
+ when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+ // send a display power request
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+ dpr.useProximitySensor = true;
+ dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+ // Run updatePowerState to start listener for the prox sensor
+ advanceTime(1);
+
+ SensorEventListener listener = getSensorEventListener(proxSensor);
+ assertNotNull(listener);
+
+ listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+ advanceTime(1);
+
+ // two times, one for unfinished business and one for proximity
+ verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+ dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+ dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+
+ dpc.stop();
+ advanceTime(1);
+
+ // two times, one for unfinished business and one for proximity
+ verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+ dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+ verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+ dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+ }
+
+ /**
+ * Creates a mock and registers it to {@link LocalServices}.
+ */
+ private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+ LocalServices.removeServiceForTest(clazz);
+ LocalServices.addService(clazz, mock);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ private Sensor setUpProxSensor() throws Exception {
+ Sensor proxSensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY);
+ when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(proxSensor));
+ return proxSensor;
+ }
+
+ private SensorEventListener getSensorEventListener(Sensor sensor) {
+ verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+ eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+ return mSensorEventListenerCaptor.getValue();
+ }
+
+ private void setUpDisplay(int displayId, String uniqueId) {
+ DisplayInfo info = new DisplayInfo();
+ DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+
+ when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+ when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
+ when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+ when(mLogicalDisplayMock.isEnabled()).thenReturn(true);
+ when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+ when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+ when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+ when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
+ when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+ }
+}
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 2f61908..57a8013 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
@@ -62,6 +62,7 @@
private static final int SOURCE_USER_ID = 0;
private BatteryController mBatteryController;
+ private FlexibilityController mFlexibilityController;
private BroadcastReceiver mPowerReceiver;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
private int mSourceUid;
@@ -99,7 +100,8 @@
// Capture the listeners.
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
- mBatteryController = new BatteryController(mJobSchedulerService);
+ mFlexibilityController = new FlexibilityController(mJobSchedulerService);
+ mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
verify(mContext).registerReceiver(receiverCaptor.capture(),
ArgumentMatchers.argThat(filter ->
@@ -211,6 +213,63 @@
}
@Test
+ public void testFlexibilityController_BatteryNotLow() {
+ setBatteryNotLow(false);
+ assertFalse(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ setBatteryNotLow(true);
+ assertTrue(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ setBatteryNotLow(false);
+ assertFalse(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ }
+
+ @Test
+ public void testFlexibilityController_Charging() {
+ setDischarging();
+ assertFalse(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ setCharging();
+ assertTrue(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ setDischarging();
+ assertFalse(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ }
+
+ @Test
+ public void testFlexibilityController_Charging_BatterNotLow() {
+ setDischarging();
+ setBatteryNotLow(false);
+
+ assertFalse(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ assertFalse(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ setCharging();
+
+ assertFalse(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ assertTrue(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ setBatteryNotLow(true);
+
+ assertTrue(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ assertTrue(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ setDischarging();
+
+ assertTrue(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ assertFalse(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+
+ setBatteryNotLow(false);
+
+ assertFalse(
+ mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW));
+ assertFalse(mFlexibilityController.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
+ }
+
+ @Test
public void testCharging_BatteryNotLow() {
JobStatus job1 = createJobStatus("testCharging_BatteryNotLow", SOURCE_PACKAGE, CALLING_UID,
createBaseJobInfoBuilder(1)
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index e4c9f97..7a70e7a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -99,6 +99,7 @@
private Constants mConstants;
+ private FlexibilityController mFlexibilityController;
private static final int UID_RED = 10001;
private static final int UID_BLUE = 10002;
@@ -116,6 +117,8 @@
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+ mFlexibilityController = new FlexibilityController(mService);
+
// Freeze the clocks at this moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
@@ -155,7 +158,8 @@
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
when(mService.isBatteryCharging()).thenReturn(false);
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
controller.onBatteryStateChangedLocked();
@@ -261,7 +265,8 @@
DataUnit.MEBIBYTES.toBytes(1))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
// Suspended networks aren't usable.
@@ -287,7 +292,8 @@
final JobStatus early = createJobStatus(job, now - 1000, now + 2000);
final JobStatus late = createJobStatus(job, now - 2000, now + 1000);
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
// Uncongested network is whenever
{
@@ -350,7 +356,8 @@
final JobStatus jobMinRunner = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
final NetworkCallback generalCallback = callbackCaptor.getValue();
@@ -523,7 +530,8 @@
final JobStatus jobMinRunner = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
final NetworkCallback generalCallback = callbackCaptor.getValue();
@@ -696,7 +704,8 @@
final JobStatus jobMinRunner = createJobStatus(
baseJobBuilder.setExpedited(false).setPriority(JobInfo.PRIORITY_MIN));
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
final NetworkCallback generalCallback = callbackCaptor.getValue();
@@ -747,7 +756,8 @@
job.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), JobInfo.NETWORK_BYTES_UNKNOWN);
final JobStatus latePrefetchUnknownUp = createJobStatus(job, now - 2000, now + 1000);
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
when(mNetPolicyManagerInternal.getSubscriptionOpportunisticQuota(
any(), eq(NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS)))
@@ -805,7 +815,9 @@
doNothing().when(mConnManager).registerDefaultNetworkCallbackForUid(
eq(UID_BLUE), blueCallbackCaptor.capture(), any());
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
final Network meteredNet = mock(Network.class);
final NetworkCapabilities meteredCaps = createCapabilitiesBuilder().build();
@@ -820,6 +832,9 @@
final JobStatus blue = createJobStatus(createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
+ assertFalse(red.getPreferUnmetered());
+ assertTrue(blue.getPreferUnmetered());
+
controller.maybeStartTrackingJobLocked(red, null);
controller.maybeStartTrackingJobLocked(blue, null);
final NetworkCallback generalCallback = callbackCaptor.getValue();
@@ -832,7 +847,9 @@
answerNetwork(generalCallback, blueCallback, null, null, null);
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(red.getHasAccessToUnmetered());
assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(blue.getHasAccessToUnmetered());
}
// Metered network
@@ -843,7 +860,9 @@
generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps);
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(red.getHasAccessToUnmetered());
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(blue.getHasAccessToUnmetered());
}
// Unmetered network background
@@ -854,7 +873,10 @@
generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(red.getHasAccessToUnmetered());
+
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(blue.getHasAccessToUnmetered());
}
// Lost metered network
@@ -865,7 +887,10 @@
generalCallback.onLost(meteredNet);
assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(red.getHasAccessToUnmetered());
+
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertTrue(blue.getHasAccessToUnmetered());
}
// Specific UID was blocked
@@ -876,13 +901,16 @@
generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertFalse(red.getHasAccessToUnmetered());
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
+ assertTrue(blue.getHasAccessToUnmetered());
}
}
@Test
public void testRequestStandbyExceptionLocked() {
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
final JobStatus red = createJobStatus(createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
@@ -919,7 +947,8 @@
@Test
public void testEvaluateStateLocked_JobWithoutConnectivity() {
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
final JobStatus red = createJobStatus(createJob().setMinimumLatency(1));
controller.evaluateStateLocked(red);
@@ -930,7 +959,8 @@
@Test
public void testEvaluateStateLocked_JobWouldBeReady() {
- final ConnectivityController controller = spy(new ConnectivityController(mService));
+ final ConnectivityController controller = spy(
+ new ConnectivityController(mService, mFlexibilityController));
doReturn(true).when(controller)
.wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
doReturn(true).when(controller).isNetworkAvailable(any());
@@ -972,7 +1002,8 @@
@Test
public void testEvaluateStateLocked_JobWouldNotBeReady() {
- final ConnectivityController controller = spy(new ConnectivityController(mService));
+ final ConnectivityController controller = spy(
+ new ConnectivityController(mService, mFlexibilityController));
doReturn(false).when(controller)
.wouldBeReadyWithConstraintLocked(any(), eq(JobStatus.CONSTRAINT_CONNECTIVITY));
final JobStatus red = createJobStatus(createJob()
@@ -1008,7 +1039,8 @@
@Test
public void testReevaluateStateLocked() {
- final ConnectivityController controller = spy(new ConnectivityController(mService));
+ final ConnectivityController controller = spy(
+ new ConnectivityController(mService, mFlexibilityController));
final JobStatus redOne = createJobStatus(createJob(1)
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
@@ -1070,7 +1102,8 @@
@Test
public void testMaybeRevokeStandbyExceptionLocked() {
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
final JobStatus red = createJobStatus(createJob()
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
@@ -1121,7 +1154,8 @@
final NetworkCapabilities cellularCaps =
createCapabilitiesBuilder().addTransportType(TRANSPORT_CELLULAR).build();
- final ConnectivityController controller = new ConnectivityController(mService);
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
controller.maybeStartTrackingJobLocked(networked, null);
controller.maybeStartTrackingJobLocked(unnetworked, null);
answerNetwork(callback.getValue(), redCallback.getValue(), null, cellularNet, cellularCaps);
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 c7ccef2..6d9f48d 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
@@ -16,11 +16,23 @@
package com.android.server.job.controllers;
+import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.job.controllers.FlexibilityController.FcConstants.KEY_FLEXIBILITY_ENABLED;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -30,6 +42,7 @@
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Looper;
+import android.provider.DeviceConfig;
import android.util.ArraySet;
import com.android.server.LocalServices;
@@ -39,14 +52,17 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
public class FlexibilityControllerTest {
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
@@ -54,19 +70,23 @@
private MockitoSession mMockingSession;
private FlexibilityController mFlexibilityController;
+ private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
+ private JobStore mJobStore;
+ private FlexibilityController.FcConstants mFcConstants;
+
@Mock
private AlarmManager mAlarmManager;
@Mock
private Context mContext;
@Mock
private JobSchedulerService mJobSchedulerService;
- private JobStore mJobStore;
@Before
public void setup() {
mMockingSession = mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class)
.startMocking();
// Called in StateController constructor.
@@ -77,6 +97,18 @@
// Called in FlexibilityController constructor.
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+ // Used in FlexibilityController.FcConstants.
+ doAnswer((Answer<Void>) invocationOnMock -> null)
+ .when(() -> DeviceConfig.addOnPropertiesChangedListener(
+ anyString(), any(Executor.class),
+ any(DeviceConfig.OnPropertiesChangedListener.class)));
+ mDeviceConfigPropertiesBuilder =
+ new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+ doAnswer(
+ (Answer<DeviceConfig.Properties>) invocationOnMock
+ -> mDeviceConfigPropertiesBuilder.build())
+ .when(() -> DeviceConfig.getProperties(
+ eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
//used to get jobs by UID
mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
@@ -90,6 +122,9 @@
Clock.fixed(Instant.ofEpochMilli(100L), ZoneOffset.UTC);
// Initialize real objects.
mFlexibilityController = new FlexibilityController(mJobSchedulerService);
+ mFcConstants = mFlexibilityController.getFcConstants();
+
+ setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
}
@After
@@ -99,6 +134,15 @@
}
}
+ private void setDeviceConfigBoolean(String key, boolean val) {
+ mDeviceConfigPropertiesBuilder.setBoolean(key, val);
+ synchronized (mFlexibilityController.mLock) {
+ mFlexibilityController.prepareForUpdatedConstantsLocked();
+ mFcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ mFlexibilityController.onConstantsUpdatedLocked();
+ }
+ }
+
private static JobInfo.Builder createJob(int id) {
return new JobInfo.Builder(id, new ComponentName("foo", "bar"));
}
@@ -189,7 +233,7 @@
public void testFlexibilityTracker() {
FlexibilityController.FlexibilityTracker flexTracker =
mFlexibilityController.new
- FlexibilityTracker(FlexibilityController.FLEXIBLE_CONSTRAINTS);
+ FlexibilityTracker(FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
JobStatus[] jobs = new JobStatus[4];
JobInfo.Builder jb;
@@ -206,37 +250,205 @@
}
jobs[i] = createJobStatus("", jb);
flexTracker.add(jobs[i]);
-
}
- ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
- assertEquals(1, trackedJobs.get(0).size());
- assertEquals(1, trackedJobs.get(1).size());
- assertEquals(1, trackedJobs.get(2).size());
- assertEquals(0, trackedJobs.get(3).size());
+ synchronized (mFlexibilityController.mLock) {
+ ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
+ assertEquals(0, trackedJobs.get(0).size());
+ assertEquals(0, trackedJobs.get(1).size());
+ assertEquals(3, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
- flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
- assertEquals(1, trackedJobs.get(0).size());
- assertEquals(2, trackedJobs.get(1).size());
- assertEquals(0, trackedJobs.get(2).size());
- assertEquals(0, trackedJobs.get(3).size());
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
+ 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);
- assertEquals(2, trackedJobs.get(0).size());
- assertEquals(1, trackedJobs.get(1).size());
- assertEquals(0, trackedJobs.get(2).size());
- assertEquals(0, trackedJobs.get(3).size());
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
+ 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);
- assertEquals(1, trackedJobs.get(0).size());
- assertEquals(1, trackedJobs.get(1).size());
- assertEquals(0, trackedJobs.get(2).size());
- assertEquals(0, trackedJobs.get(3).size());
+ flexTracker.adjustJobsRequiredConstraints(jobs[0], -1);
+ 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.remove(jobs[1]);
- assertEquals(1, trackedJobs.get(0).size());
- assertEquals(0, trackedJobs.get(1).size());
- assertEquals(0, trackedJobs.get(2).size());
- assertEquals(0, trackedJobs.get(3).size());
+ flexTracker.remove(jobs[1]);
+ assertEquals(0, trackedJobs.get(0).size());
+ assertEquals(0, trackedJobs.get(1).size());
+ assertEquals(1, trackedJobs.get(2).size());
+ assertEquals(0, trackedJobs.get(3).size());
+ }
+ }
+
+ @Test
+ public void testExceptions_Expedited() {
+ JobInfo.Builder jb = createJob(0);
+ jb.setExpedited(true);
+ JobStatus js = createJobStatus("testExceptions_Expedited", jb);
+ assertFalse(js.hasFlexibilityConstraint());
+ }
+
+ @Test
+ public void testExceptions_ShortWindow() {
+ JobInfo.Builder jb = createJob(0);
+ jb.setMinimumLatency(1);
+ jb.setOverrideDeadline(2);
+ JobStatus js = createJobStatus("Disable Flexible When Job Has Short Window", jb);
+ assertFalse(js.hasFlexibilityConstraint());
+ }
+
+ @Test
+ public void testExceptions_Prefetch() {
+ JobInfo.Builder jb = createJob(0);
+ jb.setPrefetch(true);
+ JobStatus js = createJobStatus("testExceptions_Prefetch", jb);
+ assertFalse(js.hasFlexibilityConstraint());
+ }
+
+ @Test
+ public void testExceptions_NoFlexibleConstraints() {
+ JobInfo.Builder jb = createJob(0);
+ jb.setRequiresDeviceIdle(true);
+ jb.setRequiresCharging(true);
+ jb.setRequiresBatteryNotLow(true);
+ JobStatus js = createJobStatus("testExceptions_NoFlexibleConstraints", jb);
+ assertFalse(js.hasFlexibilityConstraint());
+ }
+
+ @Test
+ public void testExceptions_None() {
+ JobInfo.Builder jb = createJob(0);
+ JobStatus js = createJobStatus("testExceptions_None", jb);
+ assertTrue(js.hasFlexibilityConstraint());
+ assertEquals(3, js.getNumRequiredFlexibleConstraints());
+ }
+
+ @Test
+ public void testTopAppBypass() {
+ JobInfo.Builder jb = createJob(0);
+ JobStatus js = createJobStatus("testTopAppBypass", jb);
+ js.adjustNumRequiredFlexibleConstraints(100);
+ mJobStore.add(js);
+
+ // Needed because if before and after Uid bias is the same, nothing happens.
+ when(mJobSchedulerService.getUidBias(js.getUid()))
+ .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
+
+ synchronized (mFlexibilityController.mLock) {
+ setUidBias(js.getUid(), JobInfo.BIAS_TOP_APP);
+
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+ assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
+
+ setUidBias(js.getUid(), JobInfo.BIAS_FOREGROUND_SERVICE);
+
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+ assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
+ }
+ }
+
+ @Test
+ public void testConnectionToUnMeteredNetwork() {
+ JobInfo.Builder jb = createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY);
+ JobStatus js = createJobStatus("testTopAppBypass", jb);
+ synchronized (mFlexibilityController.mLock) {
+ js.setHasAccessToUnmetered(false);
+ assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+ js.setHasAccessToUnmetered(true);
+ assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+ js.setHasAccessToUnmetered(false);
+ assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js));
+ }
+ }
+
+ @Test
+ public void testSetConstraintSatisfied_Constraints() {
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
+ assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
+
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true);
+ assertTrue(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
+
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
+ assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
+ }
+
+ @Test
+ public void testSetConstraintSatisfied_Jobs() {
+ JobInfo.Builder jb;
+ int[] constraintPermutations = {
+ CONSTRAINT_IDLE & CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
+ CONSTRAINT_IDLE & CONSTRAINT_BATTERY_NOT_LOW,
+ CONSTRAINT_IDLE & CONSTRAINT_CHARGING,
+ CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
+ CONSTRAINT_IDLE,
+ CONSTRAINT_CHARGING,
+ CONSTRAINT_BATTERY_NOT_LOW,
+ 0
+ };
+
+ int constraints;
+ for (int i = 0; i < constraintPermutations.length; i++) {
+ jb = createJob(i);
+ constraints = constraintPermutations[i];
+ jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
+ jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
+ jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
+ synchronized (mFlexibilityController.mLock) {
+ mFlexibilityController.maybeStartTrackingJobLocked(
+ createJobStatus(String.valueOf(i), jb), null);
+ }
+ }
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, false);
+
+ assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints);
+
+ for (int i = 0; i < constraintPermutations.length; i++) {
+ constraints = constraintPermutations[i];
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING,
+ (constraints & CONSTRAINT_CHARGING) != 0);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE,
+ (constraints & CONSTRAINT_IDLE) != 0);
+ mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW,
+ (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
+
+ assertEquals(constraints, mFlexibilityController.mSatisfiedFlexibleConstraints);
+ synchronized (mFlexibilityController.mLock) {
+ assertSatisfiedJobsMatchSatisfiedConstraints(
+ mFlexibilityController.mFlexibilityTracker.getArrayList(), constraints);
+ }
+ }
+ }
+
+ private void setUidBias(int uid, int bias) {
+ int prevBias = mJobSchedulerService.getUidBias(uid);
+ doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
+ synchronized (mFlexibilityController.mLock) {
+ mFlexibilityController.onUidBiasChangedLocked(uid, prevBias, bias);
+ }
+ }
+
+ private void assertSatisfiedJobsMatchSatisfiedConstraints(
+ ArrayList<ArraySet<JobStatus>> trackedJobs, int satisfiedConstraints) {
+ int numSatisfiedConstraints;
+ numSatisfiedConstraints = Integer.bitCount(satisfiedConstraints);
+ for (int i = 0; i < trackedJobs.size(); i++) {
+ ArraySet<JobStatus> jobs = trackedJobs.get(i);
+ for (int j = 0; j < jobs.size(); j++) {
+ JobStatus js = jobs.valueAt(j);
+ final int isUnMetered = js.getPreferUnmetered()
+ && js.getHasAccessToUnmetered() ? 1 : 0;
+ assertEquals(js.getNumRequiredFlexibleConstraints()
+ <= numSatisfiedConstraints + isUnMetered,
+ js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
+ }
+ }
}
}
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 0319d8d..f15e60f 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
@@ -797,6 +797,7 @@
sElapsedRealtimeClock.millis(), isSatisfied, false);
job.setBackgroundNotRestrictedConstraintSatisfied(
sElapsedRealtimeClock.millis(), isSatisfied, false);
+ job.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
}
private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
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 5f9f1b2..bcdfc35 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
@@ -24,6 +24,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
@@ -185,13 +186,17 @@
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
js.serviceInfo = mock(ServiceInfo.class);
+ js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
/* state */ true, /* allowlisted */false);
js.setBackgroundNotRestrictedConstraintSatisfied(
sElapsedRealtimeClock.millis(), true, false);
+ js.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
+ js.setExpeditedJobQuotaApproved(sElapsedRealtimeClock.millis(), true);
js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true);
+ js.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
return js;
}
@@ -294,6 +299,7 @@
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(job.isReady());
}
@Test
@@ -309,6 +315,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(job.isReady());
}
@Test
@@ -331,19 +338,25 @@
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobRunning.isReady());
setUidBias(uid, JobInfo.BIAS_TOP_APP);
// Processing happens on the handler, so wait until we're sure the change has been processed
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
// Already running job should continue but pending job must wait.
assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobRunning.isReady());
setUidBias(uid, JobInfo.BIAS_DEFAULT);
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobRunning.isReady());
}
@Test
@@ -367,11 +380,13 @@
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(jobNonWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobNonWidget.isReady());
when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(true);
trackJobs(jobWidget);
assertTrue(jobWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobWidget.isReady());
}
@Test
@@ -390,6 +405,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
@@ -401,6 +417,7 @@
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobStatus.isReady());
}
@Test
@@ -418,6 +435,7 @@
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + MINUTE_IN_MILLIS);
@@ -426,6 +444,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobStatus.isReady());
}
@Test
@@ -448,6 +467,7 @@
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertFalse(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
@@ -456,6 +476,7 @@
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
+ assertTrue(jobStatus.isReady());
sSystemClock = getShiftedClock(sSystemClock, HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
}
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 29a3eb1..7048fb2 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
@@ -395,6 +395,7 @@
sElapsedRealtimeClock.millis(), true, false);
js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true);
+ js.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
return js;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
new file mode 100644
index 0000000..2e200c3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.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.server.tare;
+
+import static android.app.tare.EconomyManager.arcToCake;
+import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.tare.EconomyManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.BatteryManager;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+@RunWith(AndroidJUnit4.class)
+public class AlarmManagerEconomicPolicyTest {
+ private AlarmManagerEconomicPolicy mEconomicPolicy;
+ private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
+ private EconomicPolicy.Injector mInjector = new InjectorForTest();
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+ @Mock
+ private InternalResourceService mIrs;
+
+ private static class InjectorForTest extends EconomicPolicy.Injector {
+ public String settingsConstant;
+
+ @Nullable
+ @Override
+ String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) {
+ return TARE_ALARM_MANAGER_CONSTANTS.equals(name) ? settingsConstant : null;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
+ .startMocking();
+
+ when(mIrs.getContext()).thenReturn(mContext);
+ when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+ when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
+ // Called by Modifiers.
+ when(mContext.getSystemService(BatteryManager.class))
+ .thenReturn(mock(BatteryManager.class));
+ when(mContext.getSystemService(PowerManager.class))
+ .thenReturn(mock(PowerManager.class));
+ IActivityManager activityManager = ActivityManager.getService();
+ spyOn(activityManager);
+ try {
+ doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
+ } catch (RemoteException e) {
+ fail("registerUidObserver threw exception: " + e.getMessage());
+ }
+
+ mDeviceConfigPropertiesBuilder =
+ new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TARE);
+ doAnswer(
+ (Answer<DeviceConfig.Properties>) invocationOnMock
+ -> mDeviceConfigPropertiesBuilder.build())
+ .when(() -> DeviceConfig.getProperties(
+ eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>any()));
+
+ // Initialize real objects.
+ // Capture the listeners.
+ mEconomicPolicy = new AlarmManagerEconomicPolicy(mIrs, mInjector);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void setDeviceConfigCakes(String key, long valCakes) {
+ mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c");
+ mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build());
+ }
+
+ @Test
+ public void testDefaults() {
+ assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+
+ @Test
+ public void testConstantsUpdating_ValidValues() {
+ setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(25));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
+
+ assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(7), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+
+ @Test
+ public void testConstantsUpdating_InvalidValues() {
+ // Test negatives.
+ setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(-1));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
+
+ assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+
+ // Test min+max reversed.
+ setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(3));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
+
+ assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+ assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
new file mode 100644
index 0000000..45c97e4
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.tare;
+
+import static android.app.tare.EconomyManager.arcToCake;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.tare.EconomyManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.BatteryManager;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+@RunWith(AndroidJUnit4.class)
+public class CompleteEconomicPolicyTest {
+ private CompleteEconomicPolicy mEconomicPolicy;
+ private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
+ private final CompleteEconomicPolicy.CompleteInjector mInjector = new InjectorForTest();
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+ @Mock
+ private InternalResourceService mIrs;
+
+ private static class InjectorForTest extends CompleteEconomicPolicy.CompleteInjector {
+ public String settingsConstant;
+
+ @Nullable
+ @Override
+ String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) {
+ return settingsConstant;
+ }
+
+ @Override
+ boolean isPolicyEnabled(int policy) {
+ // Use a limited set of policies so that the test doesn't need to be updated whenever
+ // a policy is added or removed.
+ return policy == EconomicPolicy.POLICY_AM || policy == EconomicPolicy.POLICY_JS;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
+ .startMocking();
+
+ when(mIrs.getContext()).thenReturn(mContext);
+ when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+ when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
+ // Called by Modifiers.
+ when(mContext.getSystemService(BatteryManager.class))
+ .thenReturn(mock(BatteryManager.class));
+ when(mContext.getSystemService(PowerManager.class))
+ .thenReturn(mock(PowerManager.class));
+ IActivityManager activityManager = ActivityManager.getService();
+ spyOn(activityManager);
+ try {
+ doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
+ } catch (RemoteException e) {
+ fail("registerUidObserver threw exception: " + e.getMessage());
+ }
+
+ mDeviceConfigPropertiesBuilder =
+ new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TARE);
+ doAnswer(
+ (Answer<DeviceConfig.Properties>) invocationOnMock
+ -> mDeviceConfigPropertiesBuilder.build())
+ .when(() -> DeviceConfig.getProperties(
+ eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>any()));
+
+ // Initialize real objects.
+ // Capture the listeners.
+ mEconomicPolicy = new CompleteEconomicPolicy(mIrs, mInjector);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void setDeviceConfigCakes(String key, long valCakes) {
+ mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c");
+ mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build());
+ }
+
+ @Test
+ public void testDefaults() {
+ assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES
+ + EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES
+ + EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
+ + EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES
+ + EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES
+ + EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+
+ @Test
+ public void testConstantsUpdated() {
+ setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(6));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(24));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(26));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(9));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(11));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(8));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(3));
+ setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(2));
+
+ assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
new file mode 100644
index 0000000..03ce91a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.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.server.tare;
+
+import static android.app.tare.EconomyManager.arcToCake;
+import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.tare.EconomyManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.BatteryManager;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+@RunWith(AndroidJUnit4.class)
+public class JobSchedulerEconomicPolicyTest {
+ private JobSchedulerEconomicPolicy mEconomicPolicy;
+ private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
+ private final EconomicPolicy.Injector mInjector = new InjectorForTest();
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+ @Mock
+ private InternalResourceService mIrs;
+
+ private static class InjectorForTest extends EconomicPolicy.Injector {
+ public String settingsConstant;
+
+ @Nullable
+ @Override
+ String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) {
+ return TARE_JOB_SCHEDULER_CONSTANTS.equals(name) ? settingsConstant : null;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DeviceConfig.class)
+ .startMocking();
+
+ when(mIrs.getContext()).thenReturn(mContext);
+ when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+ when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
+ // Called by Modifiers.
+ when(mContext.getSystemService(BatteryManager.class))
+ .thenReturn(mock(BatteryManager.class));
+ when(mContext.getSystemService(PowerManager.class))
+ .thenReturn(mock(PowerManager.class));
+ IActivityManager activityManager = ActivityManager.getService();
+ spyOn(activityManager);
+ try {
+ doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
+ } catch (RemoteException e) {
+ fail("registerUidObserver threw exception: " + e.getMessage());
+ }
+
+ mDeviceConfigPropertiesBuilder =
+ new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TARE);
+ doAnswer(
+ (Answer<DeviceConfig.Properties>) invocationOnMock
+ -> mDeviceConfigPropertiesBuilder.build())
+ .when(() -> DeviceConfig.getProperties(
+ eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>any()));
+
+ // Initialize real objects.
+ // Capture the listeners.
+ mEconomicPolicy = new JobSchedulerEconomicPolicy(mIrs, mInjector);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void setDeviceConfigCakes(String key, long valCakes) {
+ mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c");
+ mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build());
+ }
+
+ @Test
+ public void testDefaults() {
+ assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
+ mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+
+ @Test
+ public void testConstantsUpdating_ValidValues() {
+ setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
+
+ assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(7), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+
+ @Test
+ public void testConstantsUpdating_InvalidValues() {
+ // Test negatives.
+ setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(-5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
+
+ assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgExempted = "com.pkg.exempted";
+ when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+
+ // Test min+max reversed.
+ setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(3));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
+
+ assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+ assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 5d0554a..1921ce7 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -109,7 +109,11 @@
data: [
":BstatsTestApp",
":JobTestApp",
+ ":SimpleServiceTestApp1",
+ ":SimpleServiceTestApp2",
+ ":SimpleServiceTestApp3",
":StubTestApp",
+ ":SuspendTestApp",
],
java_resources: [
@@ -131,8 +135,9 @@
java_library {
name: "servicestests-core-utils",
srcs: [
- "src/com/android/server/pm/PackageSettingBuilder.java",
"src/com/android/server/am/DeviceConfigSession.java",
+ "src/com/android/server/display/TestUtils.java",
+ "src/com/android/server/pm/PackageSettingBuilder.java",
],
static_libs: [
"services.core",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 0afb182..0483a60 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -103,6 +103,7 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
+ <uses-permission android:name="android.permission.BATTERY_STATS" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 1d6ed03..c15f6a9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
@@ -27,7 +28,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -301,8 +301,9 @@
mSystemActionPerformer.performSystemAction(
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
verify(mMockScreenshotHelper).takeScreenshot(
- eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
- anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
+ eq(TAKE_SCREENSHOT_FULLSCREEN),
+ eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
+ any(Handler.class), any());
}
// PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
new file mode 100644
index 0000000..7acb6d6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.audio;
+
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.VolumeInfo;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class AudioDeviceVolumeManagerTest {
+ private static final String TAG = "AudioDeviceVolumeManagerTest";
+
+ private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+
+ private Context mContext;
+ private String mPackageName;
+ private AudioSystemAdapter mSpyAudioSystem;
+ private SystemServerAdapter mSystemServer;
+ private SettingsAdapter mSettingsAdapter;
+ private TestLooper mTestLooper;
+
+ private AudioService mAudioService;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mTestLooper = new TestLooper();
+ mContext = InstrumentationRegistry.getTargetContext();
+ mPackageName = mContext.getOpPackageName();
+ mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+
+ mSystemServer = new NoOpSystemServerAdapter();
+ mSettingsAdapter = new NoOpSettingsAdapter();
+ mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
+ mSettingsAdapter, mTestLooper.getLooper()) {
+ @Override
+ public int getDeviceForStream(int stream) {
+ return AudioSystem.DEVICE_OUT_SPEAKER;
+ }
+ };
+
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void testSetDeviceVolume() {
+ AudioManager am = mContext.getSystemService(AudioManager.class);
+ final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+ final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ final int midIndex = (minIndex + maxIndex) / 2;
+ final VolumeInfo volMedia = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMinVolumeIndex(minIndex)
+ .setMaxVolumeIndex(maxIndex)
+ .build();
+ final VolumeInfo volMin = new VolumeInfo.Builder(volMedia).setVolumeIndex(minIndex).build();
+ final VolumeInfo volMid = new VolumeInfo.Builder(volMedia).setVolumeIndex(midIndex).build();
+ final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes(
+ /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla");
+
+ mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG);
+ mTestLooper.dispatchAll();
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+
+ mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG);
+ mTestLooper.dispatchAll();
+ verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
+ AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
index 09e5d4b..ee9d59b 100644
--- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
+++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java
@@ -126,6 +126,11 @@
}
@Override
+ public int setStreamVolumeIndexAS(int stream, int index, int device) {
+ return AudioSystem.AUDIO_STATUS_OK;
+ }
+
+ @Override
@NonNull
public ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
@NonNull AudioAttributes attributes, boolean forVolume) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index c80547c..966df4f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -124,6 +124,8 @@
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
private AssociationInfo mAssociationInfo;
+ private VirtualDeviceManagerService mVdms;
+ private VirtualDeviceManagerInternal mLocalService;
@Mock
private InputController.NativeWrapper mNativeWrapperMock;
@Mock
@@ -139,6 +141,8 @@
@Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@Mock
+ private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
+ @Mock
IPowerManager mIPowerManagerMock;
@Mock
IThermalService mIThermalServiceMock;
@@ -215,6 +219,9 @@
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0);
+ mVdms = new VirtualDeviceManagerService(mContext);
+ mLocalService = mVdms.getLocalServiceInstance();
+
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
.setBlockedActivities(getBlockedActivities())
@@ -235,6 +242,28 @@
}
@Test
+ public void onVirtualDisplayCreatedLocked_listenersNotified() throws RemoteException {
+ mLocalService.registerVirtualDisplayListener(mDisplayListener);
+
+ mLocalService.onVirtualDisplayCreated(DISPLAY_ID);
+ TestableLooper.get(this).processAllMessages();
+
+ verify(mDisplayListener).onVirtualDisplayCreated(DISPLAY_ID);
+ }
+
+ @Test
+ public void onVirtualDisplayRemovedLocked_listenersNotified() throws RemoteException {
+ mLocalService.registerVirtualDisplayListener(mDisplayListener);
+ mDeviceImpl.onVirtualDisplayCreatedLocked(
+ mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+
+ mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID);
+ TestableLooper.get(this).processAllMessages();
+
+ verify(mDisplayListener).onVirtualDisplayRemoved(DISPLAY_ID);
+ }
+
+ @Test
public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
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 20cc42c..a4cccb3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -33,15 +33,18 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.service.gatekeeper.GateKeeperResponse;
+import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -450,6 +453,45 @@
PRIMARY_USER_ID));
}
+ @Test
+ public void testPasswordHistoryDisabledByDefault() throws Exception {
+ final int userId = PRIMARY_USER_ID;
+ checkPasswordHistoryLength(userId, 0);
+ initializeStorageWithCredential(userId, nonePassword());
+ checkPasswordHistoryLength(userId, 0);
+
+ assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(), userId));
+ checkPasswordHistoryLength(userId, 0);
+ }
+
+ @Test
+ public void testPasswordHistoryLengthHonored() throws Exception {
+ final int userId = PRIMARY_USER_ID;
+ when(mDevicePolicyManager.getPasswordHistoryLength(any(), eq(userId))).thenReturn(3);
+ checkPasswordHistoryLength(userId, 0);
+ initializeStorageWithCredential(userId, nonePassword());
+ checkPasswordHistoryLength(userId, 0);
+
+ assertTrue(mService.setLockCredential(newPassword("pass1"), nonePassword(), userId));
+ checkPasswordHistoryLength(userId, 1);
+
+ assertTrue(mService.setLockCredential(newPassword("pass2"), newPassword("pass1"), userId));
+ checkPasswordHistoryLength(userId, 2);
+
+ assertTrue(mService.setLockCredential(newPassword("pass3"), newPassword("pass2"), userId));
+ checkPasswordHistoryLength(userId, 3);
+
+ // maximum length should have been reached
+ assertTrue(mService.setLockCredential(newPassword("pass4"), newPassword("pass3"), userId));
+ checkPasswordHistoryLength(userId, 3);
+ }
+
+ private void checkPasswordHistoryLength(int userId, int expectedLen) {
+ String history = mService.getString(LockPatternUtils.PASSWORD_HISTORY_KEY, "", userId);
+ String[] hashes = TextUtils.split(history, LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
+ assertEquals(expectedLen, hashes.length);
+ }
+
private void testCreateCredential(int userId, LockscreenCredential credential)
throws RemoteException {
assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
@@ -504,11 +546,16 @@
badCredential, userId, 0 /* flags */).getResponseCode());
}
- @SuppressWarnings("GuardedBy") // for initializeSyntheticPasswordLocked
private void initializeStorageWithCredential(int userId, LockscreenCredential credential)
throws RemoteException {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
- mService.initializeSyntheticPasswordLocked(credential, userId);
- assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
+ synchronized (mService.mSpManager) {
+ mService.initializeSyntheticPasswordLocked(credential, userId);
+ }
+ if (credential.isNone()) {
+ assertEquals(0, mGateKeeperService.getSecureUserId(userId));
+ } else {
+ assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index 0bb2021..186a04f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -44,12 +44,14 @@
mGateKeeper = gatekeeper;
}
- private ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
+ private final ArrayMap<String, byte[]> mBlobs = new ArrayMap<>();
@Override
- protected byte[] decryptSPBlob(String blobKeyName, byte[] blob, byte[] protectorSecret) {
- if (mBlobs.containsKey(blobKeyName) && !Arrays.equals(mBlobs.get(blobKeyName), blob)) {
- throw new AssertionFailedError("blobKeyName content is overwritten: " + blobKeyName);
+ protected byte[] decryptSpBlob(String protectorKeyAlias, byte[] blob, byte[] protectorSecret) {
+ if (mBlobs.containsKey(protectorKeyAlias) &&
+ !Arrays.equals(mBlobs.get(protectorKeyAlias), blob)) {
+ throw new AssertionFailedError("Blob was overwritten; protectorKeyAlias="
+ + protectorKeyAlias);
}
ByteBuffer buffer = ByteBuffer.allocate(blob.length);
buffer.put(blob, 0, blob.length);
@@ -72,7 +74,7 @@
}
@Override
- protected byte[] createSPBlob(String blobKeyName, byte[] data, byte[] protectorSecret,
+ protected byte[] createSpBlob(String protectorKeyAlias, byte[] data, byte[] protectorSecret,
long sid) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + data.length + Integer.BYTES
+ protectorSecret.length + Long.BYTES);
@@ -82,12 +84,12 @@
buffer.put(protectorSecret);
buffer.putLong(sid);
byte[] result = buffer.array();
- mBlobs.put(blobKeyName, result);
+ mBlobs.put(protectorKeyAlias, result);
return result;
}
@Override
- protected void destroySPBlobKey(String keyAlias) {
+ protected void destroyProtectorKey(String keyAlias) {
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index d9af51f..c2e83f2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -54,6 +54,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -835,7 +836,7 @@
byte[] locallyEncryptedKey = SecureBox.decrypt(
TestData.getInsecurePrivateKeyForEndpoint1(),
/*sharedSecret=*/ KeySyncUtils.calculateThmKfHash(lockScreenHash),
- /*header=*/ KeySyncUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(THM_ENCRYPTED_RECOVERY_KEY_HEADER, vaultParams),
encryptedKey
);
return KeySyncUtils.decryptRecoveryKey(lockScreenHash, locallyEncryptedKey);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index 178fd10..19a606e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -27,6 +27,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
@@ -117,17 +118,6 @@
}
@Test
- public void concat_concatenatesArrays() {
- assertArrayEquals(
- utf8Bytes("hello, world!"),
- KeySyncUtils.concat(
- utf8Bytes("hello"),
- utf8Bytes(", "),
- utf8Bytes("world"),
- utf8Bytes("!")));
- }
-
- @Test
public void decryptApplicationKey_decryptsAnApplicationKey_nullMetadata() throws Exception {
String alias = "phoebe";
SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
@@ -253,7 +243,7 @@
byte[] encryptedPayload = SecureBox.encrypt(
/*theirPublicKey=*/ null,
/*sharedSecret=*/ keyClaimant,
- /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*payload=*/ recoveryKey);
byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse(
@@ -269,7 +259,7 @@
byte[] encryptedPayload = SecureBox.encrypt(
/*theirPublicKey=*/ null,
/*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(),
- /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*payload=*/ recoveryKey);
try {
@@ -298,9 +288,9 @@
byte[] decrypted = SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
+ /*header=*/ ArrayUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
encryptedRecoveryClaim);
- assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
+ assertArrayEquals(ArrayUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
}
@Test
@@ -320,7 +310,7 @@
SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(
+ /*header=*/ ArrayUtils.concat(
RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)),
encryptedRecoveryClaim);
fail("Should throw if challenge is incorrect.");
@@ -346,7 +336,7 @@
SecureBox.decrypt(
SecureBox.genKeyPair().getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(
+ /*header=*/ ArrayUtils.concat(
RECOVERY_CLAIM_HEADER, vaultParams, challenge),
encryptedRecoveryClaim);
fail("Should throw if secret key is incorrect.");
@@ -372,7 +362,7 @@
SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(
+ /*header=*/ ArrayUtils.concat(
RECOVERY_CLAIM_HEADER, randomBytes(100), challenge),
encryptedRecoveryClaim);
fail("Should throw if vault params is incorrect.");
@@ -399,7 +389,7 @@
SecureBox.decrypt(
keyPair.getPrivate(),
/*sharedSecret=*/ null,
- /*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge),
+ /*header=*/ ArrayUtils.concat(randomBytes(10), vaultParams, challenge),
encryptedRecoveryClaim);
fail("Should throw if header is incorrect.");
} catch (AEADBadTagException e) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 035249e..aceae61 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -58,6 +58,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
@@ -1287,7 +1288,7 @@
return SecureBox.encrypt(
/*theirPublicKey=*/ null,
/*sharedSecret=*/ keyClaimant,
- /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
+ /*header=*/ ArrayUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
/*payload=*/ locallyEncryptedRecoveryKey);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
index 15b0708..34235bd 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -27,6 +27,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import com.android.internal.util.ArrayUtils;
+
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
@@ -176,9 +178,9 @@
SecureBox.decrypt(
THM_PRIVATE_KEY,
/*sharedSecret=*/ null,
- SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+ ArrayUtils.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
RECOVERY_CLAIM);
- assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
+ assertThat(claimContent).isEqualTo(ArrayUtils.concat(THM_KF_HASH, KEY_CLAIMANT));
}
@Test
@@ -186,7 +188,7 @@
SecureBox.decrypt(
THM_PRIVATE_KEY,
THM_KF_HASH,
- SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+ ArrayUtils.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
ENCRYPTED_RECOVERY_KEY);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 20482af..503ca69 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -36,8 +36,6 @@
import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -93,16 +91,16 @@
ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
apexPackageInfo.scanApexPackages(
apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
- final PackageInfo activePkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(activePkgPi).isNotNull();
- assertThat(activePkgPi.packageName).contains(TEST_APEX_PKG);
+ assertThat(activePair).isNotNull();
+ assertThat(activePair.second.getPackageName()).contains(TEST_APEX_PKG);
- final PackageInfo factoryPkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ final var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryPkgPi).isNull();
+ assertThat(factoryPair).isNull();
}
@Test
@@ -111,16 +109,16 @@
ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
apexPackageInfo.scanApexPackages(
apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
- PackageInfo factoryPkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryPkgPi).isNotNull();
- assertThat(factoryPkgPi.packageName).contains(TEST_APEX_PKG);
+ assertThat(factoryPair).isNotNull();
+ assertThat(factoryPair.second.getPackageName()).contains(TEST_APEX_PKG);
- final PackageInfo activePkgPi = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
+ final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(activePkgPi).isNull();
+ assertThat(activePair).isNull();
}
@Test
@@ -388,23 +386,16 @@
newApexInfo = mApexManager.installPackage(installedApex);
apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2);
- PackageInfo newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(newInfo.applicationInfo.sourceDir).isEqualTo(finalApex.getAbsolutePath());
- assertThat(newInfo.applicationInfo.longVersionCode).isEqualTo(2);
- assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
- .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
- assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
- .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+ assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
+ assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2);
- PackageInfo factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryInfo.applicationInfo.sourceDir).isEqualTo(activeApexInfo.modulePath);
- assertThat(factoryInfo.applicationInfo.longVersionCode).isEqualTo(1);
- assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
- .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
- assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
- .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+ assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath);
+ assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1);
+ assertThat(factoryInfo.second.isSystem()).isTrue();
}
@Test
@@ -429,23 +420,16 @@
newApexInfo = mApexManager.installPackage(installedApex);
apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2);
- PackageInfo newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_ACTIVE_PACKAGE);
- assertThat(newInfo.applicationInfo.sourceDir).isEqualTo(finalApex.getAbsolutePath());
- assertThat(newInfo.applicationInfo.longVersionCode).isEqualTo(2);
- assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
- .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
- assertThat(newInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
- .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+ assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
+ assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2);
- PackageInfo factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
+ var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
ApexManager.MATCH_FACTORY_PACKAGE);
- assertThat(factoryInfo.applicationInfo.sourceDir).isEqualTo(factoryApexInfo.modulePath);
- assertThat(factoryInfo.applicationInfo.longVersionCode).isEqualTo(1);
- assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
- .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
- assertThat(factoryInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
- .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+ assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath);
+ assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1);
+ assertThat(factoryInfo.second.isSystem()).isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
index ecf7803..51498a6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -109,7 +109,7 @@
assertTrue("Verification should be considered complete now",
state.isVerificationComplete());
- assertFalse("Installation should be marked as allowed",
+ assertFalse("Installation should be marked as rejected",
state.isInstallAllowed());
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
new file mode 100644
index 0000000..7ae1117
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.power.stats;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.fail;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+import android.os.UidBatteryConsumer;
+
+import org.junit.Test;
+
+/**
+ * Test BatteryStatsManager and CellularBatteryStats to ensure that valid data is being reported
+ * and that invalid data is not reported.
+ */
+public class BatteryStatsManagerTest {
+
+ @Test
+ public void testBatteryUsageStatsDataConsistency() {
+ BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class);
+ BatteryUsageStats stats = bsm.getBatteryUsageStats(
+ new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(
+ 0).includeProcessStateData().build());
+ final int[] components =
+ {BatteryConsumer.POWER_COMPONENT_CPU,
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ BatteryConsumer.POWER_COMPONENT_WIFI,
+ BatteryConsumer.POWER_COMPONENT_BLUETOOTH};
+ final int[] states =
+ {BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.PROCESS_STATE_CACHED};
+ for (UidBatteryConsumer ubc : stats.getUidBatteryConsumers()) {
+ for (int component : components) {
+ double consumedPower = ubc.getConsumedPower(ubc.getKey(component));
+ double sumStates = 0;
+ for (int state : states) {
+ sumStates += ubc.getConsumedPower(ubc.getKey(component, state));
+ }
+ if (sumStates > consumedPower + 0.1) {
+ fail("Sum of states exceeds total. UID = " + ubc.getUid() + " "
+ + BatteryConsumer.powerComponentIdToString(component)
+ + " total = " + consumedPower + " states = " + sumStates);
+ }
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
index 1b724d0..807df47 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
@@ -34,6 +34,7 @@
BatteryStatsHistoryIteratorTest.class,
BatteryStatsHistoryTest.class,
BatteryStatsImplTest.class,
+ BatteryStatsManagerTest.class,
BatteryStatsNoteTest.class,
BatteryStatsSamplingTimerTest.class,
BatteryStatsSensorTest.class,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 3f3d01a..5146616 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -49,6 +49,7 @@
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -2002,7 +2003,7 @@
anyInt() /* viewVisibility */, anyInt() /* displayId */,
any() /* requestedVisibilities */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */,
- any() /* outAttachedFrame */);
+ any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
mAtm.mWindowManager.mStartingSurfaceController
.createTaskSnapshotSurface(activity, snapshot);
} catch (RemoteException ignored) {
@@ -2414,7 +2415,7 @@
activity.removeImmediately();
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testLandscapeSeascapeRotationByApp() {
final Task task = new TaskBuilder(mSupervisor)
@@ -2447,14 +2448,14 @@
appWindow.removeImmediately();
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testLandscapeSeascapeRotationByPolicy() {
final Task task = new TaskBuilder(mSupervisor)
.setDisplay(mDisplayContent).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopNonFinishingActivity();
- // This instance has been spied in {@link TestDisplayContent}.
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+ spyOn(displayRotation);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
@@ -2538,6 +2539,21 @@
}
@Test
+ public void testStuckExitingWindow() {
+ final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
+ "closingWindow");
+ closingWindow.mAnimatingExit = true;
+ closingWindow.mRemoveOnExit = true;
+ closingWindow.mActivityRecord.commitVisibility(
+ false /* visible */, true /* performLayout */);
+
+ // We pretended that we were running an exit animation, but that should have been cleared up
+ // by changing visibility of ActivityRecord
+ closingWindow.removeIfPossible();
+ assertTrue(closingWindow.mRemoved);
+ }
+
+ @Test
public void testSetOrientation() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.setVisible(true);
@@ -2556,7 +2572,6 @@
mWm.mDisplayFrozen = false;
}
- @UseTestDisplay
@Test
public void testRespectTopFullscreenOrientation() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2579,7 +2594,6 @@
assertEquals(Configuration.ORIENTATION_LANDSCAPE, activityConfig.orientation);
}
- @UseTestDisplay
@Test
public void testReportOrientationChange() {
final Task task = new TaskBuilder(mSupervisor)
@@ -3103,7 +3117,7 @@
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenResized() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3121,7 +3135,7 @@
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3133,7 +3147,6 @@
mDisplayContent.mOpeningApps.clear();
app.mActivityRecord.commitVisibility(false, false);
app.mActivityRecord.onWindowsGone();
- mDisplayContent.computeImeTargetIfNeeded(app.mActivityRecord);
assertTrue(app.mActivityRecord.mLastImeShown);
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
@@ -3150,7 +3163,7 @@
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -3187,7 +3200,7 @@
assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest()
throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 2e85897..06b4ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -87,6 +87,7 @@
private static final int TEST_UID = 0;
private static final String TEST_PACKAGE = "";
+ private static final String TEST_ATTRIBUTION_TAG = "";
private AssistDataRequester mDataRequester;
private Callbacks mCallbacks;
@@ -148,9 +149,9 @@
boolean assistScreenshotAllowed) throws Exception {
doReturn(currentActivityAssistAllowed).when(mAtm).isAssistDataAllowedOnCurrentActivity();
doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
- .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
+ .noteOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString(), any(), any());
doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
- .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
+ .noteOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString(), any(), any());
}
@Test
@@ -160,7 +161,8 @@
CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
assertReceivedDataCount(5, 5, 1, 1);
}
@@ -170,7 +172,8 @@
CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
assertReceivedDataCount(0, 0, 0, 0);
}
@@ -180,7 +183,8 @@
CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
assertReceivedDataCount(0, 1, 0, 1);
}
@@ -191,7 +195,8 @@
mCallbacks.mCanHandleReceivedData = false;
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
assertEquals(5, mDataRequester.getPendingDataCount());
assertEquals(1, mDataRequester.getPendingScreenshotCount());
mGate.countDown();
@@ -226,7 +231,8 @@
CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
assertReceivedDataCount(0, 0, 0, 1);
}
@@ -236,7 +242,8 @@
CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
// Expect a single null data when the appops is denied
assertReceivedDataCount(0, 1, 0, 1);
}
@@ -249,7 +256,8 @@
anyBoolean(), anyBoolean());
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
// Expect a single null data when requestAssistContextExtras() fails
assertReceivedDataCount(0, 1, 0, 1);
}
@@ -261,7 +269,8 @@
CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
assertReceivedDataCount(5, 5, 0, 0);
}
@@ -272,7 +281,8 @@
!CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
// Expect a single null screenshot when the appops is denied
assertReceivedDataCount(5, 5, 0, 1);
}
@@ -284,7 +294,8 @@
mCallbacks.mCanHandleReceivedData = false;
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ ALLOW_FETCH_DATA, ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
mGate.countDown();
waitForIdle(mHandler);
assertThat(mCallbacks.mReceivedData).isEmpty();
@@ -297,7 +308,8 @@
CALLER_ASSIST_SCREENSHOT_ALLOWED);
mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
- !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE);
+ !ALLOW_FETCH_DATA, !ALLOW_FETCH_SCREENSHOTS, TEST_UID, TEST_PACKAGE,
+ TEST_ATTRIBUTION_TAG);
assertReceivedDataCount(0, 1, 0, 1);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index fc41a94..c2ca0a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -29,12 +29,9 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
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.annotation.NonNull;
@@ -42,7 +39,6 @@
import android.hardware.HardwareBuffer;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
@@ -75,8 +71,7 @@
LocalServices.removeServiceForTest(WindowManagerInternal.class);
mWindowManagerInternal = mock(WindowManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
- TaskSnapshotController taskSnapshotController = createMockTaskSnapshotController();
- mBackNavigationController.setTaskSnapshotController(taskSnapshotController);
+ mBackNavigationController.setWindowManager(mWm);
}
@Test
@@ -84,19 +79,14 @@
Task task = createTopTaskWithActivity();
IOnBackInvokedCallback callback = withSystemCallback(task);
- SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
- BackNavigationInfo backNavigationInfo = mBackNavigationController.startBackNavigation(mWm,
- tx, true);
+ BackNavigationInfo backNavigationInfo =
+ mBackNavigationController.startBackNavigation(true, null);
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull();
assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull();
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
-
- verify(tx, atLeastOnce()).apply();
- verify(tx, times(1)).reparent(any(),
- eq(backNavigationInfo.getDepartingAnimationTarget().leash));
}
@Test
@@ -243,7 +233,7 @@
@Nullable
private BackNavigationInfo startBackNavigation() {
- return mBackNavigationController.startBackNavigation(mWm, new StubTransaction(), true);
+ return mBackNavigationController.startBackNavigation(true, null);
}
@NonNull
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index d94e6c9..b87c5a3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -399,9 +400,9 @@
parentBounds.right / 2, parentBounds.bottom);
final Rect childBounds2 = new Rect(parentBounds.right / 2, parentBounds.top,
parentBounds.right, parentBounds.bottom);
- TestDisplayArea parentDa = new TestDisplayArea(mWm, parentBounds);
- TestDisplayArea childDa1 = new TestDisplayArea(mWm, childBounds1);
- TestDisplayArea childDa2 = new TestDisplayArea(mWm, childBounds2);
+ TestDisplayArea parentDa = new TestDisplayArea(mWm, parentBounds, "Parent");
+ TestDisplayArea childDa1 = new TestDisplayArea(mWm, childBounds1, "Child1");
+ TestDisplayArea childDa2 = new TestDisplayArea(mWm, childBounds2, "Child2");
parentDa.addChild(childDa1, 0);
parentDa.addChild(childDa2, 1);
@@ -619,9 +620,67 @@
controller.registerOrganizer(mockDisplayAreaOrganizer, FEATURE_VENDOR_FIRST);
}
+ @Test
+ public void testSetAlwaysOnTop_movesDisplayAreaToTop() {
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+ parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child1 = new TestDisplayArea(mWm, bounds, "Child1");
+ child1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child2 = new TestDisplayArea(mWm, bounds, "Child2");
+ child2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ parent.addChild(child2, 0);
+ parent.addChild(child1, 1);
+
+ child2.setAlwaysOnTop(true);
+
+ assertEquals(parent.getChildAt(1), child2);
+ assertThat(child2.isAlwaysOnTop()).isTrue();
+ }
+
+ @Test
+ public void testDisplayAreaRequestsTopPosition_alwaysOnTopSiblingExists_doesNotMoveToTop() {
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+ parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> alwaysOnTopChild = new TestDisplayArea(mWm, bounds,
+ "AlwaysOnTopChild");
+ alwaysOnTopChild.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child = new TestDisplayArea(mWm, bounds, "Child");
+ child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ parent.addChild(alwaysOnTopChild, 0);
+ parent.addChild(child, 1);
+ alwaysOnTopChild.setAlwaysOnTop(true);
+
+ parent.positionChildAt(POSITION_TOP, child, false /* includingParents */);
+
+ assertEquals(parent.getChildAt(1), alwaysOnTopChild);
+ assertEquals(parent.getChildAt(0), child);
+ }
+
+ @Test
+ public void testAlwaysOnTopDisplayArea_requestsNonTopLocation_doesNotMove() {
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ DisplayArea<WindowContainer> parent = new TestDisplayArea(mWm, bounds, "Parent");
+ parent.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> alwaysOnTopChild = new TestDisplayArea(mWm, bounds,
+ "AlwaysOnTopChild");
+ alwaysOnTopChild.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ DisplayArea<WindowContainer> child = new TestDisplayArea(mWm, bounds, "Child");
+ child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ parent.addChild(alwaysOnTopChild, 0);
+ parent.addChild(child, 1);
+ alwaysOnTopChild.setAlwaysOnTop(true);
+
+ parent.positionChildAt(POSITION_BOTTOM, alwaysOnTopChild, false /* includingParents */);
+
+ assertEquals(parent.getChildAt(1), alwaysOnTopChild);
+ assertEquals(parent.getChildAt(0), child);
+ }
+
private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
- private TestDisplayArea(WindowManagerService wms, Rect bounds) {
- super(wms, ANY, "half display area");
+ private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) {
+ super(wms, ANY, name);
setBounds(bounds);
}
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 d6b807f..641a3ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -170,7 +170,7 @@
@RunWith(WindowTestRunner.class)
public class DisplayContentTests extends WindowTestsBase {
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows() {
final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
@@ -203,7 +203,7 @@
assertForAllWindowsOrder(windows);
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithAppImeTarget() {
final WindowState imeAppTarget =
@@ -225,7 +225,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mChildAppWindowAbove);
@@ -243,7 +243,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mStatusBarWindow);
@@ -261,7 +261,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithNotificationShadeImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mNotificationShadeWindow);
@@ -279,7 +279,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithInBetweenWindowToken() {
// This window is set-up to be z-ordered between some windows that go in the same token like
@@ -301,7 +301,7 @@
mNavBarWindow));
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testComputeImeTarget() {
// Verify that an app window can be an ime target.
@@ -321,7 +321,7 @@
assertEquals(childWin, imeTarget);
}
- @UseTestDisplay(addAllCommonWindows = true)
+ @SetupWindows(addAllCommonWindows = true)
@Test
public void testComputeImeTarget_startingWindow() {
ActivityRecord activity = createActivityRecord(mDisplayContent);
@@ -985,7 +985,7 @@
assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() {
final DisplayContent newDisplay = createNewDisplay();
@@ -1020,7 +1020,7 @@
mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testInputMethodSet_listenOnDisplayAreaConfigurationChanged() {
spyOn(mAtm);
@@ -1159,7 +1159,7 @@
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_app_notMatchParentBounds() {
spyOn(mAppWindow.mActivityRecord);
@@ -1178,7 +1178,7 @@
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_inputTargetNotUpdate() throws Exception {
WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1");
@@ -1193,7 +1193,7 @@
assertNull(mDisplayContent.computeImeParent());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_updateParentWhenTargetNotUseIme() throws Exception {
WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay");
@@ -1268,7 +1268,7 @@
dc.computeImeControlTarget());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeSecureFlagGetUpdatedAfterImeInputTarget() {
// Verify IME window can get up-to-date secure flag update when the IME input target
@@ -1282,7 +1282,7 @@
verify(t).setSecure(eq(mDisplayContent.mInputMethodWindow.mSurfaceControl), eq(true));
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testComputeImeControlTarget_notMatchParentBounds() throws Exception {
spyOn(mAppWindow.mActivityRecord);
@@ -1426,7 +1426,7 @@
win.setHasSurface(false);
}
- @UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
+ @SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY })
@Test
public void testRequestResizeForEmptyFrames() {
final WindowState win = mChildAppWindowAbove;
@@ -1501,11 +1501,12 @@
assertNull(displayContent.getAsyncRotationController());
}
- @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
+ @SetupWindows(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
public void testApplyTopFixedRotationTransform() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ spyOn(displayPolicy);
// Only non-movable (gesture) navigation bar will be animated by fixed rotation animation.
doReturn(false).when(displayPolicy).navigationBarCanMove();
displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
@@ -1624,6 +1625,8 @@
// The display should be rotated after the launch is finished.
doReturn(false).when(app).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+ mStatusBarWindow.finishSeamlessRotation(t);
+ mNavBarWindow.finishSeamlessRotation(t);
// The fixed rotation should be cleared and the new rotation is applied to display.
assertFalse(app.hasFixedRotationTransform());
@@ -1655,7 +1658,7 @@
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testRotateSeamlesslyWithFixedRotation() {
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
@@ -2081,7 +2084,7 @@
verifySizes(dc, forcedWidth, forcedHeight, forcedDensity);
}
- @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testComputeImeTarget_shouldNotCheckOutdatedImeTargetLayerWhenRemoved() {
final WindowState child1 = createWindow(mAppWindow, FIRST_SUB_WINDOW, "child1");
@@ -2104,7 +2107,7 @@
verify(child1, never()).needsRelativeLayeringToIme();
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testAttachAndShowImeScreenshotOnTarget() {
// Preparation: Simulate screen state is on.
@@ -2155,7 +2158,7 @@
assertNotNull(mDisplayContent.mImeScreenshot);
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testShowImeScreenshot() {
final Task rootTask = createTask(mDisplayContent);
@@ -2181,7 +2184,7 @@
verify(mDisplayContent, never()).showImeScreenshot();
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD})
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testShowImeScreenshot_removeCurSnapshotBeforeCreateNext() {
final Task rootTask = createTask(mDisplayContent);
@@ -2471,7 +2474,7 @@
ACTIVITY_TYPE_STANDARD));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeChildWindowFocusWhenImeLayeringTargetChanges() {
final WindowState imeChildWindow =
@@ -2496,7 +2499,7 @@
assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() {
final WindowState imeMenuDialog =
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index f41fee7..2158caf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -36,7 +36,7 @@
@SmallTest
@Presubmit
-@WindowTestsBase.UseTestDisplay(
+@WindowTestsBase.SetupWindows(
addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
@RunWith(WindowTestRunner.class)
public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 34575ae..6bdc2e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -67,7 +67,7 @@
*/
@SmallTest
@Presubmit
-@WindowTestsBase.UseTestDisplay(
+@WindowTestsBase.SetupWindows(
addWindows = { WindowTestsBase.W_STATUS_BAR, WindowTestsBase.W_NAVIGATION_BAR })
@RunWith(WindowTestRunner.class)
public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 8f2e9b4..9cc665b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -167,7 +167,7 @@
dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
}
- @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+ @SetupWindows(addWindows = W_NAVIGATION_BAR)
@Test
public void testUpdateLightNavigationBarLw() {
DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -204,7 +204,7 @@
APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar));
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_STATUS_BAR })
@Test
public void testComputeTopFullscreenOpaqueWindow() {
final WindowManager.LayoutParams attrs = mAppWindow.mAttrs;
@@ -292,7 +292,7 @@
return win;
}
- @UseTestDisplay(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
+ @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
@Test
public void testImeMinimalSourceFrame() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -328,7 +328,7 @@
assertTrue(imeSource.getFrame().contains(navBarSource.getFrame()));
}
- @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
+ @SetupWindows(addWindows = W_NAVIGATION_BAR)
@Test
public void testInsetsGivenContentFrame() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index d3282b9..041f298 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -253,7 +253,7 @@
assertEquals(ITYPE_NAVIGATION_BAR, panelControls[0].getType());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
final WindowState statusBar = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar");
@@ -295,7 +295,7 @@
.getSource(ITYPE_NAVIGATION_BAR).isVisible());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
@@ -325,7 +325,7 @@
}
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar")
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 6c161cf..fe14d8e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -131,7 +131,7 @@
assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_independentSources() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -148,7 +148,7 @@
.isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_belowIme() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -161,7 +161,7 @@
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_aboveIme() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -173,7 +173,7 @@
.isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_imeOrderChanged() {
// This can be the IME z-order target while app cannot be the IME z-order target.
@@ -228,7 +228,7 @@
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_altFocusable() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -249,7 +249,7 @@
.isVisible());
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testStripForDispatch_childWindow_splitScreen() {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
@@ -456,7 +456,7 @@
assertNotNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testGetInsetsHintForNewControl() {
final WindowState app1 = createTestWindow("app1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 027f521..c548dc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -749,7 +749,7 @@
}
}
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testLaunchRemoteAnimationWithoutImeBehind() {
final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 324e244..21839aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -61,8 +61,10 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -82,11 +84,14 @@
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
import com.android.internal.policy.SystemBarUtils;
+import com.android.internal.statusbar.LetterboxDetails;
+import com.android.server.statusbar.StatusBarManagerInternal;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -97,6 +102,7 @@
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
/**
* Tests for Size Compatibility mode.
@@ -2113,6 +2119,104 @@
assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(organizer.mPrimary.getBounds());
}
+ @Test
+ public void testLetterboxDetailsForStatusBar_noLetterbox() {
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ // We should get a null LetterboxDetails object as there is no letterboxed activity, so
+ // nothing will get passed to SysUI
+ verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), isNull());
+
+ }
+
+ @Test
+ public void testLetterboxDetailsForStatusBar_letterboxedForMaxAspectRatio() {
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ // Refresh the letterbox
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(850, 0, 1950, 1000));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
+ mBounds,
+ mActivity.getDisplayContent().getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
+ @Test
+ public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ setUpDisplaySizeWithApp(2800, 1000);
+ addStatusBar(mActivity.mDisplayContent);
+ // Create another task for the second activity
+ final Task newTask = new TaskBuilder(mSupervisor).setDisplay(mActivity.getDisplayContent())
+ .setCreateActivity(true).build();
+ ActivityRecord newActivity = newTask.getTopNonFinishingActivity();
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Move first activity to split screen which takes half of the screen.
+ organizer.mPrimary.setBounds(0, 0, 1400, 1000);
+ organizer.putTaskToPrimary(mTask, true);
+ // Move second activity to split screen which takes half of the screen.
+ organizer.mSecondary.setBounds(1400, 0, 2800, 1000);
+ organizer.putTaskToSecondary(newTask, true);
+
+ addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
+ // appearance inside letterboxDetails
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+ addWindowToActivity(newActivity);
+ prepareUnresizable(newActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // Refresh the letterboxes
+ newActivity.mRootWindowContainer.performSurfacePlacement();
+
+ Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
+ assertEquals(mBounds, new Rect(150, 0, 1250, 1000));
+ final Rect newBounds = new Rect(newActivity.getWindowConfiguration().getBounds());
+ assertEquals(newBounds, new Rect(1550, 0, 2650, 1000));
+
+ DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
+ LetterboxDetails[] expectedLetterboxDetails = { new LetterboxDetails(
+ mBounds,
+ organizer.mPrimary.getBounds(),
+ mActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ ), new LetterboxDetails(
+ newBounds,
+ organizer.mSecondary.getBounds(),
+ newActivity.findMainWindow().mAttrs.insetsFlags.appearance
+ )};
+
+ // Check that letterboxDetails actually gets passed to SysUI
+ StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
+ verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
+ any(), anyBoolean(), anyInt(),
+ any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ }
+
private void recomputeNaturalConfigurationOfUnresizableActivity() {
// Recompute the natural configuration of the non-resizable activity and the split screen.
mActivity.clearSizeCompatMode();
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 97f0918..f38731b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -199,9 +199,11 @@
final Task parent = mock(Task.class);
final Configuration parentConfig = new Configuration();
parentConfig.smallestScreenWidthDp = 10;
- doReturn(parent).when(mTaskFragment).getParent();
+ doReturn(parent).when(mTaskFragment).getTask();
doReturn(parentConfig).when(parent).getConfiguration();
- doReturn(parent).when(parent).asTask();
+ // Task needs to be visible
+ parent.lastActiveTime = 100;
+ doReturn(true).when(parent).shouldBeVisible(any());
mTaskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentParentInfoChanged(
@@ -420,6 +422,7 @@
@Test
public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
throws RemoteException {
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(mIOrganizer);
final TaskFragment taskFragment2 =
new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */);
final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken();
@@ -595,6 +598,25 @@
}
@Test
+ public void testApplyTransaction_skipTransactionForUnregisterOrganizer() {
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+ final IBinder fragmentToken = new Binder();
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+ // Nothing should happen as the organizer is not registered.
+ assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+ mController.registerOrganizer(mIOrganizer);
+ mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
+
+ // Successfully created when the organizer is registered.
+ assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ @Test
public void testTaskFragmentInPip_startActivityInTaskFragment() {
setupTaskFragmentInPip();
final ActivityRecord activity = mTaskFragment.getTopMostActivity();
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 5f30963..1096351 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -23,6 +23,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.os.Process.FIRST_APPLICATION_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -31,6 +32,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -433,6 +437,40 @@
}
@Test
+ public void testIsAllowedToEmbedActivity() {
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .createActivityCount(1)
+ .build();
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+
+ // Not allow embedding activity if not a trusted host.
+ doReturn(false).when(taskFragment).isAllowedToEmbedActivityInUntrustedMode(any());
+ doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
+ assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+ taskFragment.isAllowedToEmbedActivity(activity));
+
+ // Not allow embedding activity if the TaskFragment is smaller than activity min dimension.
+ doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
+ doReturn(true).when(taskFragment).smallerThanMinDimension(any());
+ assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+ taskFragment.isAllowedToEmbedActivity(activity));
+
+ // Not allow to start activity across TaskFragments for result.
+ final TaskFragment newTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(taskFragment.getTask())
+ .build();
+ final ActivityRecord newActivity = new ActivityBuilder(mAtm)
+ .setUid(FIRST_APPLICATION_UID)
+ .build();
+ doReturn(true).when(newTaskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
+ doReturn(false).when(newTaskFragment).smallerThanMinDimension(any());
+ newActivity.resultTo = activity;
+ assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
+ newTaskFragment.isAllowedToEmbedActivity(newActivity));
+ }
+
+ @Test
public void testIgnoreRequestedOrientationForActivityEmbeddingSplit() {
// Setup two activities in ActivityEmbedding split.
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index e433684..06a176f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -190,7 +190,7 @@
}
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testCreateTaskSnapshotWithExcludingIme() {
Task task = mAppWindow.mActivityRecord.getTask();
@@ -209,7 +209,7 @@
}
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testCreateTaskSnapshotWithIncludingIme() {
Task task = mAppWindow.mActivityRecord.getTask();
@@ -237,7 +237,7 @@
}
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testPrepareTaskSnapshot() {
mAppWindow.mWinAnimator.mLastAlpha = 1f;
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 4227939..5a2d456 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -63,9 +63,11 @@
import android.util.ArraySet;
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizer;
+import android.window.IRemoteTransition;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.ITransitionPlayer;
+import android.window.RemoteTransition;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
@@ -416,6 +418,38 @@
}
@Test
+ public void testRunningRemoteTransition() {
+ final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
+ mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
+ final WindowProcessController playerProc = mSystemServicesTestRule.addProcess(
+ "pkg.player", "proc.player", 5000 /* pid */, 5000 /* uid */);
+ testPlayer.mController.registerTransitionPlayer(testPlayer, playerProc);
+ doReturn(mock(IBinder.class)).when(playerProc.getThread()).asBinder();
+ final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess(
+ "pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */);
+ doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final TransitionController controller = app.mTransitionController;
+ final Transition transition = controller.createTransition(TRANSIT_OPEN);
+ final RemoteTransition remoteTransition = new RemoteTransition(
+ mock(IRemoteTransition.class));
+ remoteTransition.setAppThread(delegateProc.getThread());
+ transition.collectExistenceChange(app.getTask());
+ controller.requestStartTransition(transition, app.getTask(), remoteTransition,
+ null /* displayChange */);
+ testPlayer.startTransition();
+ testPlayer.onTransactionReady(app.getSyncTransaction());
+ assertTrue(playerProc.isRunningRemoteTransition());
+ assertTrue(delegateProc.isRunningRemoteTransition());
+ assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+
+ testPlayer.finish();
+ assertFalse(playerProc.isRunningRemoteTransition());
+ assertFalse(delegateProc.isRunningRemoteTransition());
+ assertFalse(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+ }
+
+ @Test
public void testOpenActivityInTheSameTaskWithDisplayChange() {
final ActivityRecord closing = createActivityRecord(mDisplayContent);
closing.mVisibleRequested = true;
@@ -864,7 +898,7 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
- controller.registerTransitionPlayer(player, null /* appThread */);
+ controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
@@ -929,7 +963,7 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
- controller.registerTransitionPlayer(player, null /* appThread */);
+ controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
@@ -993,7 +1027,7 @@
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
- controller.registerTransitionPlayer(player, null /* appThread */);
+ controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 64959f2..593e983 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -45,7 +45,7 @@
@RunWith(WindowTestRunner.class)
public class WindowContainerTraversalTests extends WindowTestsBase {
- @UseTestDisplay(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
+ @SetupWindows(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
@Test
public void testDockedDividerPosition() {
final WindowState splitScreenWindow = createWindow(null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 685a8f4..05cc0cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -33,6 +33,10 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
import static com.google.common.truth.Truth.assertThat;
@@ -275,7 +279,7 @@
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(),
- new InsetsSourceControl[0], new Rect());
+ new InsetsSourceControl[0], new Rect(), new float[1]);
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
any(), anyInt(), anyInt(), any());
@@ -375,6 +379,18 @@
assertThat(wct).isNull();
}
+ @Test
+ public void testisLetterboxBackgroundMultiColored() {
+ assertThat(setupLetterboxConfigurationWithBackgroundType(
+ LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
+ assertThat(setupLetterboxConfigurationWithBackgroundType(
+ LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND)).isTrue();
+ assertThat(setupLetterboxConfigurationWithBackgroundType(
+ LETTERBOX_BACKGROUND_WALLPAPER)).isTrue();
+ assertThat(setupLetterboxConfigurationWithBackgroundType(
+ LETTERBOX_BACKGROUND_SOLID_COLOR)).isFalse();
+ }
+
private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
when(remoteToken.toWindowContainerToken()).thenReturn(wct);
@@ -384,4 +400,10 @@
testActivity.mLaunchCookie = launchCookie;
testActivity.getTask().mRemoteToken = remoteToken;
}
+
+ private boolean setupLetterboxConfigurationWithBackgroundType(
+ @LetterboxConfiguration.LetterboxBackgroundType int letterboxBackgroundType) {
+ mWm.mLetterboxConfiguration.setLetterboxBackgroundType(letterboxBackgroundType);
+ return mWm.isLetterboxBackgroundMultiColored();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 84c2c55..9693532 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -810,7 +810,6 @@
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
}
- @UseTestDisplay
@Test
public void testTaskInfoCallback() {
final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>();
@@ -842,8 +841,7 @@
lastReportedTiles.clear();
called[0] = false;
- final Task rootTask2 = createTask(
- mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
+ final Task rootTask2 = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
wct = new WindowContainerTransaction();
wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
info1.token, true /* onTop */);
@@ -869,7 +867,6 @@
assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
}
- @UseTestDisplay
@Test
public void testHierarchyTransaction() {
final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>();
@@ -890,23 +887,22 @@
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+ // 2 + 1 (home) = 3
final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */).size();
-
final Task rootTask = createTask(
mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
- final Task rootTask2 = createTask(
- mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
// Check getRootTasks works
List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */);
- assertEquals(initialRootTaskCount + 2, roots.size());
+ assertEquals(initialRootTaskCount + 1, roots.size());
lastReportedTiles.clear();
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
info1.token, true /* onTop */);
+ final Task rootTask2 = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
info2.token, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
@@ -940,7 +936,8 @@
// Check that getRootTasks doesn't include children of tiles
roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId,
null /* activityTypes */);
- assertEquals(initialRootTaskCount, roots.size());
+ // Home (rootTask2) was moved into task1, so only remain 2 roots: task1 and task2.
+ assertEquals(initialRootTaskCount - 1, roots.size());
lastReportedTiles.clear();
wct = new WindowContainerTransaction();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 334cfd5..5c7b882 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -418,7 +418,7 @@
assertFalse(app.canAffectSystemUiFlags());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_STATUS_BAR })
@Test
public void testVisibleWithInsetsProvider() {
final WindowState statusBar = mStatusBarWindow;
@@ -694,7 +694,7 @@
verify(t).setMatrix(child2.mSurfaceControl, w.mInvGlobalScale, 0, 0, w.mInvGlobalScale);
}
- @UseTestDisplay(addWindows = {W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE})
+ @SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
@Test
public void testRequestDrawIfNeeded() {
final WindowState startingApp = createWindow(null /* parent */,
@@ -739,7 +739,7 @@
assertFalse(startingApp.getOrientationChanging());
}
- @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
+ @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testReportResizedWithRemoteException() {
final WindowState win = mChildAppWindowAbove;
@@ -769,7 +769,7 @@
assertFalse(win.getOrientationChanging());
}
- @UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
+ @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testRequestResizeForBlastSync() {
final WindowState win = mChildAppWindowAbove;
@@ -907,7 +907,7 @@
assertTrue(mAtm.mActiveUids.hasNonAppVisibleWindow(uid));
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testNeedsRelativeLayeringToIme_notAttached() {
WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken,
@@ -920,7 +920,7 @@
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testNeedsRelativeLayeringToIme_startingWindow() {
WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
@@ -963,7 +963,7 @@
assertFalse(overlay.getWindowFrames().hasInsetsChanged());
}
- @UseTestDisplay(addWindows = {W_INPUT_METHOD, W_ACTIVITY})
+ @SetupWindows(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
@Test
public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
@@ -1009,7 +1009,6 @@
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is visible on app, but not for app2 during app task switching.
- mDisplayContent.computeImeTargetIfNeeded(app.mActivityRecord);
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
@@ -1054,7 +1053,7 @@
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY})
+ @SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
@@ -1080,7 +1079,7 @@
assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE})
+ @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
public void testNotificationShadeHasImeInsetsWhenMultiWindow() {
WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 0cbf1b2..564d3ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -110,6 +110,7 @@
import org.junit.runner.Description;
import org.mockito.Mockito;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -151,7 +152,8 @@
*/
DisplayContent mDisplayContent;
- // The following fields are only available depending on the usage of annotation UseTestDisplay.
+ // The following fields are only available depending on the usage of annotation UseTestDisplay
+ // and UseCommonWindows.
WindowState mWallpaperWindow;
WindowState mImeWindow;
WindowState mImeDialogWindow;
@@ -214,14 +216,15 @@
// Only create an additional test display for annotated test class/method because it may
// significantly increase the execution time.
final Description description = mSystemServicesTestRule.getDescription();
- UseTestDisplay testDisplayAnnotation = description.getAnnotation(UseTestDisplay.class);
- if (testDisplayAnnotation == null) {
- testDisplayAnnotation = description.getTestClass().getAnnotation(UseTestDisplay.class);
- }
- if (testDisplayAnnotation != null) {
- createTestDisplay(testDisplayAnnotation);
+ final UseTestDisplay useTestDisplay = getAnnotation(description, UseTestDisplay.class);
+ if (useTestDisplay != null) {
+ createTestDisplay(useTestDisplay);
} else {
mDisplayContent = mDefaultDisplay;
+ final SetupWindows setupWindows = getAnnotation(description, SetupWindows.class);
+ if (setupWindows != null) {
+ addCommonWindows(setupWindows.addAllCommonWindows(), setupWindows.addWindows());
+ }
}
// Ensure letterbox aspect ratio is not overridden on any device target.
@@ -290,10 +293,15 @@
private void createTestDisplay(UseTestDisplay annotation) {
beforeCreateTestDisplay();
mDisplayContent = createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
+ addCommonWindows(annotation.addAllCommonWindows(), annotation.addWindows());
+ mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
- final boolean addAll = annotation.addAllCommonWindows();
- final @CommonTypes int[] requestedWindows = annotation.addWindows();
+ // Adding a display will cause freezing the display. Make sure to wait until it's
+ // unfrozen to not run into race conditions with the tests.
+ waitUntilHandlersIdle();
+ }
+ private void addCommonWindows(boolean addAll, @CommonTypes int[] requestedWindows) {
if (addAll || ArrayUtils.contains(requestedWindows, W_WALLPAPER)) {
mWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow");
}
@@ -346,12 +354,6 @@
mChildAppWindowBelow = createCommonWindow(mAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY,
"mChildAppWindowBelow");
}
-
- mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
-
- // Adding a display will cause freezing the display. Make sure to wait until it's
- // unfrozen to not run into race conditions with the tests.
- waitUntilHandlersIdle();
}
private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) {
@@ -830,7 +832,7 @@
TestTransitionPlayer registerTestTransitionPlayer() {
final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
- testPlayer.mController.registerTransitionPlayer(testPlayer, null /* appThread */);
+ testPlayer.mController.registerTransitionPlayer(testPlayer, null /* playerProc */);
return testPlayer;
}
@@ -877,11 +879,21 @@
}
/**
+ * The annotation to provide common windows on default display. This is mutually exclusive
+ * with {@link UseTestDisplay}.
+ */
+ @Target({ ElementType.METHOD, ElementType.TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface SetupWindows {
+ boolean addAllCommonWindows() default false;
+ @CommonTypes int[] addWindows() default {};
+ }
+
+ /**
* The annotation for class and method (higher priority) to create a non-default display that
* will be assigned to {@link #mDisplayContent}. It is used if the test needs
* <ul>
* <li>Pure empty display.</li>
- * <li>Configured common windows.</li>
* <li>Independent and customizable orientation.</li>
* <li>Cross display operation.</li>
* </ul>
@@ -896,6 +908,12 @@
@CommonTypes int[] addWindows() default {};
}
+ static <T extends Annotation> T getAnnotation(Description desc, Class<T> type) {
+ final T annotation = desc.getAnnotation(type);
+ if (annotation != null) return annotation;
+ return desc.getTestClass().getAnnotation(type);
+ }
+
/** Creates and adds a {@link TestDisplayContent} to supervisor at the given position. */
TestDisplayContent addNewDisplayContentAt(int position) {
return new TestDisplayContent.Builder(mAtm, 1000, 1500).setPosition(position).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 4b5f330a..3ff791b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -258,7 +258,7 @@
* states for its children windows and by default it shouldn't let IME window setting
* the frozen insets state even the window of the window token is the IME layering target.
*/
- @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @SetupWindows(addWindows = W_INPUT_METHOD)
@Test
public void testSetInsetsFrozen_notAffectImeWindowState() {
// Pre-condition: make the IME window be controlled by IME insets provider.
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a0b01a7..046ff66 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -639,6 +639,11 @@
mAppStandby.initializeDefaultsForSystemApps(userId);
}
+ private boolean sameApp(int callingUid, @UserIdInt int userId, String packageName) {
+ return mPackageManagerInternal.getPackageUid(packageName, 0 /* flags */, userId)
+ == callingUid;
+ }
+
private boolean isInstantApp(String packageName, int userId) {
return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
}
@@ -2289,6 +2294,11 @@
}
@Override
+ public boolean isAppStandbyEnabled() {
+ return mAppStandby.isAppIdleEnabled();
+ }
+
+ @Override
public boolean isAppInactive(String packageName, int userId, String callingPackage) {
final int callingUid = Binder.getCallingUid();
try {
@@ -2354,17 +2364,15 @@
}
final int packageUid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
// If the calling app is asking about itself, continue, else check for permission.
- if (packageUid != callingUid) {
- if (!hasPermission(callingPackage)) {
- throw new SecurityException(
- "Don't have permission to query app standby bucket");
- }
+ final boolean sameApp = packageUid == callingUid;
+ if (!sameApp && !hasPermission(callingPackage)) {
+ throw new SecurityException("Don't have permission to query app standby bucket");
}
final boolean isInstantApp = isInstantApp(packageName, userId);
final boolean cannotAccessInstantApps = shouldObfuscateInstantAppsForCaller(callingUid,
userId);
- if (packageUid < 0 || (isInstantApp && cannotAccessInstantApps)) {
+ if (packageUid < 0 || (!sameApp && isInstantApp && cannotAccessInstantApps)) {
throw new IllegalArgumentException(
"Cannot get standby bucket for non existent package (" + packageName + ")");
}
@@ -2418,7 +2426,9 @@
}
final int targetUserId = userId;
standbyBucketList.removeIf(
- i -> cannotAccessInstantApps && isInstantApp(i.mPackageName, targetUserId));
+ i -> !sameApp(callingUid, targetUserId, i.mPackageName)
+ && isInstantApp(i.mPackageName, targetUserId)
+ && cannotAccessInstantApps);
return new ParceledListSlice<>(standbyBucketList);
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7699262..4b9b4a9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -102,6 +102,7 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.soundtrigger.SoundTriggerInternal;
+import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -134,18 +135,21 @@
private final RemoteCallbackList<IVoiceInteractionSessionListener>
mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+ // TODO(b/226201975): remove once RoleService supports pre-created users
+ private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
+
public VoiceInteractionManagerService(Context context) {
super(context);
mContext = context;
mResolver = context.getContentResolver();
+ mUserManagerInternal = Objects.requireNonNull(
+ LocalServices.getService(UserManagerInternal.class));
mDbHelper = new DatabaseHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
mAmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
- mUserManagerInternal = Objects.requireNonNull(
- LocalServices.getService(UserManagerInternal.class));
LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService(
LegacyPermissionManagerInternal.class);
@@ -223,12 +227,13 @@
class LocalService extends VoiceInteractionManagerInternal {
@Override
- public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options) {
+ public void startLocalVoiceInteraction(@NonNull IBinder callingActivity,
+ @Nullable String attributionTag, @NonNull Bundle options) {
if (DEBUG) {
Slog.i(TAG, "startLocalVoiceInteraction " + callingActivity);
}
VoiceInteractionManagerService.this.mServiceStub.startLocalVoiceInteraction(
- callingActivity, options);
+ callingActivity, attributionTag, options);
}
@Override
@@ -300,6 +305,25 @@
}
return hotwordDetectionConnection.mIdentity;
}
+
+ @Override
+ public void onPreCreatedUserConversion(int userId) {
+ Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
+
+ for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
+ UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
+ if (preCreatedUser.getIdentifier() == userId) {
+ Slogf.d(TAG, "Updating role on pre-created user %d", userId);
+ mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+ preCreatedUser);
+ mIgnoredPreCreatedUsers.remove(i);
+ return;
+ }
+ }
+ Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
+ + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+ }
+
}
// implementation entry point and binder service
@@ -317,10 +341,12 @@
private boolean mTemporarilyDisabled;
private final boolean mEnableService;
+ // TODO(b/226201975): remove reference once RoleService supports pre-created users
+ private final RoleObserver mRoleObserver;
VoiceInteractionManagerServiceStub() {
mEnableService = shouldEnableService(mContext);
- new RoleObserver(mContext.getMainExecutor());
+ mRoleObserver = new RoleObserver(mContext.getMainExecutor());
}
void handleUserStop(String packageName, int userHandle) {
@@ -383,14 +409,15 @@
}
// TODO: VI Make sure the caller is the current user or profile
- void startLocalVoiceInteraction(final IBinder token, Bundle options) {
+ void startLocalVoiceInteraction(@NonNull final IBinder token,
+ @Nullable String attributionTag, @NonNull Bundle options) {
if (mImpl == null) return;
final int callingUid = Binder.getCallingUid();
final long caller = Binder.clearCallingIdentity();
try {
mImpl.showSessionLocked(options,
- VoiceInteractionSession.SHOW_SOURCE_ACTIVITY,
+ VoiceInteractionSession.SHOW_SOURCE_ACTIVITY, attributionTag,
new IVoiceInteractionSessionShowCallback.Stub() {
@Override
public void onFailed() {
@@ -898,13 +925,13 @@
}
@Override
- public void showSession(Bundle args, int flags) {
+ public void showSession(@NonNull Bundle args, int flags, @Nullable String attributionTag) {
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.showSessionLocked(args, flags, null, null);
+ mImpl.showSessionLocked(args, flags, attributionTag, null, null);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -929,7 +956,8 @@
}
@Override
- public boolean showSessionFromSession(IBinder token, Bundle sessionArgs, int flags) {
+ public boolean showSessionFromSession(@NonNull IBinder token, @NonNull Bundle sessionArgs,
+ int flags, @Nullable String attributionTag) {
synchronized (this) {
if (mImpl == null) {
Slog.w(TAG, "showSessionFromSession without running voice interaction service");
@@ -937,7 +965,7 @@
}
final long caller = Binder.clearCallingIdentity();
try {
- return mImpl.showSessionLocked(sessionArgs, flags, null, null);
+ return mImpl.showSessionLocked(sessionArgs, flags, attributionTag, null, null);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -961,8 +989,8 @@
}
@Override
- public int startVoiceActivity(IBinder token, Intent intent, String resolvedType,
- String callingFeatureId) {
+ public int startVoiceActivity(@NonNull IBinder token, @NonNull Intent intent,
+ @Nullable String resolvedType, @Nullable String attributionTag) {
synchronized (this) {
if (mImpl == null) {
Slog.w(TAG, "startVoiceActivity without running voice interaction service");
@@ -980,8 +1008,8 @@
} else {
Slog.w(TAG, "Cannot find ActivityInfo in startVoiceActivity.");
}
- return mImpl.startVoiceActivityLocked(
- callingFeatureId, callingPid, callingUid, token, intent, resolvedType);
+ return mImpl.startVoiceActivityLocked(attributionTag, callingPid, callingUid,
+ token, intent, resolvedType);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -989,8 +1017,8 @@
}
@Override
- public int startAssistantActivity(IBinder token, Intent intent, String resolvedType,
- String callingFeatureId) {
+ public int startAssistantActivity(@NonNull IBinder token, @NonNull Intent intent,
+ @Nullable String resolvedType, @Nullable String attributionTag) {
synchronized (this) {
if (mImpl == null) {
Slog.w(TAG, "startAssistantActivity without running voice interaction service");
@@ -1000,7 +1028,7 @@
final int callingUid = Binder.getCallingUid();
final long caller = Binder.clearCallingIdentity();
try {
- return mImpl.startAssistantActivityLocked(callingFeatureId, callingPid,
+ return mImpl.startAssistantActivityLocked(attributionTag, callingPid,
callingUid, token, intent, resolvedType);
} finally {
Binder.restoreCallingIdentity(caller);
@@ -1669,8 +1697,10 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
@Override
- public boolean showSessionForActiveService(Bundle args, int sourceFlags,
- IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
+ public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+ @Nullable String attributionTag,
+ @Nullable IVoiceInteractionSessionShowCallback showCallback,
+ @Nullable IBinder activityToken) {
if (DEBUG_USER) Slog.d(TAG, "showSessionForActiveService()");
synchronized (this) {
@@ -1691,7 +1721,7 @@
sourceFlags
| VoiceInteractionSession.SHOW_WITH_ASSIST
| VoiceInteractionSession.SHOW_WITH_SCREENSHOT,
- showCallback, activityToken);
+ attributionTag, showCallback, activityToken);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -1882,6 +1912,7 @@
pw.println(" mTemporarilyDisabled: " + mTemporarilyDisabled);
pw.println(" mCurUser: " + mCurUser);
pw.println(" mCurUserSupported: " + mCurUserSupported);
+ pw.println(" mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
dumpSupportedUsers(pw, " ");
mDbHelper.dump(pw);
if (mImpl == null) {
@@ -1995,6 +2026,23 @@
List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
+ // TODO(b/226201975): this method is beling called when a pre-created user is added,
+ // at which point it doesn't have any role holders. But it's not called again when
+ // the actual user is added (i.e., when the pre-created user is converted), so we
+ // need to save the user id and call this method again when it's converted
+ // (at onPreCreatedUserConversion()).
+ // Once RoleService properly handles pre-created users, this workaround should be
+ // removed.
+ if (roleHolders.isEmpty()) {
+ UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
+ if (userInfo != null && userInfo.preCreated) {
+ Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
+ userInfo.toFullString());
+ mIgnoredPreCreatedUsers.add(user);
+ return;
+ }
+ }
+
int userId = user.getIdentifier();
if (roleHolders.isEmpty()) {
Settings.Secure.putStringForUser(getContext().getContentResolver(),
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b9793ca..9f66059 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -244,8 +244,10 @@
/* direct= */ true);
}
- public boolean showSessionLocked(Bundle args, int flags,
- IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
+ public boolean showSessionLocked(@NonNull Bundle args, int flags,
+ @Nullable String attributionTag,
+ @Nullable IVoiceInteractionSessionShowCallback showCallback,
+ @Nullable IBinder activityToken) {
if (mActiveSession == null) {
mActiveSession = new VoiceInteractionSessionConnection(mServiceStub,
mSessionComponentName, mUser, mContext, this,
@@ -269,8 +271,8 @@
} else {
visibleActivities = allVisibleActivities;
}
- return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
- visibleActivities);
+ return mActiveSession.showLocked(args, flags, attributionTag, mDisabledShowContext,
+ showCallback, visibleActivities);
}
public void getActiveServiceSupportedActions(List<String> commands,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
index 9bdf4e4..80f5dc5 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
@@ -114,8 +114,8 @@
try {
Bundle args = new Bundle();
- boolean ok = mService.showSessionForActiveService(args, /* sourceFlags= */ 0, callback,
- /* activityToken= */ null);
+ boolean ok = mService.showSessionForActiveService(args, /* sourceFlags= */ 0,
+ /* attributionTag= */ null, callback, /* activityToken= */ null);
if (!ok) {
pw.println("showSessionForActiveService() returned false");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 093e976..63781cc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -29,6 +29,8 @@
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_TASK_ID;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
@@ -263,9 +265,9 @@
return flags;
}
- public boolean showLocked(Bundle args, int flags, int disabledContext,
- IVoiceInteractionSessionShowCallback showCallback,
- List<ActivityAssistInfo> topActivities) {
+ public boolean showLocked(@NonNull Bundle args, int flags, @Nullable String attributionTag,
+ int disabledContext, @Nullable IVoiceInteractionSessionShowCallback showCallback,
+ @NonNull List<ActivityAssistInfo> topActivities) {
if (mBound) {
if (!mFullyBound) {
mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -291,12 +293,15 @@
for (int i = 0; i < topActivitiesCount; i++) {
topActivitiesToken.add(topActivities.get(i).getActivityToken());
}
+
mAssistDataRequester.requestAssistData(topActivitiesToken,
fetchData,
fetchScreenshot,
(disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
(disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
- mCallingUid, mSessionComponentName.getPackageName());
+ mCallingUid,
+ mSessionComponentName.getPackageName(),
+ attributionTag);
boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
|| mAssistDataRequester.getPendingScreenshotCount() > 0;
diff --git a/startop/view_compiler/dex_builder_test/AndroidTest.xml b/startop/view_compiler/dex_builder_test/AndroidTest.xml
index 82509b9..59093c7 100644
--- a/startop/view_compiler/dex_builder_test/AndroidTest.xml
+++ b/startop/view_compiler/dex_builder_test/AndroidTest.xml
@@ -21,7 +21,7 @@
<option name="test-file-name" value="dex-builder-test.apk" />
</target_preparer>
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="trivial.dex->/data/local/tmp/dex-builder-test/trivial.dex" />
<option name="push" value="simple.dex->/data/local/tmp/dex-builder-test/simple.dex" />
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 4469ffc..7eec86a 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -115,15 +115,15 @@
/** @hide */
public static @RadioAccessNetworkType int fromString(@NonNull String str) {
switch (str.toUpperCase()) {
- case "GERAN" : return GERAN;
- case "UTRAN" : return UTRAN;
- case "EUTRAN" : return EUTRAN;
- case "CDMA2000" : return CDMA2000;
- case "IWLAN" : return IWLAN;
- case "NGRAN" : return NGRAN;
+ case "UNKNOWN": return UNKNOWN;
+ case "GERAN": return GERAN;
+ case "UTRAN": return UTRAN;
+ case "EUTRAN": return EUTRAN;
+ case "CDMA2000": return CDMA2000;
+ case "IWLAN": return IWLAN;
+ case "NGRAN": return NGRAN;
default:
- Rlog.e(TAG, "Invalid access network type " + str);
- return UNKNOWN;
+ throw new IllegalArgumentException("Invalid access network type " + str);
}
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0bf4088..4d18dfe 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,7 +39,6 @@
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
import android.telephony.gba.TlsParams;
import android.telephony.gba.UaSecurityProtocolIdentifier;
import android.telephony.ims.ImsReasonInfo;
@@ -1126,27 +1125,6 @@
public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
/**
- * The data call retry configuration for different types of APN.
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
- "carrier_data_call_retry_config_strings";
-
- /**
- * Delay in milliseconds between trying APN from the pool
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
- "carrier_data_call_apn_delay_default_long";
-
- /**
- * Faster delay in milliseconds between trying APN from the pool
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
- "carrier_data_call_apn_delay_faster_long";
-
- /**
* Delay in milliseconds for retrying APN after disconnect
* @hide
*/
@@ -1154,21 +1132,6 @@
"carrier_data_call_apn_retry_after_disconnect_long";
/**
- * The maximum times for telephony to retry data setup on the same APN requested by
- * network through the data setup response retry timer
- * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
- * asking device to retry data setup forever and causes power consumption issue. For infinite
- * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
- *
- * Note if network does not suggest any retry timer, frameworks uses the retry configuration
- * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
- * be configured there.
- * @hide
- */
- public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
- "carrier_data_call_retry_network_requested_max_count_int";
-
- /**
* Data call setup permanent failure causes by the carrier
*/
public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
@@ -1188,19 +1151,6 @@
"carrier_metered_roaming_apn_types_strings";
/**
- * APN types that are not allowed on cellular
- * @hide
- */
- public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
- "carrier_wwan_disallowed_apn_types_string_array";
-
- /**
- * APN types that are not allowed on IWLAN
- * @hide
- */
- public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
- "carrier_wlan_disallowed_apn_types_string_array";
- /**
* CDMA carrier ERI (Enhanced Roaming Indicator) file name
* @hide
*/
@@ -8419,7 +8369,6 @@
* "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
* 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
*
- * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
* @hide
*/
public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8647,6 +8596,13 @@
* IWLAN handover rules that determine whether handover is allowed or disallowed between
* cellular and IWLAN.
*
+ * Rule syntax: "source=[GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN], target=[GERAN|UTRAN|EUTRAN
+ * |NGRAN|IWLAN], type=[allowed|disallowed], roaming=[true|false], capabilities=[INTERNET|MMS
+ * |FOTA|IMS|CBS|SUPL|EIMS|XCAP|DUN]"
+ *
+ * Note that UNKNOWN can be only specified in the source access network and can be only used
+ * in the disallowed rule.
+ *
* The handover rules will be matched in the order. Here are some sample rules.
* <string-array name="iwlan_handover_rules" num="5">
* <!-- Handover from IWLAN to 2G/3G is not allowed -->
@@ -8814,27 +8770,13 @@
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
- sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
- "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
- + "320000:5000,640000:5000,1280000:5000,1800000:5000",
- "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
- + "320000:5000,640000:5000,1280000:5000,1800000:5000",
- "ims:max_retries=10, 5000, 5000, 5000",
- "others:max_retries=3, 5000, 5000, 5000"});
- sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
- sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
- sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
- sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
- new String[]{""});
- sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
- new String[]{""});
sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ce6b14..da1ffcd 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2510,9 +2510,6 @@
CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
String callingFeatureId);
- /** Check if telephony new data stack is enabled. */
- boolean isUsingNewDataStack();
-
/**
* @return true if the modem service is set successfully, false otherwise.
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 24c4661..e0ae193 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -40,6 +40,7 @@
testSpec.setIsTablet(
WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
)
+ tapl.setExpectedRotationCheckEnabled(true)
}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
index 4e91ce1..3853af2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
@@ -49,6 +49,9 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
+ test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
eachRun {
testApp.launchViaIntent(wmHelper)
}
@@ -58,7 +61,7 @@
}
teardown {
test {
- device.pressHome()
+ tapl.goHome()
testApp.exit(wmHelper)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index d53d96b..9cc1bfe 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -22,6 +22,7 @@
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
@@ -60,6 +61,7 @@
* apps are running before setup
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 1348d7a..58a8011 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -21,6 +21,7 @@
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
@@ -59,6 +60,7 @@
* apps are running before setup
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -68,12 +70,17 @@
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
+ setup {
+ test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
+ }
transitions {
// Can't use TAPL at the moment because of rotation test issues
// When pressing home, TAPL expects the orientation to remain constant
// However, when closing a landscape app back to a portrait-only launcher
// this causes an error in verifyActiveContainer();
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
new file mode 100644
index 0000000..bd0ae4d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/CameraAppHelper.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.provider.MediaStore
+import com.android.server.wm.traces.common.ComponentMatcher
+import com.android.server.wm.traces.common.IComponentMatcher
+
+class CameraAppHelper @JvmOverloads constructor(
+ instrumentation: Instrumentation,
+ private val pkgManager: PackageManager = instrumentation.context.packageManager
+) : StandardAppHelper(instrumentation, getCameraLauncherName(pkgManager),
+ getCameraComponent(pkgManager)){
+ companion object{
+ private fun getCameraIntent(): Intent {
+ return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+ }
+
+ private fun getResolveInfo(pkgManager: PackageManager): ResolveInfo {
+ val intent = getCameraIntent()
+ return pkgManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ ?: error("unable to resolve camera activity")
+ }
+
+ private fun getCameraComponent(pkgManager: PackageManager): IComponentMatcher {
+ val resolveInfo = getResolveInfo(pkgManager)
+ return ComponentMatcher(resolveInfo.activityInfo.packageName,
+ className = resolveInfo.activityInfo.name)
+ }
+
+ private fun getCameraLauncherName(pkgManager: PackageManager): String {
+ val resolveInfo = getResolveInfo(pkgManager)
+ return resolveInfo.activityInfo.name
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 99c3ac6..a92ecb9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -57,6 +57,9 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
+ test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
eachRun {
testApp.launchViaIntent(wmHelper)
}
@@ -67,7 +70,7 @@
}
}
transitions {
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.withImeGone()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index 6ceb9ea..0e4d1dd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -46,6 +46,9 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
+ test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
eachRun {
imeTestApp.launchViaIntent(wmHelper)
imeTestApp.openIME(wmHelper)
@@ -59,7 +62,7 @@
}
teardown {
eachRun {
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index f00a7ab..856df26 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -49,13 +49,16 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
+ test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
eachRun {
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
}
}
transitions {
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.withImeGone()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index e85b9c0..d900815 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -55,12 +55,15 @@
override val transition: FlickerBuilder.() -> Unit = {
setup {
test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
+ test {
// Launch the activity with expecting IME will be shown.
imeTestApp.launchViaIntent(wmHelper)
}
eachRun {
// Swiping out the IME activity to home.
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 4954adb..2207fe5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -30,7 +30,6 @@
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.ComponentMatcher
-import com.android.server.wm.traces.common.ComponentMatcher.Companion.LAUNCHER
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -68,6 +67,7 @@
transitions {
device.reopenAppFromOverview(wmHelper)
wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp)
.withImeShown()
.waitForAndVerify()
}
@@ -111,9 +111,9 @@
@Test
fun launcherWindowBecomesInvisible() {
testSpec.assertWm {
- this.isAppWindowVisible(LAUNCHER)
+ this.isAppWindowVisible(ComponentMatcher.LAUNCHER)
.then()
- .isAppWindowInvisible(LAUNCHER)
+ .isAppWindowInvisible(ComponentMatcher.LAUNCHER)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 0aeeb03..be7b80e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -29,7 +29,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.ComponentMatcher
@@ -65,6 +64,9 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
+ test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
eachRun {
this.setRotation(testSpec.startRotation)
testApp.launchViaIntent(wmHelper)
@@ -82,7 +84,7 @@
}
teardown {
eachRun {
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
@@ -93,12 +95,7 @@
transitions {
// [Step1]: Swipe right from imeTestApp to testApp task
createTag(TAG_IME_VISIBLE)
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
- device.swipe(
- 0, displayBounds.bounds.height,
- displayBounds.bounds.width, displayBounds.bounds.height, 50
- )
-
+ tapl.launchedAppState.quickSwitchToPreviousApp()
wmHelper.StateSyncBuilder()
.withFullScreenApp(testApp)
.waitForAndVerify()
@@ -106,11 +103,7 @@
}
transitions {
// [Step2]: Swipe left to back to imeTestApp task
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
- device.swipe(
- displayBounds.bounds.width, displayBounds.bounds.height,
- 0, displayBounds.bounds.height, 50
- )
+ tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
wmHelper.StateSyncBuilder()
.withFullScreenApp(imeTestApp)
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
new file mode 100644
index 0000000..3ff59e9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.CameraAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app after cold opening camera
+ *
+ * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
+ *
+ * Notes:
+ * Some default assertions are inherited [OpenAppTransition]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+open class OpenAppAfterCameraTest(
+ testSpec: FlickerTestParameter
+) : OpenAppFromLauncherTransition(testSpec) {
+ private val cameraApp = CameraAppHelper(instrumentation) /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ test{
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
+ eachRun {
+ // 1. Open camera - cold -> close it first
+ cameraApp.exit(wmHelper)
+ cameraApp.launchViaIntent(wmHelper)
+ // 2. Press home button (button nav mode) / swipe up to home (gesture nav mode)
+ tapl.goHome()
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit(wmHelper)
+ }
+ }
+ transitions {
+ testApp.launchViaIntent(wmHelper)
+ }
+ }
+
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 206753786)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun focusChanges() = super.focusChanges()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() =
+ super.appWindowReplacesLauncherAsTopWindow()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 4640d36..cd01f74 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -22,6 +22,7 @@
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
@@ -50,6 +51,7 @@
* apps are running before setup
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 34c0b23..7c07ace 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -23,6 +23,7 @@
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
@@ -52,6 +53,7 @@
* apps are running before setup
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -68,11 +70,11 @@
super.transition(this)
setup {
test {
+ tapl.setExpectedRotationCheckEnabled(false)
testApp.launchViaIntent(wmHelper)
}
eachRun {
- // Can't use tapl.goHome() because of b/235841947
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 1ad5426..6476077 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -25,6 +25,7 @@
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
@@ -57,6 +58,7 @@
* apps are running before setup
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -72,6 +74,7 @@
@FlakyTest(bugId = 227083463)
@Test
fun navBarLayerVisibilityChanges() {
+ Assume.assumeFalse(testSpec.isTablet)
testSpec.assertLayers {
this.isInvisible(ComponentMatcher.NAV_BAR)
.then()
@@ -134,27 +137,32 @@
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun taskBarLayerIsVisibleAtStartAndEnd() { }
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ }
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun navBarLayerIsVisibleAtStartAndEnd() { }
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ }
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun taskBarWindowIsAlwaysVisible() { }
+ override fun taskBarWindowIsAlwaysVisible() {
+ }
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun navBarWindowIsAlwaysVisible() { }
+ override fun navBarWindowIsAlwaysVisible() {
+ }
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun statusBarWindowIsAlwaysVisible() { }
+ override fun statusBarWindowIsAlwaysVisible() {
+ }
/**
* Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the
@@ -170,6 +178,7 @@
@Postsubmit
@Test
fun navBarLayerIsVisibleAtEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
testSpec.assertLayersEnd {
this.isVisible(ComponentMatcher.NAV_BAR)
}
@@ -179,7 +188,7 @@
@FlakyTest
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
@Presubmit
@@ -205,7 +214,7 @@
@FlakyTest(bugId = 218470989)
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@FlakyTest(bugId = 227143265)
@Test
@@ -223,12 +232,12 @@
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- repetitions = 3,
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ .getConfigNonRotationTests(
+ repetitions = 3,
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
+ supportedRotations = listOf(Surface.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index bc5ab7d..4f69f01 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -22,6 +22,7 @@
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
@@ -50,6 +51,7 @@
* apps are running before setup
*/
@RequiresDevice
+@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -64,10 +66,11 @@
super.transition(this)
setup {
test {
+ tapl.setExpectedRotationCheckEnabled(false)
testApp.launchViaIntent(wmHelper)
}
eachRun {
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 2c3fce9..510043b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -60,12 +60,15 @@
override val transition: FlickerBuilder.() -> Unit = {
setup {
test {
+ tapl.setExpectedRotationCheckEnabled(false)
+ }
+ test {
tapl.setExpectedRotation(testSpec.startRotation)
}
eachRun {
testApp.launchViaIntent(wmHelper)
- device.pressHome()
+ tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
.withWindowSurfaceDisappeared(testApp)
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
index a8613f5..95f933f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
@@ -16,6 +16,8 @@
package com.android.server.wm.flicker.testapp;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
@@ -30,6 +32,7 @@
WindowManager.LayoutParams p = getWindow().getAttributes();
p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ p.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN;
getWindow().setAttributes(p);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);