Merge "Remove requestCursorUpdatesFromImm() again"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
deleted file mode 100644
index 78a77fe..0000000
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.job.controllers;
-
-import java.util.Objects;
-
-/** Wrapper class to represent a userId-pkgName combo. */
-final class Package {
- public final String packageName;
- public final int userId;
-
- Package(int userId, String packageName) {
- this.userId = userId;
- this.packageName = packageName;
- }
-
- @Override
- public String toString() {
- return packageToString(userId, packageName);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof Package)) {
- return false;
- }
- Package other = (Package) obj;
- return userId == other.userId && Objects.equals(packageName, other.packageName);
- }
-
- @Override
- public int hashCode() {
- return packageName.hashCode() + userId;
- }
-
- /**
- * Standardize the output of userId-packageName combo.
- */
- static String packageToString(int userId, String packageName) {
- return "<" + userId + ">" + packageName;
- }
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index d69b9e0..c46ffd7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -21,7 +21,6 @@
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
-import static com.android.server.job.controllers.Package.packageToString;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
@@ -31,6 +30,7 @@
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
import android.appwidget.AppWidgetManager;
import android.content.Context;
+import android.content.pm.UserPackage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -172,7 +172,7 @@
final String pkgName = jobStatus.getSourcePackageName();
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
- mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+ mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
}
}
@@ -186,7 +186,7 @@
final int userId = UserHandle.getUserId(uid);
mTrackedJobs.delete(userId, packageName);
mEstimatedLaunchTimes.delete(userId, packageName);
- mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName));
+ mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, packageName));
}
@Override
@@ -354,7 +354,7 @@
@CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
if (jobs == null || jobs.size() == 0) {
- mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+ mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
return;
}
@@ -365,10 +365,10 @@
// Set alarm to be notified when this crosses the threshold.
final long timeToCrossThresholdMs =
nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs);
- mThresholdAlarmListener.addAlarm(new Package(userId, pkgName),
+ mThresholdAlarmListener.addAlarm(UserPackage.of(userId, pkgName),
nowElapsed + timeToCrossThresholdMs);
} else {
- mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+ mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
}
}
@@ -427,25 +427,25 @@
}
/** Track when apps will cross the "will run soon" threshold. */
- private class ThresholdAlarmListener extends AlarmQueue<Package> {
+ private class ThresholdAlarmListener extends AlarmQueue<UserPackage> {
private ThresholdAlarmListener(Context context, Looper looper) {
super(context, looper, "*job.prefetch*", "Prefetch threshold", false,
PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10);
}
@Override
- protected boolean isForUser(@NonNull Package key, int userId) {
+ protected boolean isForUser(@NonNull UserPackage key, int userId) {
return key.userId == userId;
}
@Override
- protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+ protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
synchronized (mLock) {
final long now = sSystemClock.millis();
final long nowElapsed = sElapsedRealtimeClock.millis();
for (int i = 0; i < expired.size(); ++i) {
- Package p = expired.valueAt(i);
+ UserPackage p = expired.valueAt(i);
if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) {
Slog.e(TAG, "Alarm expired for "
+ packageToString(p.userId, p.packageName) + " at the wrong time");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 659e7c0..d8206ad 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -28,7 +28,6 @@
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.Package.packageToString;
import android.Manifest;
import android.annotation.NonNull;
@@ -44,6 +43,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
@@ -532,7 +532,7 @@
*/
private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>();
- /** An app has reached its quota. The message should contain a {@link Package} object. */
+ /** An app has reached its quota. The message should contain a {@link UserPackage} object. */
@VisibleForTesting
static final int MSG_REACHED_QUOTA = 0;
/** Drop any old timing sessions. */
@@ -542,7 +542,7 @@
/** Process state for a UID has changed. */
private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
/**
- * An app has reached its expedited job quota. The message should contain a {@link Package}
+ * An app has reached its expedited job quota. The message should contain a {@link UserPackage}
* object.
*/
@VisibleForTesting
@@ -680,7 +680,7 @@
final String pkgName = jobStatus.getSourcePackageName();
ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
- mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+ mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
}
}
}
@@ -746,7 +746,7 @@
}
mTimingEvents.delete(userId, packageName);
mEJTimingSessions.delete(userId, packageName);
- mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+ mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
mExecutionStatsCache.delete(userId, packageName);
mEJStats.delete(userId, packageName);
mTopAppTrackers.delete(userId, packageName);
@@ -1725,7 +1725,7 @@
// exempted.
maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
} else {
- mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+ mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
}
return changedJobs;
}
@@ -1764,7 +1764,7 @@
&& isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
// TODO(141645789): we probably shouldn't cancel the alarm until we've verified
// that all jobs for the userId-package are within quota.
- mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+ mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
} else {
mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
}
@@ -1814,7 +1814,7 @@
if (jobs == null || jobs.size() == 0) {
Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
+ packageToString(userId, packageName) + " that has no jobs");
- mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+ mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
return;
}
@@ -1838,7 +1838,7 @@
+ getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
+ "ms in its quota.");
}
- mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+ mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
return;
}
@@ -1903,7 +1903,7 @@
+ nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats);
inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS;
}
- mInQuotaAlarmQueue.addAlarm(new Package(userId, packageName), inQuotaTimeElapsed);
+ mInQuotaAlarmQueue.addAlarm(UserPackage.of(userId, packageName), inQuotaTimeElapsed);
}
private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
@@ -2098,7 +2098,7 @@
}
private final class Timer {
- private final Package mPkg;
+ private final UserPackage mPkg;
private final int mUid;
private final boolean mRegularJobTimer;
@@ -2110,7 +2110,7 @@
private long mDebitAdjustment;
Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
- mPkg = new Package(userId, packageName);
+ mPkg = UserPackage.of(userId, packageName);
mUid = uid;
mRegularJobTimer = regularJobTimer;
}
@@ -2365,7 +2365,7 @@
}
private final class TopAppTimer {
- private final Package mPkg;
+ private final UserPackage mPkg;
// List of jobs currently running for this app that started when the app wasn't in the
// foreground.
@@ -2373,7 +2373,7 @@
private long mStartTimeElapsed;
TopAppTimer(int userId, String packageName) {
- mPkg = new Package(userId, packageName);
+ mPkg = UserPackage.of(userId, packageName);
}
private int calculateTimeChunks(final long nowElapsed) {
@@ -2656,7 +2656,7 @@
synchronized (mLock) {
switch (msg.what) {
case MSG_REACHED_QUOTA: {
- Package pkg = (Package) msg.obj;
+ UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
}
@@ -2685,7 +2685,7 @@
break;
}
case MSG_REACHED_EJ_QUOTA: {
- Package pkg = (Package) msg.obj;
+ UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
}
@@ -2887,21 +2887,21 @@
}
/** Track when UPTCs are expected to come back into quota. */
- private class InQuotaAlarmQueue extends AlarmQueue<Package> {
+ private class InQuotaAlarmQueue extends AlarmQueue<UserPackage> {
private InQuotaAlarmQueue(Context context, Looper looper) {
super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false,
QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
}
@Override
- protected boolean isForUser(@NonNull Package key, int userId) {
+ protected boolean isForUser(@NonNull UserPackage key, int userId) {
return key.userId == userId;
}
@Override
- protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+ protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
for (int i = 0; i < expired.size(); ++i) {
- Package p = expired.valueAt(i);
+ UserPackage p = expired.valueAt(i);
mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName)
.sendToTarget();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 0eedcf0..44ac798 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -186,4 +186,11 @@
/** Dump any internal constants the Controller may have. */
public void dumpConstants(ProtoOutputStream proto) {
}
+
+ /**
+ * Standardize the output of userId-packageName combo.
+ */
+ static String packageToString(int userId, String packageName) {
+ return "<" + userId + ">" + packageName;
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 7a13e3f..abc196f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -36,6 +36,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.UserPackage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -824,7 +825,7 @@
void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) {
mScribe.discardLedgerLocked(userId, pkgName);
mCurrentOngoingEvents.delete(userId, pkgName);
- mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+ mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
}
@GuardedBy("mLock")
@@ -959,7 +960,7 @@
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
// No ongoing transactions. No reason to schedule
- mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+ mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
return;
}
mTrendCalculator.reset(getBalanceLocked(userId, pkgName),
@@ -972,7 +973,7 @@
if (lowerTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
if (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
// Will never cross a threshold based on current events.
- mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+ mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
return;
}
timeToThresholdMs = upperTimeMs;
@@ -980,7 +981,7 @@
timeToThresholdMs = (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD)
? lowerTimeMs : Math.min(lowerTimeMs, upperTimeMs);
}
- mBalanceThresholdAlarmQueue.addAlarm(new Package(userId, pkgName),
+ mBalanceThresholdAlarmQueue.addAlarm(UserPackage.of(userId, pkgName),
SystemClock.elapsedRealtime() + timeToThresholdMs);
}
@@ -1071,57 +1072,22 @@
private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater();
- private static final class Package {
- public final String packageName;
- public final int userId;
-
- Package(int userId, String packageName) {
- this.userId = userId;
- this.packageName = packageName;
- }
-
- @Override
- public String toString() {
- return appToString(userId, packageName);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (this == obj) {
- return true;
- }
- if (obj instanceof Package) {
- Package other = (Package) obj;
- return userId == other.userId && Objects.equals(packageName, other.packageName);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return packageName.hashCode() + userId;
- }
- }
-
/** Track when apps will cross the closest affordability threshold (in both directions). */
- private class BalanceThresholdAlarmQueue extends AlarmQueue<Package> {
+ private class BalanceThresholdAlarmQueue extends AlarmQueue<UserPackage> {
private BalanceThresholdAlarmQueue(Context context, Looper looper) {
super(context, looper, ALARM_TAG_AFFORDABILITY_CHECK, "Affordability check", true,
15_000L);
}
@Override
- protected boolean isForUser(@NonNull Package key, int userId) {
+ protected boolean isForUser(@NonNull UserPackage key, int userId) {
return key.userId == userId;
}
@Override
- protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+ protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
for (int i = 0; i < expired.size(); ++i) {
- Package p = expired.valueAt(i);
+ UserPackage p = expired.valueAt(i);
mHandler.obtainMessage(
MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName)
.sendToTarget();
diff --git a/cmds/uiautomator/OWNERS b/cmds/uiautomator/OWNERS
new file mode 100644
index 0000000..5c7f452
--- /dev/null
+++ b/cmds/uiautomator/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 833089
+peykov@google.com
+normancheung@google.com
+guran@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index 164e14a..e2f2fae 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8956,9 +8956,18 @@
package android.companion {
+ public final class AssociatedDevice implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.bluetooth.le.ScanResult getBleDevice();
+ method @Nullable public android.bluetooth.BluetoothDevice getBluetoothDevice();
+ method @Nullable public android.net.wifi.ScanResult getWifiDevice();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociatedDevice> CREATOR;
+ }
+
public final class AssociationInfo implements android.os.Parcelable {
method public int describeContents();
- method @Nullable public android.os.Parcelable getAssociatedDevice();
+ method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
method @Nullable public android.net.MacAddress getDeviceMacAddress();
method @Nullable public String getDeviceProfile();
method @Nullable public CharSequence getDisplayName();
@@ -18425,7 +18434,7 @@
method public android.graphics.Point getLeftEyePosition();
method public android.graphics.Point getMouthPosition();
method public android.graphics.Point getRightEyePosition();
- method public int getScore();
+ method @IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) public int getScore();
field public static final int ID_UNSUPPORTED = -1; // 0xffffffff
field public static final int SCORE_MAX = 100; // 0x64
field public static final int SCORE_MIN = 1; // 0x1
@@ -18440,7 +18449,7 @@
method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point);
method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point);
method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point);
- method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int);
+ method @NonNull public android.hardware.camera2.params.Face.Builder setScore(@IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) int);
}
public final class InputConfiguration {
@@ -19481,6 +19490,7 @@
public final class GnssCapabilities implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<android.location.GnssSignalType> getGnssSignalTypes();
method public boolean hasAntennaInfo();
method public boolean hasGeofencing();
method @Deprecated public boolean hasGnssAntennaInfo();
@@ -19514,6 +19524,7 @@
ctor public GnssCapabilities.Builder();
ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities);
method @NonNull public android.location.GnssCapabilities build();
+ method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasGeofencing(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);
@@ -19726,6 +19737,16 @@
field @Deprecated public static final int STATUS_READY = 1; // 0x1
}
+ public final class GnssSignalType implements android.os.Parcelable {
+ method @NonNull public static android.location.GnssSignalType create(int, @FloatRange(from=0.0f, fromInclusive=false) double, @NonNull String);
+ method public int describeContents();
+ method @FloatRange(from=0.0f, fromInclusive=false) public double getCarrierFrequencyHz();
+ method @NonNull public String getCodeType();
+ method public int getConstellationType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssSignalType> CREATOR;
+ }
+
public final class GnssStatus implements android.os.Parcelable {
method public int describeContents();
method @FloatRange(from=0, to=360) public float getAzimuthDegrees(@IntRange(from=0) int);
@@ -50091,7 +50112,7 @@
method public void dispatchCreateViewTranslationRequest(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @NonNull android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
- method protected void dispatchDraw(android.graphics.Canvas);
+ method protected void dispatchDraw(@NonNull android.graphics.Canvas);
method public void dispatchDrawableHotspotChanged(float, float);
method @CallSuper public void dispatchFinishTemporaryDetach();
method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
@@ -50129,7 +50150,7 @@
method @NonNull public android.view.WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimation, @NonNull android.view.WindowInsetsAnimation.Bounds);
method @Deprecated public void dispatchWindowSystemUiVisiblityChanged(int);
method public void dispatchWindowVisibilityChanged(int);
- method @CallSuper public void draw(android.graphics.Canvas);
+ method @CallSuper public void draw(@NonNull android.graphics.Canvas);
method @CallSuper public void drawableHotspotChanged(float, float);
method @CallSuper protected void drawableStateChanged();
method public android.view.View findFocus();
@@ -50422,9 +50443,9 @@
method @CallSuper protected void onDetachedFromWindow();
method protected void onDisplayHint(int);
method public boolean onDragEvent(android.view.DragEvent);
- method protected void onDraw(android.graphics.Canvas);
- method public void onDrawForeground(android.graphics.Canvas);
- method protected final void onDrawScrollBars(android.graphics.Canvas);
+ method protected void onDraw(@NonNull android.graphics.Canvas);
+ method public void onDrawForeground(@NonNull android.graphics.Canvas);
+ method protected final void onDrawScrollBars(@NonNull android.graphics.Canvas);
method public boolean onFilterTouchEventForSecurity(android.view.MotionEvent);
method @CallSuper protected void onFinishInflate();
method public void onFinishTemporaryDetach();
@@ -50910,7 +50931,7 @@
ctor public View.DragShadowBuilder(android.view.View);
ctor public View.DragShadowBuilder();
method public final android.view.View getView();
- method public void onDrawShadow(android.graphics.Canvas);
+ method public void onDrawShadow(@NonNull android.graphics.Canvas);
method public void onProvideShadowMetrics(android.graphics.Point, android.graphics.Point);
}
@@ -51140,7 +51161,7 @@
method public void dispatchSetActivated(boolean);
method public void dispatchSetSelected(boolean);
method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
- method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
+ method protected boolean drawChild(@NonNull android.graphics.Canvas, android.view.View, long);
method public void endViewTransition(android.view.View);
method public android.view.View focusSearch(android.view.View, int);
method public void focusableViewAvailable(android.view.View);
@@ -53353,6 +53374,7 @@
method public android.graphics.Matrix getMatrix();
method public int getSelectionEnd();
method public int getSelectionStart();
+ method @Nullable public android.view.inputmethod.TextAppearanceInfo getTextAppearanceInfo();
method @NonNull public java.util.List<android.graphics.RectF> getVisibleLineBounds();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.CursorAnchorInfo> CREATOR;
@@ -53373,6 +53395,7 @@
method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float, int);
method public android.view.inputmethod.CursorAnchorInfo.Builder setMatrix(android.graphics.Matrix);
method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
+ method @NonNull public android.view.inputmethod.CursorAnchorInfo.Builder setTextAppearanceInfo(@Nullable android.view.inputmethod.TextAppearanceInfo);
}
public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
@@ -53628,6 +53651,7 @@
field public static final int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 8; // 0x8
field public static final int CURSOR_UPDATE_FILTER_EDITOR_BOUNDS = 4; // 0x4
field public static final int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 16; // 0x10
+ field public static final int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 64; // 0x40
field public static final int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 32; // 0x20
field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1
field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
@@ -53931,6 +53955,35 @@
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR;
}
+ public final class TextAppearanceInfo implements android.os.Parcelable {
+ ctor public TextAppearanceInfo(@NonNull android.widget.TextView);
+ method public int describeContents();
+ method @Nullable public String getFontFamilyName();
+ method @Nullable public String getFontFeatureSettings();
+ method @Nullable public String getFontVariationSettings();
+ method public float getLetterSpacing();
+ method public int getLineBreakStyle();
+ method public int getLineBreakWordStyle();
+ method public int getMaxLength();
+ method @Px public float getShadowDx();
+ method @Px public float getShadowDy();
+ method @Px public float getShadowRadius();
+ method @ColorInt public int getTextColor();
+ method @ColorInt public int getTextColorHighlight();
+ method @ColorInt public int getTextColorHint();
+ method @Nullable public android.content.res.ColorStateList getTextColorLink();
+ method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
+ method @NonNull public android.os.LocaleList getTextLocales();
+ method public float getTextScaleX();
+ method @Px public float getTextSize();
+ method public int getTextStyle();
+ method public boolean isAllCaps();
+ method public boolean isElegantTextHeight();
+ method public boolean isFallbackLineSpacing();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR;
+ }
+
public final class TextAttribute implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.os.PersistableBundle getExtras();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 352b4f9..70b89b8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6346,12 +6346,21 @@
public final class AudioPlaybackConfiguration implements android.os.Parcelable {
method public int getClientPid();
method public int getClientUid();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMutedBy();
method public int getPlayerInterfaceId();
method public android.media.PlayerProxy getPlayerProxy();
method public int getPlayerState();
method public int getPlayerType();
method @IntRange(from=0) public int getSessionId();
method public boolean isActive();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20
field public static final int PLAYER_STATE_IDLE = 1; // 0x1
field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
field public static final int PLAYER_STATE_RELEASED = 0; // 0x0
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cdb1510..e9f9136 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3159,7 +3159,7 @@
public final class InputMethodManager {
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
method public int getDisplayId();
- method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
+ method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index c2c065b..45515dd 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -117,27 +117,27 @@
/**
* Bundle key used for the {@link String} account type in session bundle.
* This is used in the default implementation of
- * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_AUTH_TOKEN_TYPE =
"android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
/**
* Bundle key used for the {@link String} array of required features in
* session bundle. This is used in the default implementation of
- * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_REQUIRED_FEATURES =
"android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
/**
* Bundle key used for the {@link Bundle} options in session bundle. This is
* used in default implementation of {@link #startAddAccountSession} and
- * {@link startUpdateCredentialsSession}.
+ * {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_OPTIONS =
"android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
/**
* Bundle key used for the {@link Account} account in session bundle. This is used
- * used in default implementation of {@link startUpdateCredentialsSession}.
+ * used in default implementation of {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_ACCOUNT =
"android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
@@ -154,6 +154,8 @@
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
String authTokenType, String[] features, Bundle options)
throws RemoteException {
+ super.addAccount_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "addAccount: accountType " + accountType
+ ", authTokenType " + authTokenType
@@ -184,6 +186,8 @@
@Override
public void confirmCredentials(IAccountAuthenticatorResponse response,
Account account, Bundle options) throws RemoteException {
+ super.confirmCredentials_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "confirmCredentials: " + account);
}
@@ -210,6 +214,8 @@
public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
String authTokenType)
throws RemoteException {
+ super.getAuthTokenLabel_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
}
@@ -235,6 +241,8 @@
public void getAuthToken(IAccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions)
throws RemoteException {
+ super.getAuthToken_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthToken: " + account
+ ", authTokenType " + authTokenType);
@@ -262,6 +270,8 @@
@Override
public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
String authTokenType, Bundle loginOptions) throws RemoteException {
+ super.updateCredentials_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "updateCredentials: " + account
+ ", authTokenType " + authTokenType);
@@ -291,6 +301,8 @@
@Override
public void editProperties(IAccountAuthenticatorResponse response,
String accountType) throws RemoteException {
+ super.editProperties_enforcePermission();
+
try {
final Bundle result = AbstractAccountAuthenticator.this.editProperties(
new AccountAuthenticatorResponse(response), accountType);
@@ -306,6 +318,8 @@
@Override
public void hasFeatures(IAccountAuthenticatorResponse response,
Account account, String[] features) throws RemoteException {
+ super.hasFeatures_enforcePermission();
+
try {
final Bundle result = AbstractAccountAuthenticator.this.hasFeatures(
new AccountAuthenticatorResponse(response), account, features);
@@ -321,6 +335,8 @@
@Override
public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
Account account) throws RemoteException {
+ super.getAccountRemovalAllowed_enforcePermission();
+
try {
final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed(
new AccountAuthenticatorResponse(response), account);
@@ -336,6 +352,8 @@
@Override
public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
Account account) throws RemoteException {
+ super.getAccountCredentialsForCloning_enforcePermission();
+
try {
final Bundle result =
AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
@@ -353,6 +371,8 @@
public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
Account account,
Bundle accountCredentials) throws RemoteException {
+ super.addAccountFromCredentials_enforcePermission();
+
try {
final Bundle result =
AbstractAccountAuthenticator.this.addAccountFromCredentials(
@@ -371,6 +391,8 @@
public void startAddAccountSession(IAccountAuthenticatorResponse response,
String accountType, String authTokenType, String[] features, Bundle options)
throws RemoteException {
+ super.startAddAccountSession_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG,
"startAddAccountSession: accountType " + accountType
@@ -403,6 +425,8 @@
Account account,
String authTokenType,
Bundle loginOptions) throws RemoteException {
+ super.startUpdateCredentialsSession_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "startUpdateCredentialsSession: "
+ account
@@ -441,6 +465,8 @@
IAccountAuthenticatorResponse response,
String accountType,
Bundle sessionBundle) throws RemoteException {
+ super.finishSession_enforcePermission();
+
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "finishSession: accountType " + accountType);
}
@@ -468,6 +494,8 @@
IAccountAuthenticatorResponse response,
Account account,
String statusToken) throws RemoteException {
+ super.isCredentialsUpdateSuggested_enforcePermission();
+
try {
final Bundle result = AbstractAccountAuthenticator.this
.isCredentialsUpdateSuggested(
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index a573776..b3db38d 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -27,7 +27,6 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +36,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.database.SQLException;
import android.os.Build;
@@ -348,43 +348,11 @@
*/
public static final int CACHE_ACCOUNTS_DATA_SIZE = 4;
- private static final class UserIdPackage
- {
- @UserIdInt
- public int userId;
- public String packageName;
-
- public UserIdPackage(int UserId, String PackageName) {
- this.userId = UserId;
- this.packageName = PackageName;
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (o == null) {
- return false;
- }
- if (o == this) {
- return true;
- }
- if (o.getClass() != getClass()) {
- return false;
- }
- UserIdPackage e = (UserIdPackage) o;
- return e.userId == userId && e.packageName.equals(packageName);
- }
-
- @Override
- public int hashCode() {
- return userId ^ packageName.hashCode();
- }
- }
-
- PropertyInvalidatedCache<UserIdPackage, Account[]> mAccountsForUserCache =
- new PropertyInvalidatedCache<UserIdPackage, Account[]>(
+ PropertyInvalidatedCache<UserPackage, Account[]> mAccountsForUserCache =
+ new PropertyInvalidatedCache<UserPackage, Account[]>(
CACHE_ACCOUNTS_DATA_SIZE, CACHE_KEY_ACCOUNTS_DATA_PROPERTY) {
@Override
- public Account[] recompute(UserIdPackage userAndPackage) {
+ public Account[] recompute(UserPackage userAndPackage) {
try {
return mService.getAccountsAsUser(null, userAndPackage.userId, userAndPackage.packageName);
} catch (RemoteException e) {
@@ -392,7 +360,7 @@
}
}
@Override
- public boolean bypass(UserIdPackage query) {
+ public boolean bypass(UserPackage query) {
return query.userId < 0;
}
@Override
@@ -731,7 +699,7 @@
*/
@NonNull
public Account[] getAccountsAsUser(int userId) {
- UserIdPackage userAndPackage = new UserIdPackage(userId, mContext.getOpPackageName());
+ UserPackage userAndPackage = UserPackage.of(userId, mContext.getOpPackageName());
return mAccountsForUserCache.query(userAndPackage);
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 32d0d75..501b136 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8364,11 +8364,17 @@
}
final void performNewIntent(@NonNull Intent intent) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performNewIntent");
mCanEnterPictureInPicture = true;
onNewIntent(intent);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
final void performStart(String reason) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStart:"
+ + mComponent.getClassName());
+ }
dispatchActivityPreStarted();
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
@@ -8415,6 +8421,7 @@
mActivityTransitionState.enterReady(this);
dispatchActivityPostStarted();
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
/**
@@ -8423,7 +8430,8 @@
* The option to not start immediately is needed in case a transaction with
* multiple lifecycle transitions is in progress.
*/
- final void performRestart(boolean start, String reason) {
+ final void performRestart(boolean start) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performRestart");
mCanEnterPictureInPicture = true;
mFragments.noteStateNotSaved();
@@ -8458,17 +8466,18 @@
final long startTime = SystemClock.uptimeMillis();
mInstrumentation.callActivityOnRestart(this);
final long duration = SystemClock.uptimeMillis() - startTime;
- EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason,
- duration);
+ EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(),
+ "performRestart", duration);
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onRestart()");
}
if (start) {
- performStart(reason);
+ performStart("performRestart");
}
}
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
final void performResume(boolean followedByPause, String reason) {
@@ -8477,7 +8486,6 @@
+ mComponent.getClassName());
}
dispatchActivityPreResumed();
- performRestart(true /* start */, reason);
mFragments.execPendingActions();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 67d4416..1f63343 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -173,7 +173,6 @@
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import android.view.Display;
-import android.view.DisplayAdjustments;
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
@@ -244,7 +243,6 @@
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
/**
* This manages the execution of the main thread in an
@@ -409,7 +407,6 @@
boolean mInstrumentingWithoutRestart;
boolean mSystemThread = false;
boolean mSomeActivitiesChanged = false;
- /* package */ boolean mHiddenApiWarningShown = false;
// These can be accessed by multiple threads; mResourcesManager is the lock.
// XXX For now we keep around information about all packages we have
@@ -438,16 +435,10 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final ResourcesManager mResourcesManager;
- /** The active adjustments that override the {@link DisplayAdjustments} in resources. */
- private ArrayList<Pair<IBinder, Consumer<DisplayAdjustments>>> mActiveRotationAdjustments;
-
// Registry of remote cancellation transports pending a reply with reply handles.
@GuardedBy("this")
private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
- private final Map<IBinder, Integer> mLastReportedWindowingMode = Collections.synchronizedMap(
- new ArrayMap<>());
-
private static final class ProviderKey {
final String authority;
final int userId;
@@ -518,8 +509,6 @@
*/
private final Object mCoreSettingsLock = new Object();
- boolean mHasImeComponent = false;
-
private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null;
/** A client side controller to handle process level configuration changes. */
@@ -599,6 +588,12 @@
/** Whether this activiy was launched from a bubble. */
boolean mLaunchedFromBubble;
+ /**
+ * This can be different from the current configuration because a new configuration may not
+ * always update to activity, e.g. windowing mode change without size change.
+ */
+ int mLastReportedWindowingMode = WINDOWING_MODE_UNDEFINED;
+
@LifecycleState
private int mLifecycleState = PRE_ON_CREATE;
@@ -3654,8 +3649,7 @@
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
- mLastReportedWindowingMode.put(activity.getActivityToken(),
- config.windowConfiguration.getWindowingMode());
+ r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode();
}
r.setState(ON_CREATE);
@@ -3714,12 +3708,14 @@
// Call postOnCreate()
if (pendingActions.shouldCallOnPostCreate()) {
activity.mCalled = false;
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onPostCreate");
if (r.isPersistable()) {
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnPostCreate(activity, r.state);
}
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString()
@@ -5268,7 +5264,7 @@
@Override
public void performRestartActivity(ActivityClientRecord r, boolean start) {
if (r.stopped) {
- r.activity.performRestart(start, "performRestartActivity");
+ r.activity.performRestart(start);
if (start) {
r.setState(ON_START);
}
@@ -5285,7 +5281,7 @@
private void onCoreSettingsChange() {
if (updateDebugViewAttributeState()) {
// request all activities to relaunch for the changes to take place
- relaunchAllActivities(false /* preserveWindows */, "onCoreSettingsChange");
+ relaunchAllActivities(true /* preserveWindows */, "onCoreSettingsChange");
}
}
@@ -5433,7 +5429,6 @@
}
}
r.setState(ON_DESTROY);
- mLastReportedWindowingMode.remove(r.activity.getActivityToken());
schedulePurgeIdler();
synchronized (this) {
if (mSplashScreenGlobal != null) {
@@ -5865,7 +5860,7 @@
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
- final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
+ final Configuration reportedConfig = performActivityConfigurationChanged(r,
r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
return reportedConfig;
@@ -5873,7 +5868,7 @@
/**
* Decides whether to update an Activity's configuration and whether to inform it.
- * @param activity The activity to notify of configuration change.
+ * @param r The activity client record to notify of configuration change.
* @param newConfig The new configuration.
* @param amOverrideConfig The override config that differentiates the Activity's configuration
* from the base global configuration. This is supplied by
@@ -5881,29 +5876,29 @@
* @param displayId Id of the display where activity currently resides.
* @return Configuration sent to client, null if no changes and not moved to different display.
*/
- private Configuration performActivityConfigurationChanged(Activity activity,
+ private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
Configuration newConfig, Configuration amOverrideConfig, int displayId,
boolean alwaysReportChange) {
+ final Activity activity = r.activity;
final IBinder activityToken = activity.getActivityToken();
// WindowConfiguration differences aren't considered as public, check it separately.
// multi-window / pip mode changes, if any, should be sent before the configuration
// change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
- handleWindowingModeChangeIfNeeded(activity, newConfig);
+ handleWindowingModeChangeIfNeeded(r, newConfig);
final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
displayId);
final Configuration currentResConfig = activity.getResources().getConfiguration();
final int diff = currentResConfig.diffPublicOnly(newConfig);
final boolean hasPublicResConfigChange = diff != 0;
- final ActivityClientRecord r = getActivityClient(activityToken);
// TODO(b/173090263): Use diff instead after the improvement of AssetManager and
// ResourcesImpl constructions.
final boolean shouldUpdateResources = hasPublicResConfigChange
|| shouldUpdateResources(activityToken, currentResConfig, newConfig,
amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
final boolean shouldReportChange = shouldReportChange(
- activity.mCurrentConfig, newConfig, r != null ? r.mSizeConfigurations : null,
+ activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
// Nothing significant, don't proceed with updating and reporting.
if (!shouldUpdateResources && !shouldReportChange) {
@@ -6012,12 +6007,11 @@
* See also {@link Activity#onMultiWindowModeChanged(boolean, Configuration)} and
* {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)}
*/
- private void handleWindowingModeChangeIfNeeded(Activity activity,
+ private void handleWindowingModeChangeIfNeeded(ActivityClientRecord r,
Configuration newConfiguration) {
+ final Activity activity = r.activity;
final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode();
- final IBinder token = activity.getActivityToken();
- final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(token,
- WINDOWING_MODE_UNDEFINED);
+ final int oldWindowingMode = r.mLastReportedWindowingMode;
if (oldWindowingMode == newWindowingMode) return;
// PiP callback is sent before the MW one.
if (newWindowingMode == WINDOWING_MODE_PINNED) {
@@ -6032,7 +6026,7 @@
if (wasInMultiWindowMode != nowInMultiWindowMode) {
activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);
}
- mLastReportedWindowingMode.put(token, newWindowingMode);
+ r.mLastReportedWindowingMode = newWindowingMode;
}
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 267e5b6..dc325ff 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1368,9 +1368,31 @@
public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+ /**
+ * Prevent an app from being placed into app standby buckets.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_APP_STANDBY;
+
+ /**
+ * Prevent an app from being placed into forced app standby.
+ * {@link ActivityManager#isBackgroundRestricted()}
+ * {@link #OP_RUN_ANY_IN_BACKGROUND}
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 124;
+ public static final int _NUM_OP = 126;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1869,6 +1891,28 @@
*/
public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
+ /**
+ * Prevent an app from being placed into app standby buckets.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+ "android:system_exempt_from_app_standby";
+
+ /**
+ * Prevent an app from being placed into forced app standby.
+ * {@link ActivityManager#isBackgroundRestricted()}
+ * {@link #OP_RUN_ANY_IN_BACKGROUND}
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+ "android:system_exempt_from_forced_app_standby";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2350,7 +2394,13 @@
new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
.setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
- .setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+ OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+ "SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+ OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+ "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
};
/**
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 0f26818..ef10c0b 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -115,6 +115,15 @@
"file_patterns": ["(/|^)VoiceInteract[^/]*"]
},
{
+ "name": "CtsLocalVoiceInteraction",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ],
+ "file_patterns": ["(/|^)VoiceInteract[^/]*"]
+ },
+ {
"name": "CtsOsTestCases",
"options": [
{
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index a2dc47d..5a2f261 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -458,7 +458,8 @@
&& isVisible == that.isVisible
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
- && parentTaskId == that.parentTaskId;
+ && parentTaskId == that.parentTaskId
+ && Objects.equals(topActivity, that.topActivity);
}
/**
diff --git a/core/java/android/companion/AssociatedDevice.java b/core/java/android/companion/AssociatedDevice.java
index 3758cdb..a833661 100644
--- a/core/java/android/companion/AssociatedDevice.java
+++ b/core/java/android/companion/AssociatedDevice.java
@@ -16,6 +16,7 @@
package android.companion;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,19 +24,14 @@
import androidx.annotation.Nullable;
/**
- * Loose wrapper around device parcelable. Device can be one of three types:
+ * Container for device info from an association that is not self-managed.
+ * Device can be one of three types:
*
* <ul>
* <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
* <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
* <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
* </ul>
- *
- * This class serves as temporary wrapper to deliver a loosely-typed parcelable object from
- * {@link com.android.companiondevicemanager.CompanionDeviceActivity} to the Companion app,
- * and should only be used internally.
- *
- * @hide
*/
public final class AssociatedDevice implements Parcelable {
private static final int CLASSIC_BLUETOOTH = 0;
@@ -44,6 +40,7 @@
@NonNull private final Parcelable mDevice;
+ /** @hide */
public AssociatedDevice(@NonNull Parcelable device) {
mDevice = device;
}
@@ -54,11 +51,39 @@
}
/**
- * Return device info. Cast to expected device type.
+ * Return bluetooth device info. Null if associated device is not a bluetooth device.
+ * @return Remote bluetooth device details containing MAC address.
*/
- @NonNull
- public Parcelable getDevice() {
- return mDevice;
+ @Nullable
+ public BluetoothDevice getBluetoothDevice() {
+ if (mDevice instanceof BluetoothDevice) {
+ return (BluetoothDevice) mDevice;
+ }
+ return null;
+ }
+
+ /**
+ * Return bluetooth LE device info. Null if associated device is not a BLE device.
+ * @return BLE scan result containing details of detected BLE device.
+ */
+ @Nullable
+ public android.bluetooth.le.ScanResult getBleDevice() {
+ if (mDevice instanceof android.bluetooth.le.ScanResult) {
+ return (android.bluetooth.le.ScanResult) mDevice;
+ }
+ return null;
+ }
+
+ /**
+ * Return Wi-Fi device info. Null if associated device is not a Wi-Fi device.
+ * @return Wi-Fi scan result containing details of detected access point.
+ */
+ @Nullable
+ public android.net.wifi.ScanResult getWifiDevice() {
+ if (mDevice instanceof android.net.wifi.ScanResult) {
+ return (android.net.wifi.ScanResult) mDevice;
+ }
+ return null;
}
@Override
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 93964b3..5fd39fe 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -164,20 +164,19 @@
/**
* Companion device that was associated. Note that this field is not persisted across sessions.
- *
- * Cast to expected device type before use:
+ * Device can be one of the following types:
*
* <ul>
- * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
- * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
- * <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+ * <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li>
+ * <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li>
+ * <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li>
* </ul>
*
* @return the companion device that was associated, or {@code null} if the device is
- * self-managed.
+ * self-managed or this association info was retrieved from persistent storage.
*/
- public @Nullable Parcelable getAssociatedDevice() {
- return mAssociatedDevice == null ? null : mAssociatedDevice.getDevice();
+ public @Nullable AssociatedDevice getAssociatedDevice() {
+ return mAssociatedDevice;
}
/**
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b0c6cbc..e981581 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -29,6 +29,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.UserHandle;
import android.permission.PermissionManager;
import android.util.ArraySet;
@@ -297,7 +298,7 @@
public boolean checkCallingUid() {
final int callingUid = Binder.getCallingUid();
if (callingUid != Process.ROOT_UID
- && callingUid != Process.SYSTEM_UID
+ && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID
&& callingUid != mAttributionSourceState.uid) {
return false;
}
diff --git a/core/java/android/content/pm/UserPackage.java b/core/java/android/content/pm/UserPackage.java
new file mode 100644
index 0000000..e75f551
--- /dev/null
+++ b/core/java/android/content/pm/UserPackage.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Objects;
+
+/**
+ * POJO to represent a package for a specific user ID.
+ *
+ * @hide
+ */
+public final class UserPackage {
+ @UserIdInt
+ public final int userId;
+ public final String packageName;
+
+ @GuardedBy("sCache")
+ private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>();
+
+ private static final Object sUserIdLock = new Object();
+ private static final class NoPreloadHolder {
+ /** Set of userIDs to cache objects for. */
+ @GuardedBy("sUserIdLock")
+ private static int[] sUserIds = new int[]{UserHandle.getUserId(Process.myUid())};
+ }
+
+ private UserPackage(int userId, String packageName) {
+ this.userId = userId;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + userId + ">" + packageName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof UserPackage) {
+ UserPackage other = (UserPackage) obj;
+ return userId == other.userId && Objects.equals(packageName, other.packageName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ result = 31 * result + userId;
+ result = 31 * result + packageName.hashCode();
+ return result;
+ }
+
+ /** Return an instance of this class representing the given userId + packageName combination. */
+ @NonNull
+ public static UserPackage of(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (sUserIdLock) {
+ if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) {
+ // Don't cache objects for invalid userIds.
+ return new UserPackage(userId, packageName);
+ }
+ }
+ synchronized (sCache) {
+ UserPackage up = sCache.get(userId, packageName);
+ if (up == null) {
+ packageName = packageName.intern();
+ up = new UserPackage(userId, packageName);
+ sCache.add(userId, packageName, up);
+ }
+ return up;
+ }
+ }
+
+ /** Remove the specified app from the cache. */
+ public static void removeFromCache(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (sCache) {
+ sCache.delete(userId, packageName);
+ }
+ }
+
+ /** Indicate the list of valid user IDs on the device. */
+ public static void setValidUserIds(@NonNull int[] userIds) {
+ userIds = userIds.clone();
+ synchronized (sUserIdLock) {
+ NoPreloadHolder.sUserIds = userIds;
+ }
+ synchronized (sCache) {
+ for (int u = sCache.numMaps() - 1; u >= 0; --u) {
+ final int userId = sCache.keyAt(u);
+ // Not holding sUserIdLock is intentional here. We don't modify the elements within
+ // the array and so even if this method is called multiple times with different sets
+ // of user IDs, we want to adjust the cache based on each new array.
+ if (!ArrayUtils.contains(userIds, userId)) {
+ sCache.deleteAt(u);
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 645a1ac..80f3264 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -43,17 +43,20 @@
// Attribute strings for reading/writing properties to/from XML.
private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
private static final String ATTR_START_WITH_PARENT = "startWithParent";
+ private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
/** Index values of each property (to indicate whether they are present in this object). */
@IntDef(prefix = "INDEX_", value = {
INDEX_SHOW_IN_LAUNCHER,
INDEX_START_WITH_PARENT,
+ INDEX_SHOW_IN_SETTINGS,
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
}
private static final int INDEX_SHOW_IN_LAUNCHER = 0;
private static final int INDEX_START_WITH_PARENT = 1;
+ private static final int INDEX_SHOW_IN_SETTINGS = 2;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -87,6 +90,37 @@
public static final int SHOW_IN_LAUNCHER_NO = 2;
/**
+ * Possible values for whether or how to show this user in the Settings app.
+ * @hide
+ */
+ @IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+ SHOW_IN_SETTINGS_WITH_PARENT,
+ SHOW_IN_SETTINGS_SEPARATE,
+ SHOW_IN_SETTINGS_NO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShowInSettings {
+ }
+ /**
+ * Suggests that the Settings app should show this user's apps in the main tab.
+ * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+ * this user is a profile, then its apps should be shown alongside its parent's apps.
+ * @hide
+ */
+ public static final int SHOW_IN_SETTINGS_WITH_PARENT = 0;
+ /**
+ * Suggests that the Settings app should show this user's apps, but separately from the apps of
+ * this user's parent.
+ * @hide
+ */
+ public static final int SHOW_IN_SETTINGS_SEPARATE = 1;
+ /**
+ * Suggests that the Settings app should not show this user.
+ * @hide
+ */
+ public static final int SHOW_IN_SETTINGS_NO = 2;
+
+ /**
* Reference to the default user properties for this user's user type.
* <li>If non-null, then any absent property will use the default property from here instead.
* <li>If null, then any absent property indicates that the caller lacks permission to see it,
@@ -130,6 +164,7 @@
}
if (hasManagePermission) {
// Add items that require MANAGE_USERS or stronger.
+ setShowInSettings(orig.getShowInSettings());
}
if (hasQueryOrManagePermission) {
// Add items that require QUERY_USERS or stronger.
@@ -183,6 +218,33 @@
private @ShowInLauncher int mShowInLauncher;
/**
+ * Returns whether, and how, a user should be shown in the Settings app.
+ * This is generally inapplicable for non-profile users.
+ *
+ * Possible return values include
+ * {@link #SHOW_IN_SETTINGS_WITH_PARENT}},
+ * {@link #SHOW_IN_SETTINGS_SEPARATE},
+ * and {@link #SHOW_IN_SETTINGS_NO}.
+ *
+ * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
+ * property.
+ *
+ * @return whether, and how, a profile should be shown in the Settings.
+ * @hide
+ */
+ public @ShowInSettings int getShowInSettings() {
+ if (isPresent(INDEX_SHOW_IN_SETTINGS)) return mShowInSettings;
+ if (mDefaultProperties != null) return mDefaultProperties.mShowInSettings;
+ throw new SecurityException("You don't have permission to query mShowInSettings");
+ }
+ /** @hide */
+ public void setShowInSettings(@ShowInSettings int val) {
+ this.mShowInSettings = val;
+ setPresent(INDEX_SHOW_IN_SETTINGS);
+ }
+ private @ShowInSettings int mShowInSettings;
+
+ /**
* Returns whether a profile should be started when its parent starts (unless in quiet mode).
* This only applies for users that have parents (i.e. for profiles).
* @hide
@@ -206,6 +268,7 @@
+ "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
+ ", mShowInLauncher=" + getShowInLauncher()
+ ", mStartWithParent=" + getStartWithParent()
+ + ", mShowInSettings=" + getShowInSettings()
+ "}";
}
@@ -219,6 +282,7 @@
pw.println(prefix + " mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
pw.println(prefix + " mShowInLauncher=" + getShowInLauncher());
pw.println(prefix + " mStartWithParent=" + getStartWithParent());
+ pw.println(prefix + " mShowInSettings=" + getShowInSettings());
}
/**
@@ -258,6 +322,8 @@
case ATTR_START_WITH_PARENT:
setStartWithParent(parser.getAttributeBoolean(i));
break;
+ case ATTR_SHOW_IN_SETTINGS:
+ setShowInSettings(parser.getAttributeInt(i));
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -281,6 +347,9 @@
if (isPresent(INDEX_START_WITH_PARENT)) {
serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
}
+ if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
+ serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -289,6 +358,7 @@
dest.writeLong(mPropertiesPresent);
dest.writeInt(mShowInLauncher);
dest.writeBoolean(mStartWithParent);
+ dest.writeInt(mShowInSettings);
}
/**
@@ -301,6 +371,7 @@
mPropertiesPresent = source.readLong();
mShowInLauncher = source.readInt();
mStartWithParent = source.readBoolean();
+ mShowInSettings = source.readInt();
}
@Override
@@ -327,6 +398,7 @@
// UserProperties fields and their default values.
private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
private boolean mStartWithParent = false;
+ private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
@@ -338,21 +410,30 @@
return this;
}
+ /** Sets the value for {@link #mShowInSettings} */
+ public Builder setShowInSettings(@ShowInSettings int showInSettings) {
+ mShowInSettings = showInSettings;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated. */
public UserProperties build() {
return new UserProperties(
mShowInLauncher,
- mStartWithParent);
+ mStartWithParent,
+ mShowInSettings);
}
} // end Builder
/** Creates a UserProperties with the given properties. Intended for building default values. */
private UserProperties(
@ShowInLauncher int showInLauncher,
- boolean startWithParent) {
+ boolean startWithParent,
+ @ShowInSettings int showInSettings) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
setStartWithParent(startWithParent);
+ setShowInSettings(showInSettings);
}
}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 30ee118..c9a0626 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -62,10 +62,10 @@
* <p>The execution can potentially launch UI flows to collect user consent to using a
* credential, display a picker when multiple credentials exist, etc.
*
- * @param request the request specifying type(s) of credentials to get from the user.
- * @param cancellationSignal an optional signal that allows for cancelling this call.
- * @param executor the callback will take place on this {@link Executor}.
- * @param callback the callback invoked when the request succeeds or fails.
+ * @param request the request specifying type(s) of credentials to get from the user
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
*/
public void executeGetCredential(
@NonNull GetCredentialRequest request,
@@ -101,10 +101,10 @@
* <p>The execution can potentially launch UI flows to collect user consent to creating
* or storing the new credential, etc.
*
- * @param request the request specifying type(s) of credentials to get from the user.
- * @param cancellationSignal an optional signal that allows for cancelling this call.
- * @param executor the callback will take place on this {@link Executor}.
- * @param callback the callback invoked when the request succeeds or fails.
+ * @param request the request specifying type(s) of credentials to get from the user
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
*/
public void executeCreateCredential(
@NonNull CreateCredentialRequest request,
@@ -135,6 +135,44 @@
}
}
+ /**
+ * Clears the current user credential session from all credential providers.
+ *
+ * <p>Usually invoked after your user signs out of your app so that they will not be
+ * automatically signed in the next time.
+ *
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @hide
+ */
+ public void clearCredentialSession(
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "executeCreateCredential already canceled");
+ return;
+ }
+
+ ICancellationSignal cancelRemote = null;
+ try {
+ cancelRemote = mService.clearCredentialSession(
+ new ClearCredentialSessionTransport(executor, callback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (cancellationSignal != null && cancelRemote != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
@@ -184,4 +222,29 @@
() -> mCallback.onError(new CredentialManagerException(errorCode, message)));
}
}
+
+ private static class ClearCredentialSessionTransport
+ extends IClearCredentialSessionCallback.Stub {
+ // TODO: listen for cancellation to release callback.
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+
+ private ClearCredentialSessionTransport(Executor executor,
+ OutcomeReceiver<Void, CredentialManagerException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onSuccess() {
+ mCallback.onResult(null);
+ }
+
+ @Override
+ public void onError(int errorCode, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+ }
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
similarity index 66%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to core/java/android/credentials/IClearCredentialSessionCallback.aidl
index 1550ab3..903e7f5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package android.credentials;
-parcelable RemoteTransitionCompat;
+/**
+ * Listener for clearCredentialSession request.
+ *
+ * @hide
+ */
+interface IClearCredentialSessionCallback {
+ oneway void onSuccess();
+ oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index b0f27f9..35688d7 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,6 +18,7 @@
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.os.ICancellationSignal;
@@ -32,4 +33,6 @@
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
+
+ @nullable ICancellationSignal clearCredentialSession(in IClearCredentialSessionCallback callback, String callingPackage);
}
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 99b58c9..535b551 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -73,6 +73,13 @@
+ ".extra.all_sensors";
/**
+ * An extra containing the sensor type
+ * @hide
+ */
+ public static final String EXTRA_TOGGLE_TYPE = SensorPrivacyManager.class.getName()
+ + ".extra.toggle_type";
+
+ /**
* Sensor constants which are used in {@link SensorPrivacyManager}
*/
public static class Sensors {
diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index b9a327b..d9ee561 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -204,7 +204,7 @@
*
*/
@SuppressLint("MissingGetterMatchingBuilder")
- public @NonNull Builder addOrientationForState(long deviceState, long angle) {
+ public @NonNull Builder addOrientationForState(@DeviceState long deviceState, long angle) {
if (angle % 90 != 0) {
throw new IllegalArgumentException("Sensor orientation not divisible by 90: "
+ angle);
diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java
index 1d9a5a3a..32688a72 100644
--- a/core/java/android/hardware/camera2/params/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -17,6 +17,7 @@
package android.hardware.camera2.params;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
@@ -173,6 +174,7 @@
* @see #SCORE_MAX
* @see #SCORE_MIN
*/
+ @IntRange(from = SCORE_MIN, to = SCORE_MAX)
public int getScore() {
return mScore;
}
@@ -377,7 +379,7 @@
* @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
* @return This builder.
*/
- public @NonNull Builder setScore(int score) {
+ public @NonNull Builder setScore(@IntRange(from = SCORE_MIN, to = SCORE_MAX) int score) {
checkNotUsed();
checkScore(score);
mBuilderFieldsSet |= FIELD_SCORE;
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
deleted file mode 100644
index 9c2aa66..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.hardware.fingerprint;
-
-/**
- * A listener for the high-brightness mode (HBM) transitions. This allows other components to
- * perform certain actions when the HBM is toggled on or off. For example, a display manager
- * implementation can subscribe to these events from UdfpsController and adjust the display's
- * refresh rate when the HBM is enabled.
- *
- * @hide
- */
-oneway interface IUdfpsHbmListener {
- /**
- * UdfpsController will call this method when the HBM is enabled.
- *
- * @param displayId The displayId for which the HBM is enabled. See
- * {@link android.view.Display#getDisplayId()}.
- */
- void onHbmEnabled(int displayId);
-
- /**
- * UdfpsController will call this method when the HBM is disabled.
- *
- * @param displayId The displayId for which the HBM is disabled. See
- * {@link android.view.Display#getDisplayId()}.
- */
- void onHbmDisabled(int displayId);
-}
-
diff --git a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
new file mode 100644
index 0000000..8587348
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.fingerprint;
+
+/**
+ * A callback for UDFPS refresh rate. This allows other components to
+ * perform certain actions when the refresh rate is enabled or disabled.
+ * For example, a display manager implementation can subscribe to these
+ * events from UdfpsController when refresh rate is enabled or disabled.
+ *
+ * @hide
+ */
+oneway interface IUdfpsRefreshRateRequestCallback {
+ /**
+ * Sets the appropriate display refresh rate for UDFPS.
+ *
+ * @param displayId The displayId for which the refresh rate should be set. See
+ * {@link android.view.Display#getDisplayId()}.
+ */
+ void onRequestEnabled(int displayId);
+
+ /**
+ * Unsets the appropriate display refresh rate for UDFPS.
+ *
+ * @param displayId The displayId for which the refresh rate should be unset. See
+ * {@link android.view.Display#getDisplayId()}.
+ */
+ void onRequestDisabled(int displayId);
+}
+
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
index 20d6338..2754096 100644
--- a/core/java/android/hardware/location/ActivityRecognitionHardware.java
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -91,12 +91,16 @@
@android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
@Override
public String[] getSupportedActivities() {
+ super.getSupportedActivities_enforcePermission();
+
return mSupportedActivities;
}
@android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
@Override
public boolean isActivitySupported(String activity) {
+ super.isActivitySupported_enforcePermission();
+
int activityType = getActivityType(activity);
return activityType != INVALID_ACTIVITY_TYPE;
}
@@ -104,12 +108,16 @@
@android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
@Override
public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+ super.registerSink_enforcePermission();
+
return mSinks.register(sink);
}
@android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
@Override
public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+ super.unregisterSink_enforcePermission();
+
return mSinks.unregister(sink);
}
@@ -117,6 +125,8 @@
@Override
public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
+ super.enableActivityEvent_enforcePermission();
+
int activityType = getActivityType(activity);
if (activityType == INVALID_ACTIVITY_TYPE) {
return false;
@@ -134,6 +144,8 @@
@Override
public boolean disableActivityEvent(String activity, int eventType) {
+ super.disableActivityEvent_enforcePermission();
+
int activityType = getActivityType(activity);
if (activityType == INVALID_ACTIVITY_TYPE) {
return false;
@@ -150,6 +162,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
@Override
public boolean flush() {
+ super.flush_enforcePermission();
+
int result = nativeFlush();
return result == NATIVE_SUCCESS_RESULT;
}
diff --git a/core/java/android/hardware/location/GeofenceHardwareService.java b/core/java/android/hardware/location/GeofenceHardwareService.java
index 106bfd5..99c1e16 100644
--- a/core/java/android/hardware/location/GeofenceHardwareService.java
+++ b/core/java/android/hardware/location/GeofenceHardwareService.java
@@ -79,6 +79,8 @@
@Override
public int[] getMonitoringTypes() {
+ super.getMonitoringTypes_enforcePermission();
+
return mGeofenceHardwareImpl.getMonitoringTypes();
}
@@ -86,6 +88,8 @@
@Override
public int getStatusOfMonitoringType(int monitoringType) {
+ super.getStatusOfMonitoringType_enforcePermission();
+
return mGeofenceHardwareImpl.getStatusOfMonitoringType(monitoringType);
}
@@ -95,6 +99,8 @@
int monitoringType,
GeofenceHardwareRequestParcelable request,
IGeofenceHardwareCallback callback) {
+ super.addCircularFence_enforcePermission();
+
checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
return mGeofenceHardwareImpl.addCircularFence(monitoringType, request, callback);
}
@@ -103,6 +109,8 @@
@Override
public boolean removeGeofence(int id, int monitoringType) {
+ super.removeGeofence_enforcePermission();
+
checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
return mGeofenceHardwareImpl.removeGeofence(id, monitoringType);
}
@@ -111,6 +119,8 @@
@Override
public boolean pauseGeofence(int id, int monitoringType) {
+ super.pauseGeofence_enforcePermission();
+
checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
return mGeofenceHardwareImpl.pauseGeofence(id, monitoringType);
}
@@ -119,6 +129,8 @@
@Override
public boolean resumeGeofence(int id, int monitoringType, int monitorTransitions) {
+ super.resumeGeofence_enforcePermission();
+
checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
return mGeofenceHardwareImpl.resumeGeofence(id, monitoringType, monitorTransitions);
}
@@ -128,6 +140,8 @@
public boolean registerForMonitorStateChangeCallback(int monitoringType,
IGeofenceHardwareMonitorCallback callback) {
+ super.registerForMonitorStateChangeCallback_enforcePermission();
+
checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
return mGeofenceHardwareImpl.registerForMonitorStateChangeCallback(monitoringType,
callback);
@@ -138,6 +152,8 @@
public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
IGeofenceHardwareMonitorCallback callback) {
+ super.unregisterForMonitorStateChangeCallback_enforcePermission();
+
checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
return mGeofenceHardwareImpl.unregisterForMonitorStateChangeCallback(monitoringType,
callback);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 39d362b..cea46f3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1774,16 +1774,13 @@
}
/**
- * Implement to return our standard {@link InputMethodImpl}. Subclasses
- * can override to provide their own customized version.
+ * Implement to return our standard {@link InputMethodImpl}.
*
- * @deprecated IME developers don't need to override this method to get callbacks information.
- * Most methods in {@link InputMethodImpl} have corresponding callbacks.
- * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()},
- * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc.
- *
- * <p>Starting from Android U and later, override this method won't guarantee that IME works
- * as previous platform behavior.</p>
+ * @deprecated Overriding or calling this method is strongly discouraged. A future version of
+ * Android will remove the ability to use this method. Use the callbacks on
+ * {@link InputMethodService} as {@link InputMethodService#onBindInput()},
+ * {@link InputMethodService#onUnbindInput()}, {@link InputMethodService#onWindowShown()},
+ * {@link InputMethodService#onWindowHidden()}, etc.
*/
@Deprecated
@Override
@@ -1792,18 +1789,17 @@
}
/**
- * Implement to return our standard {@link InputMethodSessionImpl}. Subclasses
- * can override to provide their own customized version.
+ * Implement to return our standard {@link InputMethodSessionImpl}.
*
- * @deprecated IME developers don't need to override this method to get callbacks information.
+ * <p>IMEs targeting on Android U and above cannot override this method, or an
+ * {@link LinkageError} would be thrown.</p>
+ *
+ * @deprecated Overriding or calling this method is strongly discouraged.
* Most methods in {@link InputMethodSessionImpl} have corresponding callbacks.
* Use {@link InputMethodService#onFinishInput()},
* {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
* {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
* {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
- *
- * <p>IMEs targeting on Android U and above cannot override this method, or an
- * {@link LinkageError} would be thrown.</p>
*/
@Deprecated
@Override
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 933769a..8eaa5ad 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -69,6 +69,7 @@
boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
UserInfo getProfileParent(int userId);
boolean isSameProfileGroup(int userId, int otherUserHandle);
+ boolean isHeadlessSystemUserMode();
boolean isUserOfType(int userId, in String userType);
@UnsupportedAppUsage
UserInfo getUserInfo(int userId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d84037f..9d8df7e 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -70,3 +70,7 @@
# Tracing
per-file Trace.java = file:/TRACE_OWNERS
+
+# PermissionEnforcer
+per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
+per-file PermissionEnforcer.java = file:/core/java/android/permission/OWNERS
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f1879dd..3d20d63 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -91,7 +91,6 @@
public class UserManager {
private static final String TAG = "UserManager";
- private static final boolean VERBOSE = false;
@UnsupportedAppUsage
private final IUserManager mService;
@@ -104,6 +103,9 @@
/** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */
private String mProfileTypeOfProcessUser = null;
+ /** Whether the device is in headless system user mode; null until cached. */
+ private static Boolean sIsHeadlessSystemUser = null;
+
/**
* User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
* This type of user cannot be created; it can only pre-exist on first boot.
@@ -2068,28 +2070,20 @@
* @return whether the device is running in a headless system user mode.
*/
public static boolean isHeadlessSystemUserMode() {
- final boolean realMode = RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
- if (!Build.isDebuggable()) {
- return realMode;
+ // No need for synchronization. Once it becomes non-null, it'll be non-null forever.
+ // (Its value is determined when UMS is constructed and cannot change.)
+ // Worst case we might end up calling the AIDL method multiple times but that's fine.
+ if (sIsHeadlessSystemUser == null) {
+ // Unfortunately this API is static, but the property no longer is. So go fetch the UMS.
+ try {
+ final IUserManager service = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ sIsHeadlessSystemUser = service.isHeadlessSystemUserMode();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
}
-
- final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
- switch (emulatedMode) {
- case SYSTEM_USER_MODE_EMULATION_FULL:
- if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false");
- return false;
- case SYSTEM_USER_MODE_EMULATION_HEADLESS:
- if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true");
- return true;
- case SYSTEM_USER_MODE_EMULATION_DEFAULT:
- case "": // property not set
- return realMode;
- default:
- Log.wtf(TAG, "isHeadlessSystemUserMode(): invalid value of property "
- + SYSTEM_USER_MODE_EMULATION_PROPERTY + " (" + emulatedMode + "); using"
- + " default value (headless=" + realMode + ")");
- return realMode;
- }
+ return sIsHeadlessSystemUser;
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 897b7c3..fab6f7b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9876,6 +9876,13 @@
"fingerprint_side_fps_auth_downtime";
/**
+ * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * @hide
+ */
+ public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+ "sfps_require_screen_on_to_auth_enabled";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index 559b1ca..e330d1e 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -35,18 +35,22 @@
public final class CreateCredentialResponse implements Parcelable {
private final @Nullable CharSequence mHeader;
private final @NonNull List<SaveEntry> mSaveEntries;
+ private final @Nullable Action mRemoteSaveEntry;
+ //TODO : Add actions if needed
private CreateCredentialResponse(@NonNull Parcel in) {
mHeader = in.readCharSequence();
List<SaveEntry> saveEntries = new ArrayList<>();
in.readTypedList(saveEntries, SaveEntry.CREATOR);
mSaveEntries = saveEntries;
+ mRemoteSaveEntry = in.readTypedObject(Action.CREATOR);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeCharSequence(mHeader);
dest.writeTypedList(mSaveEntries);
+ dest.writeTypedObject(mRemoteSaveEntry, flags);
}
@Override
@@ -69,11 +73,13 @@
/* package-private */ CreateCredentialResponse(
@Nullable CharSequence header,
- @NonNull List<SaveEntry> saveEntries) {
+ @NonNull List<SaveEntry> saveEntries,
+ @Nullable Action remoteSaveEntry) {
this.mHeader = header;
this.mSaveEntries = saveEntries;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mSaveEntries);
+ this.mRemoteSaveEntry = remoteSaveEntry;
}
/** Returns the header to be displayed on the UI. */
@@ -86,6 +92,11 @@
return mSaveEntries;
}
+ /** Returns the remote save entry to be displayed on the UI. */
+ public @NonNull Action getRemoteSaveEntry() {
+ return mRemoteSaveEntry;
+ }
+
/**
* A builder for {@link CreateCredentialResponse}
*/
@@ -94,6 +105,7 @@
private @Nullable CharSequence mHeader;
private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
+ private @Nullable Action mRemoteSaveEntry;
/** Sets the header to be displayed on the UI. */
public @NonNull Builder setHeader(@Nullable CharSequence header) {
@@ -126,6 +138,14 @@
}
/**
+ * Sets a remote save entry to be shown on the UI.
+ */
+ public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) {
+ mRemoteSaveEntry = remoteSaveEntry;
+ return this;
+ }
+
+ /**
* Builds the instance.
*
* @throws IllegalArgumentException If {@code saveEntries} is empty.
@@ -135,7 +155,8 @@
+ "not be empty");
return new CreateCredentialResponse(
mHeader,
- mSaveEntries);
+ mSaveEntries,
+ mRemoteSaveEntry);
}
}
}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index 2cce169..ab5b524 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -43,12 +43,17 @@
/** List of provider actions to be displayed on the UI. */
private final @NonNull List<Action> mActions;
+ /** Remote credential entry to get the response from a different device. */
+ private final @Nullable Action mRemoteCredentialEntry;
+
private CredentialsDisplayContent(@Nullable CharSequence header,
@NonNull List<CredentialEntry> credentialEntries,
- @NonNull List<Action> actions) {
+ @NonNull List<Action> actions,
+ @Nullable Action remoteCredentialEntry) {
mHeader = header;
mCredentialEntries = credentialEntries;
mActions = actions;
+ mRemoteCredentialEntry = remoteCredentialEntry;
}
private CredentialsDisplayContent(@NonNull Parcel in) {
@@ -59,6 +64,7 @@
List<Action> actions = new ArrayList<>();
in.readTypedList(actions, Action.CREATOR);
mActions = actions;
+ mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR);
}
public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
@@ -84,6 +90,7 @@
dest.writeCharSequence(mHeader);
dest.writeTypedList(mCredentialEntries, flags);
dest.writeTypedList(mActions, flags);
+ dest.writeTypedObject(mRemoteCredentialEntry, flags);
}
/**
@@ -108,12 +115,20 @@
}
/**
+ * Returns the remote credential entry to be displayed on the UI.
+ */
+ public @Nullable Action getRemoteCredentialEntry() {
+ return mRemoteCredentialEntry;
+ }
+
+ /**
* Builds an instance of {@link CredentialsDisplayContent}.
*/
public static final class Builder {
- private CharSequence mHeader = null;
+ private CharSequence mHeader;
private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
private List<Action> mActions = new ArrayList<>();
+ private Action mRemoteCredentialEntry;
/**
* Sets the header to be displayed on the UI.
@@ -124,6 +139,14 @@
}
/**
+ * Sets the remote credential entry to be displayed on the UI.
+ */
+ public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
+ mRemoteCredentialEntry = remoteCredentialEntry;
+ return this;
+ }
+
+ /**
* Adds a {@link CredentialEntry} to the list of entries to be displayed on
* the UI.
*
@@ -185,7 +208,8 @@
throw new IllegalStateException("credentialEntries and actions must not both "
+ "be empty");
}
- return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
+ return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+ mRemoteCredentialEntry);
}
}
}
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 1148120..6f8c5db 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -224,7 +224,7 @@
byte[] resultBytes = messageDigest.digest();
if (separator == null) {
- return HexEncoding.encodeToString(resultBytes, true);
+ return HexEncoding.encodeToString(resultBytes, false);
}
int length = resultBytes.length;
diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java
index 524bb4c..719c614 100644
--- a/core/java/android/view/HandwritingDelegateConfiguration.java
+++ b/core/java/android/view/HandwritingDelegateConfiguration.java
@@ -47,7 +47,7 @@
* @param delegatorViewId identifier of the delegator editor view for which handwriting mode
* should be initiated
* @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within
- * this view's bounds
+ * this view's bounds. This will be called from the UI thread.
*/
public HandwritingDelegateConfiguration(
@IdRes int delegatorViewId, @NonNull Runnable initiationCallback) {
@@ -65,7 +65,7 @@
/**
* Returns the callback which should be called when a stylus {@link MotionEvent} occurs within
- * the delegate view's bounds.
+ * the delegate view's bounds. The callback should only be called from the UI thread.
*/
@NonNull
public Runnable getInitiationCallback() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 49d9e67..2b071db 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17434,7 +17434,7 @@
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
- protected void dispatchDraw(Canvas canvas) {
+ protected void dispatchDraw(@NonNull Canvas canvas) {
}
@@ -20719,7 +20719,7 @@
out.bottom = mScrollY + mBottom - mTop;
}
- private void onDrawScrollIndicators(Canvas c) {
+ private void onDrawScrollIndicators(@NonNull Canvas c) {
if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) {
// No scroll indicators enabled.
return;
@@ -20903,7 +20903,7 @@
*
* @see #awakenScrollBars(int)
*/
- protected final void onDrawScrollBars(Canvas canvas) {
+ protected final void onDrawScrollBars(@NonNull Canvas canvas) {
// scrollbars are drawn only when the animation is running
final ScrollabilityCache cache = mScrollCache;
@@ -21015,7 +21015,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+ protected void onDrawHorizontalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
int l, int t, int r, int b) {
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
@@ -21035,7 +21035,7 @@
* @hide
*/
@UnsupportedAppUsage
- protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+ protected void onDrawVerticalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
int l, int t, int r, int b) {
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
@@ -21046,7 +21046,7 @@
*
* @param canvas the canvas on which the background will be drawn
*/
- protected void onDraw(Canvas canvas) {
+ protected void onDraw(@NonNull Canvas canvas) {
}
/*
@@ -23161,7 +23161,7 @@
*
* @hide
*/
- protected final boolean drawsWithRenderNode(Canvas canvas) {
+ protected final boolean drawsWithRenderNode(@NonNull Canvas canvas) {
return mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& canvas.isHardwareAccelerated();
@@ -23173,7 +23173,7 @@
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
- boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+ boolean draw(@NonNull Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
@@ -23461,7 +23461,7 @@
return (int) (dips * scale + 0.5f);
}
- final private void debugDrawFocus(Canvas canvas) {
+ private void debugDrawFocus(@NonNull Canvas canvas) {
if (isFocused()) {
final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
final int l = mScrollX;
@@ -23496,7 +23496,7 @@
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
@@ -23731,7 +23731,7 @@
* @param canvas Canvas on which to draw the background
*/
@UnsupportedAppUsage
- private void drawBackground(Canvas canvas) {
+ private void drawBackground(@NonNull Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
@@ -24631,7 +24631,7 @@
* Draw the default focus highlight onto the canvas if there is one and this view is focused.
* @param canvas the canvas where we're drawing the highlight.
*/
- private void drawDefaultFocusHighlight(Canvas canvas) {
+ private void drawDefaultFocusHighlight(@NonNull Canvas canvas) {
if (mDefaultFocusHighlight != null && isFocused()) {
if (mDefaultFocusHighlightSizeChanged) {
mDefaultFocusHighlightSizeChanged = false;
@@ -25429,7 +25429,7 @@
*
* @param canvas canvas to draw into
*/
- public void onDrawForeground(Canvas canvas) {
+ public void onDrawForeground(@NonNull Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
@@ -27487,7 +27487,7 @@
*
* @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
*/
- public void onDrawShadow(Canvas canvas) {
+ public void onDrawShadow(@NonNull Canvas canvas) {
final View view = mView.get();
if (view != null) {
view.draw(canvas);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c4f20dc..46b2cfc 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4166,7 +4166,7 @@
/**
* @hide
*/
- protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
+ protected void onDebugDrawMargins(@NonNull Canvas canvas, Paint paint) {
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
c.getLayoutParams().onDebugDraw(c, canvas, paint);
@@ -4176,7 +4176,7 @@
/**
* @hide
*/
- protected void onDebugDraw(Canvas canvas) {
+ protected void onDebugDraw(@NonNull Canvas canvas) {
Paint paint = getDebugPaint();
// Draw optical bounds
@@ -4224,7 +4224,7 @@
}
@Override
- protected void dispatchDraw(Canvas canvas) {
+ protected void dispatchDraw(@NonNull Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
@@ -4533,7 +4533,7 @@
* @param drawingTime The time at which draw is occurring
* @return True if an invalidate() was issued
*/
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
@@ -9208,7 +9208,8 @@
}
}
- private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+ private static void drawRect(@NonNull Canvas canvas, Paint paint, int x1, int y1,
+ int x2, int y2) {
if (sDebugLines== null) {
// TODO: This won't work with multiple UI threads in a single process
sDebugLines = new float[16];
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index a8ed96e..2d974db 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -20,12 +20,14 @@
import android.annotation.Nullable;
import android.graphics.Matrix;
import android.graphics.RectF;
+import android.inputmethodservice.InputMethodService;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Layout;
import android.text.SpannedString;
import android.text.TextUtils;
import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
+import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
@@ -110,7 +112,7 @@
/**
* Container of rectangular position of Editor in the local coordinates that will be transformed
* with the transformation matrix when rendered on the screen.
- * @see {@link EditorBoundsInfo}.
+ * @see EditorBoundsInfo
*/
private final EditorBoundsInfo mEditorBoundsInfo;
@@ -122,6 +124,12 @@
private final float[] mMatrixValues;
/**
+ * Information about text appearance in the editor for use by {@link InputMethodService}.
+ */
+ @Nullable
+ private final TextAppearanceInfo mTextAppearanceInfo;
+
+ /**
* A list of visible line bounds stored in a float array. This array is divided into segment of
* four where each element in the segment represents left, top, right respectively and bottom
* of the line bounds.
@@ -157,10 +165,11 @@
mInsertionMarkerTop = source.readFloat();
mInsertionMarkerBaseline = source.readFloat();
mInsertionMarkerBottom = source.readFloat();
- mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class);
+ mCharacterBoundsArray = source.readTypedObject(SparseRectFArray.CREATOR);
mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR);
mMatrixValues = source.createFloatArray();
mVisibleLineBounds = source.createFloatArray();
+ mTextAppearanceInfo = source.readTypedObject(TextAppearanceInfo.CREATOR);
}
/**
@@ -181,10 +190,11 @@
dest.writeFloat(mInsertionMarkerTop);
dest.writeFloat(mInsertionMarkerBaseline);
dest.writeFloat(mInsertionMarkerBottom);
- dest.writeParcelable(mCharacterBoundsArray, flags);
+ dest.writeTypedObject(mCharacterBoundsArray, flags);
dest.writeTypedObject(mEditorBoundsInfo, flags);
dest.writeFloatArray(mMatrixValues);
dest.writeFloatArray(mVisibleLineBounds);
+ dest.writeTypedObject(mTextAppearanceInfo, flags);
}
@Override
@@ -262,6 +272,11 @@
return false;
}
}
+
+ if (!Objects.equals(mTextAppearanceInfo, that.mTextAppearanceInfo)) {
+ return false;
+ }
+
return true;
}
@@ -270,16 +285,17 @@
return "CursorAnchorInfo{mHashCode=" + mHashCode
+ " mSelection=" + mSelectionStart + "," + mSelectionEnd
+ " mComposingTextStart=" + mComposingTextStart
- + " mComposingText=" + Objects.toString(mComposingText)
+ + " mComposingText=" + mComposingText
+ " mInsertionMarkerFlags=" + mInsertionMarkerFlags
+ " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
+ " mInsertionMarkerTop=" + mInsertionMarkerTop
+ " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
+ " mInsertionMarkerBottom=" + mInsertionMarkerBottom
- + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray)
+ + " mCharacterBoundsArray=" + mCharacterBoundsArray
+ " mEditorBoundsInfo=" + mEditorBoundsInfo
+ " mVisibleLineBounds=" + getVisibleLineBounds()
+ " mMatrix=" + Arrays.toString(mMatrixValues)
+ + " mTextAppearanceInfo=" + mTextAppearanceInfo
+ "}";
}
@@ -303,6 +319,7 @@
private boolean mMatrixInitialized = false;
private float[] mVisibleLineBounds = new float[LINE_BOUNDS_INITIAL_SIZE * 4];
private int mVisibleLineBoundsCount = 0;
+ private TextAppearanceInfo mTextAppearanceInfo = null;
/**
* Sets the text range of the selection. Calling this can be skipped if there is no
@@ -416,6 +433,17 @@
}
/**
+ * Set the information related to text appearance, which is extracted from the original
+ * {@link TextView}.
+ * @param textAppearanceInfo {@link TextAppearanceInfo} of TextView.
+ */
+ @NonNull
+ public Builder setTextAppearanceInfo(@Nullable TextAppearanceInfo textAppearanceInfo) {
+ mTextAppearanceInfo = textAppearanceInfo;
+ return this;
+ }
+
+ /**
* Add the bounds of a visible text line of the current editor.
*
* The line bounds should not include the vertical space between lines or the horizontal
@@ -504,6 +532,7 @@
}
mEditorBoundsInfo = null;
clearVisibleLineBounds();
+ mTextAppearanceInfo = null;
}
}
@@ -524,7 +553,8 @@
builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop,
builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom,
characterBoundsArray, builder.mEditorBoundsInfo, matrixValues,
- Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount));
+ Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount),
+ builder.mTextAppearanceInfo);
}
private CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart,
@@ -533,7 +563,8 @@
float insertionMarkerBaseline, float insertionMarkerBottom,
@Nullable SparseRectFArray characterBoundsArray,
@Nullable EditorBoundsInfo editorBoundsInfo,
- @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds) {
+ @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds,
+ @Nullable TextAppearanceInfo textAppearanceInfo) {
mSelectionStart = selectionStart;
mSelectionEnd = selectionEnd;
mComposingTextStart = composingTextStart;
@@ -547,6 +578,7 @@
mEditorBoundsInfo = editorBoundsInfo;
mMatrixValues = matrixValues;
mVisibleLineBounds = visibleLineBounds;
+ mTextAppearanceInfo = textAppearanceInfo;
// To keep hash function simple, we only use some complex objects for hash.
int hashCode = Objects.hashCode(mComposingText);
@@ -573,7 +605,7 @@
original.mInsertionMarkerTop, original.mInsertionMarkerBaseline,
original.mInsertionMarkerBottom, original.mCharacterBoundsArray,
original.mEditorBoundsInfo, computeMatrixValues(parentMatrix, original),
- original.mVisibleLineBounds);
+ original.mVisibleLineBounds, original.mTextAppearanceInfo);
}
/**
@@ -741,6 +773,16 @@
}
/**
+ * Returns {@link TextAppearanceInfo} for the current editor, or {@code null} if IME is not
+ * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}
+ * or {@link InputConnection#CURSOR_UPDATE_MONITOR}.
+ */
+ @Nullable
+ public TextAppearanceInfo getTextAppearanceInfo() {
+ return mTextAppearanceInfo;
+ }
+
+ /**
* Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
* matrix that is to be applied other positional data in this class.
* @return a new instance (copy) of the transformation matrix.
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 1afa987..a66c67b 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -107,8 +107,8 @@
* @param where where the information is coming from.
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
- @RequiresNoPermission
@AnyThread
+ @RequiresNoPermission
static void startProtoDump(byte[] protoDump, int source, String where,
@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
@@ -127,8 +127,8 @@
*
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
@AnyThread
+ @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -146,8 +146,8 @@
*
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
@AnyThread
+ @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -165,8 +165,8 @@
*
* @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
*/
- @RequiresNoPermission
@AnyThread
+ @RequiresNoPermission
static boolean isImeTraceEnabled() {
final IInputMethodManager service = getService();
if (service == null) {
@@ -182,8 +182,8 @@
/**
* Invokes {@link IInputMethodManager#removeImeSurface()}
*/
- @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@AnyThread
+ @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -212,6 +212,7 @@
@AnyThread
@NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness) {
final IInputMethodManager service = getService();
@@ -227,6 +228,7 @@
@AnyThread
@NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -241,6 +243,7 @@
@AnyThread
@NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
final IInputMethodManager service = getService();
@@ -257,6 +260,7 @@
@AnyThread
@Nullable
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -302,6 +306,7 @@
@AnyThread
@NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
@StartInputFlags int startInputFlags,
@@ -340,14 +345,14 @@
}
@AnyThread
- static void showInputMethodPickerFromSystem(@NonNull IInputMethodClient client,
- int auxiliarySubtypeMode, int displayId) {
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
final IInputMethodManager service = getService();
if (service == null) {
return;
}
try {
- service.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
+ service.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -369,6 +374,7 @@
@AnyThread
@Nullable
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -382,6 +388,7 @@
}
@AnyThread
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static void setAdditionalInputMethodSubtypes(@NonNull String imeId,
@NonNull InputMethodSubtype[] subtypes, @UserIdInt int userId) {
final IInputMethodManager service = getService();
@@ -396,6 +403,7 @@
}
@AnyThread
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imeId,
@NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
final IInputMethodManager service = getService();
@@ -476,6 +484,7 @@
}
@AnyThread
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -503,6 +512,7 @@
}
@AnyThread
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
static void setStylusWindowIdleTimeoutForTest(
IInputMethodClient client, @DurationMillisLong long timeout) {
final IInputMethodManager service = getService();
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index d6d7339..2099a31 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1057,8 +1057,10 @@
* used together with {@link #CURSOR_UPDATE_MONITOR}.
* <p>
* Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
- * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS} and
- * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} are included but specifying them can
+ * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and
+ * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can
* filter-out others.
* It can be CPU intensive to include all, filtering specific info is recommended.
* </p>
@@ -1076,7 +1078,8 @@
* <p>
* Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
* {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
- * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and
+ * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and
* {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can
* filter-out others.
* It can be CPU intensive to include all, filtering specific info is recommended.
@@ -1092,6 +1095,7 @@
* <p>
* This flag can be used together with filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
* {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE},
* {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags
* {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
* </p>
@@ -1107,8 +1111,8 @@
* <p>
* This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
* {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
- * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags
- * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}
+ * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
* </p>
*/
int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 1 << 3;
@@ -1123,8 +1127,8 @@
* <p>
* This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
* {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
- * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} and update flags {@link #CURSOR_UPDATE_IMMEDIATE}
- * and {@link #CURSOR_UPDATE_MONITOR}.
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}
+ * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
* </p>
*/
int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 1 << 4;
@@ -1138,14 +1142,29 @@
* {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
* <p>
* This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
- * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}
- * and update flags {@link #CURSOR_UPDATE_IMMEDIATE}
- * and {@link #CURSOR_UPDATE_MONITOR}.
+ * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+ * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE} and update flags
+ * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
* </p>
*/
int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 1 << 5;
/**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+ * with new text appearance info {@link CursorAnchorInfo#getTextAppearanceInfo()}}
+ * whenever cursor/anchor position is changed. To disable monitoring, call
+ * {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
+ * <p>
+ * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
+ * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+ * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and update flags
+ * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
+ * </p>
+ */
+ int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 1 << 6;
+
+ /**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -1158,7 +1177,8 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS,
- CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+ CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS,
+ CURSOR_UPDATE_FILTER_TEXT_APPEARANCE},
flag = true, prefix = { "CURSOR_UPDATE_FILTER_" })
@interface CursorUpdateFilter{}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9106ce2..d08816b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -16,8 +16,6 @@
package android.view.inputmethod;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -128,6 +126,7 @@
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
@@ -1525,12 +1524,18 @@
/**
* Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled for
* the given userId.
- * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
- * called and Stylus touch should continue as normal touch input.
+ *
+ * <p>If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
+ * called and Stylus touch should continue as normal touch input.</p>
+ *
+ * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+ * {@code userId} is different from the user id of the current process.</p>
+ *
* @see #startStylusHandwriting(View)
* @param userId user ID to query.
* @hide
*/
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
final Context fallbackContext = ActivityThread.currentApplication();
if (fallbackContext == null) {
@@ -1549,13 +1554,16 @@
/**
* Returns the list of installed input methods for the specified user.
*
+ * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+ * {@code userId} is different from the user id of the current process.</p>
+ *
* @param userId user ID to query
* @return {@link List} of {@link InputMethodInfo}.
* @hide
*/
@TestApi
- @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
@NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return IInputMethodManagerGlobalInvoker.getInputMethodList(userId,
DirectBootAwareness.AUTO);
@@ -1564,14 +1572,17 @@
/**
* Returns the list of installed input methods for the specified user.
*
+ * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+ * {@code userId} is different from the user id of the current process.</p>
+ *
* @param userId user ID to query
* @param directBootAwareness {@code true} if caller want to query installed input methods list
* on user locked state.
* @return {@link List} of {@link InputMethodInfo}.
* @hide
*/
- @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
@NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness) {
return IInputMethodManagerGlobalInvoker.getInputMethodList(userId, directBootAwareness);
@@ -1595,11 +1606,14 @@
/**
* Returns the list of enabled input methods for the specified user.
*
+ * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+ * {@code userId} is different from the user id of the current process.</p>
+ *
* @param userId user ID to query
* @return {@link List} of {@link InputMethodInfo}.
* @hide
*/
- @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(userId);
}
@@ -2352,7 +2366,11 @@
* Starts an input connection from the served view that gains the window focus.
* Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
* background thread may blocked by other methods which already inside {@code mH} lock.
+ *
+ * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+ * {@code userId} is different from the user id of the current process.</p>
*/
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
private boolean startInputInner(@StartInputReason int startInputReason,
@Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
@SoftInputModeFlags int softInputMode, int windowFlags) {
@@ -2609,8 +2627,8 @@
* @param timeout to set in milliseconds. To reset to default, use a value <= zero.
* @hide
*/
- @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
@TestApi
+ @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
synchronized (mH) {
IInputMethodManagerGlobalInvoker.setStylusWindowIdleTimeoutForTest(mClient, timeout);
@@ -3089,7 +3107,7 @@
*
* <p>On Android {@link Build.VERSION_CODES#Q} and later devices, the undocumented behavior that
* token can be {@code null} when the caller has
- * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
+ * {@link Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
* {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
* {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
*
@@ -3121,7 +3139,7 @@
if (fallbackContext == null) {
return;
}
- if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+ if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
@@ -3158,7 +3176,7 @@
* from an application or a service which has a token of the currently active input method.
*
* <p>On Android {@link Build.VERSION_CODES#Q} and later devices, {@code token} cannot be
- * {@code null} even with {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
+ * {@code null} even with {@link Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
* update {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
* {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
*
@@ -3440,12 +3458,12 @@
* @param displayId The ID of the display where the chooser dialog should be shown.
* @hide
*/
- @RequiresPermission(WRITE_SECURE_SETTINGS)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public void showInputMethodPickerFromSystem(boolean showAuxiliarySubtypes, int displayId) {
final int mode = showAuxiliarySubtypes
? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
: SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
- IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
+ IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mode, displayId);
}
@GuardedBy("mH")
@@ -3519,11 +3537,11 @@
* {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
* does not require any permission as long as the caller is the current IME.
* If the calling process is some privileged app that already has
- * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+ * {@link Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
* directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
*/
@Deprecated
- @RequiresPermission(WRITE_SECURE_SETTINGS)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
if (Process.myUid() == Process.SYSTEM_UID) {
Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
@@ -3540,7 +3558,7 @@
if (fallbackContext == null) {
return false;
}
- if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+ if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
@@ -3622,6 +3640,32 @@
}
/**
+ * {@code true} means that
+ * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+ * {@code false} when the IME client and the IME run in different displays.
+ */
+ final AtomicBoolean mRequestCursorUpdateDisplayIdCheck = new AtomicBoolean(true);
+
+ /**
+ * Controls the display ID mismatch validation in
+ * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)}.
+ *
+ * <p>{@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} is not guaranteed to work
+ * correctly when the IME client and the IME run in different displays. This is why
+ * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+ * {@code false} by default when the display ID does not match. This method allows special apps
+ * to override this behavior when they are sure that it should work.</p>
+ *
+ * <p>By default the validation is enabled.</p>
+ *
+ * @param enabled {@code false} to disable the display ID validation.
+ * @hide
+ */
+ public void setRequestCursorUpdateDisplayIdCheck(boolean enabled) {
+ mRequestCursorUpdateDisplayIdCheck.set(enabled);
+ }
+
+ /**
* An internal API for {@link android.hardware.display.VirtualDisplay} to report where its
* embedded virtual display is placed.
*
diff --git a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
index 63d9167..5df9fd1 100644
--- a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
+++ b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.Manifest;
import android.annotation.AnyThread;
import android.annotation.Nullable;
import android.annotation.RequiresNoPermission;
@@ -51,8 +52,8 @@
* @param where where the information is coming from.
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
- @RequiresNoPermission
@AnyThread
+ @RequiresNoPermission
public static void startProtoDump(byte[] protoDump, int source, String where,
@Nullable Consumer<RemoteException> exceptionHandler) {
IInputMethodManagerGlobalInvoker.startProtoDump(protoDump, source, where, exceptionHandler);
@@ -63,8 +64,8 @@
*
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
@AnyThread
+ @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
IInputMethodManagerGlobalInvoker.startImeTrace(exceptionHandler);
}
@@ -74,8 +75,8 @@
*
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
- @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
@AnyThread
+ @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
IInputMethodManagerGlobalInvoker.stopImeTrace(exceptionHandler);
}
@@ -85,8 +86,8 @@
*
* @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
*/
- @RequiresNoPermission
@AnyThread
+ @RequiresNoPermission
public static boolean isImeTraceEnabled() {
return IInputMethodManagerGlobalInvoker.isImeTraceEnabled();
}
@@ -96,8 +97,8 @@
*
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
- @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@AnyThread
+ @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
public static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
IInputMethodManagerGlobalInvoker.removeImeSurface(exceptionHandler);
}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index e6b22de..c01c310 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1050,7 +1050,8 @@
Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
return false;
}
- if (mParentInputMethodManager.getDisplayId() != imeDisplayId
+ if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
+ && mParentInputMethodManager.getDisplayId() != imeDisplayId
&& !mParentInputMethodManager.hasVirtualDisplayToScreenMatrix()) {
// requestCursorUpdates() is not currently supported across displays.
return false;
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
new file mode 100644
index 0000000..1df4fc5
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.ColorInt;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+import android.content.res.ColorStateList;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
+import android.inputmethodservice.InputMethodService;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputFilter;
+import android.widget.TextView;
+
+import java.util.Objects;
+
+/**
+ * Information about text appearance in an editor, passed through
+ * {@link CursorAnchorInfo} for use by {@link InputMethodService}.
+ *
+ * @see TextView
+ * @see Paint
+ * @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo)
+ * @see CursorAnchorInfo#getTextAppearanceInfo()
+ */
+public final class TextAppearanceInfo implements Parcelable {
+ /**
+ * The text size (in pixels) for current {@link TextView}.
+ */
+ private final @Px float mTextSize;
+
+ /**
+ * The LocaleList of the text.
+ */
+ @NonNull private final LocaleList mTextLocales;
+
+ /**
+ * The font family name if the {@link Typeface} of the text is created from a system font
+ * family, otherwise this value should be null.
+ */
+ @Nullable private final String mSystemFontFamilyName;
+
+ /**
+ * The weight of the text.
+ */
+ private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight;
+
+ /**
+ * The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}.
+ */
+ private final @Typeface.Style int mTextStyle;
+
+ /**
+ * Whether the transformation method applied to the current {@link TextView} is set to
+ * ALL CAPS.
+ */
+ private final boolean mAllCaps;
+
+ /**
+ * The horizontal offset (in pixels) of the text shadow.
+ */
+ private final @Px float mShadowDx;
+
+ /**
+ * The vertical offset (in pixels) of the text shadow.
+ */
+ private final @Px float mShadowDy;
+
+ /**
+ * The blur radius (in pixels) of the text shadow.
+ */
+ private final @Px float mShadowRadius;
+
+ /**
+ * The elegant text height, especially for less compacted complex script text.
+ */
+ private final boolean mElegantTextHeight;
+
+ /**
+ * Whether to expand linespacing based on fallback fonts.
+ */
+ private final boolean mFallbackLineSpacing;
+
+ /**
+ * The text letter-spacing (in ems), which determines the spacing between characters.
+ */
+ private final float mLetterSpacing;
+
+ /**
+ * The font feature settings.
+ */
+ @Nullable private final String mFontFeatureSettings;
+
+ /**
+ * The font variation settings.
+ */
+ @Nullable private final String mFontVariationSettings;
+
+ /**
+ * The line-break strategies for text wrapping.
+ */
+ private final @LineBreakConfig.LineBreakStyle int mLineBreakStyle;
+
+ /**
+ * The line-break word strategies for text wrapping.
+ */
+ private final @LineBreakConfig.LineBreakWordStyle int mLineBreakWordStyle;
+
+ /**
+ * The extent by which text should be stretched horizontally. Returns 1.0 if not specified.
+ */
+ private final float mTextScaleX;
+
+ /**
+ * The color of the text selection highlight.
+ */
+ private final @ColorInt int mTextColorHighlight;
+
+ /**
+ * The current text color.
+ */
+ private final @ColorInt int mTextColor;
+
+ /**
+ * The current color of the hint text.
+ */
+ private final @ColorInt int mTextColorHint;
+
+ /**
+ * The text color for links.
+ */
+ @Nullable private final ColorStateList mTextColorLink;
+
+ /**
+ * The max length of text.
+ */
+ private final int mMaxLength;
+
+
+ public TextAppearanceInfo(@NonNull TextView textView) {
+ mTextSize = textView.getTextSize();
+ mTextLocales = textView.getTextLocales();
+ Typeface typeface = textView.getPaint().getTypeface();
+ String systemFontFamilyName = null;
+ int textFontWeight = -1;
+ if (typeface != null) {
+ systemFontFamilyName = typeface.getSystemFontFamilyName();
+ textFontWeight = typeface.getWeight();
+ }
+ mSystemFontFamilyName = systemFontFamilyName;
+ mTextFontWeight = textFontWeight;
+ mTextStyle = textView.getTypefaceStyle();
+ mAllCaps = textView.isAllCaps();
+ mShadowRadius = textView.getShadowRadius();
+ mShadowDx = textView.getShadowDx();
+ mShadowDy = textView.getShadowDy();
+ mElegantTextHeight = textView.isElegantTextHeight();
+ mFallbackLineSpacing = textView.isFallbackLineSpacing();
+ mLetterSpacing = textView.getLetterSpacing();
+ mFontFeatureSettings = textView.getFontFeatureSettings();
+ mFontVariationSettings = textView.getFontVariationSettings();
+ mLineBreakStyle = textView.getLineBreakStyle();
+ mLineBreakWordStyle = textView.getLineBreakWordStyle();
+ mTextScaleX = textView.getTextScaleX();
+ mTextColorHighlight = textView.getHighlightColor();
+ mTextColor = textView.getCurrentTextColor();
+ mTextColorHint = textView.getCurrentHintTextColor();
+ mTextColorLink = textView.getLinkTextColors();
+ int maxLength = -1;
+ for (InputFilter filter: textView.getFilters()) {
+ if (filter instanceof InputFilter.LengthFilter) {
+ maxLength = ((InputFilter.LengthFilter) filter).getMax();
+ // There is at most one LengthFilter.
+ break;
+ }
+ }
+ mMaxLength = maxLength;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeFloat(mTextSize);
+ mTextLocales.writeToParcel(dest, flags); // NonNull
+ dest.writeBoolean(mAllCaps);
+ dest.writeString8(mSystemFontFamilyName);
+ dest.writeInt(mTextFontWeight);
+ dest.writeInt(mTextStyle);
+ dest.writeFloat(mShadowDx);
+ dest.writeFloat(mShadowDy);
+ dest.writeFloat(mShadowRadius);
+ dest.writeBoolean(mElegantTextHeight);
+ dest.writeBoolean(mFallbackLineSpacing);
+ dest.writeFloat(mLetterSpacing);
+ dest.writeString8(mFontFeatureSettings);
+ dest.writeString8(mFontVariationSettings);
+ dest.writeInt(mLineBreakStyle);
+ dest.writeInt(mLineBreakWordStyle);
+ dest.writeFloat(mTextScaleX);
+ dest.writeInt(mTextColorHighlight);
+ dest.writeInt(mTextColor);
+ dest.writeInt(mTextColorHint);
+ dest.writeTypedObject(mTextColorLink, flags);
+ dest.writeInt(mMaxLength);
+ }
+
+ private TextAppearanceInfo(@NonNull Parcel in) {
+ mTextSize = in.readFloat();
+ mTextLocales = LocaleList.CREATOR.createFromParcel(in);
+ mAllCaps = in.readBoolean();
+ mSystemFontFamilyName = in.readString8();
+ mTextFontWeight = in.readInt();
+ mTextStyle = in.readInt();
+ mShadowDx = in.readFloat();
+ mShadowDy = in.readFloat();
+ mShadowRadius = in.readFloat();
+ mElegantTextHeight = in.readBoolean();
+ mFallbackLineSpacing = in.readBoolean();
+ mLetterSpacing = in.readFloat();
+ mFontFeatureSettings = in.readString8();
+ mFontVariationSettings = in.readString8();
+ mLineBreakStyle = in.readInt();
+ mLineBreakWordStyle = in.readInt();
+ mTextScaleX = in.readFloat();
+ mTextColorHighlight = in.readInt();
+ mTextColor = in.readInt();
+ mTextColorHint = in.readInt();
+ mTextColorLink = in.readTypedObject(ColorStateList.CREATOR);
+ mMaxLength = in.readInt();
+ }
+
+ @NonNull
+ public static final Creator<TextAppearanceInfo> CREATOR = new Creator<TextAppearanceInfo>() {
+ @Override
+ public TextAppearanceInfo createFromParcel(@NonNull Parcel in) {
+ return new TextAppearanceInfo(in);
+ }
+
+ @Override
+ public TextAppearanceInfo[] newArray(int size) {
+ return new TextAppearanceInfo[size];
+ }
+ };
+
+ /**
+ * Returns the text size (in pixels) for current {@link TextView}.
+ */
+ public @Px float getTextSize() {
+ return mTextSize;
+ }
+
+ /**
+ * Returns the LocaleList of the text.
+ */
+ @NonNull
+ public LocaleList getTextLocales() {
+ return mTextLocales;
+ }
+
+ /**
+ * Returns the font family name if the {@link Typeface} of the text is created from a
+ * system font family. Returns null if no {@link Typeface} is specified, or it is not created
+ * from a system font family.
+ */
+ @Nullable
+ public String getFontFamilyName() {
+ return mSystemFontFamilyName;
+ }
+
+ /**
+ * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified.
+ */
+ public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() {
+ return mTextFontWeight;
+ }
+
+ /**
+ * Returns the style (normal, bold, italic, bold|italic) of the text. Returns
+ * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for
+ * more information.
+ */
+ public @Typeface.Style int getTextStyle() {
+ return mTextStyle;
+ }
+
+ /**
+ * Returns whether the transformation method applied to the current {@link TextView} is set to
+ * ALL CAPS.
+ */
+ public boolean isAllCaps() {
+ return mAllCaps;
+ }
+
+ /**
+ * Returns the horizontal offset (in pixels) of the text shadow.
+ */
+ public @Px float getShadowDx() {
+ return mShadowDx;
+ }
+
+ /**
+ * Returns the vertical offset (in pixels) of the text shadow.
+ */
+ public @Px float getShadowDy() {
+ return mShadowDy;
+ }
+
+ /**
+ * Returns the blur radius (in pixels) of the text shadow.
+ */
+ public @Px float getShadowRadius() {
+ return mShadowRadius;
+ }
+
+ /**
+ * Returns {@code true} if the elegant height metrics flag is set. This setting selects font
+ * variants that have not been compacted to fit Latin-based vertical metrics, and also increases
+ * top and bottom bounds to provide more space.
+ */
+ public boolean isElegantTextHeight() {
+ return mElegantTextHeight;
+ }
+
+ /**
+ * Returns whether to expand linespacing based on fallback fonts.
+ *
+ * @see TextView#setFallbackLineSpacing(boolean)
+ */
+ public boolean isFallbackLineSpacing() {
+ return mFallbackLineSpacing;
+ }
+
+ /**
+ * Returns the text letter-spacing, which determines the spacing between characters.
+ * The value is in 'EM' units. Normally, this value is 0.0.
+ */
+ public float getLetterSpacing() {
+ return mLetterSpacing;
+ }
+
+ /**
+ * Returns the font feature settings. Returns null if not specified.
+ *
+ * @see Paint#getFontFeatureSettings()
+ */
+ @Nullable
+ public String getFontFeatureSettings() {
+ return mFontFeatureSettings;
+ }
+
+ /**
+ * Returns the font variation settings. Returns null if no variation is specified.
+ *
+ * @see Paint#getFontVariationSettings()
+ */
+ @Nullable
+ public String getFontVariationSettings() {
+ return mFontVariationSettings;
+ }
+
+ /**
+ * Returns the line-break strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakStyle(int)
+ */
+ public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
+ return mLineBreakStyle;
+ }
+
+ /**
+ * Returns the line-break word strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakWordStyle(int)
+ */
+ public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
+ return mLineBreakWordStyle;
+ }
+
+ /**
+ * Returns the extent by which text should be stretched horizontally. Returns 1.0 if not
+ * specified.
+ */
+ public float getTextScaleX() {
+ return mTextScaleX;
+ }
+
+ /**
+ * Returns the color of the text selection highlight.
+ */
+ public @ColorInt int getTextColorHighlight() {
+ return mTextColorHighlight;
+ }
+
+ /**
+ * Returns the current text color.
+ */
+ public @ColorInt int getTextColor() {
+ return mTextColor;
+ }
+
+ /**
+ * Returns the current color of the hint text.
+ */
+ public @ColorInt int getTextColorHint() {
+ return mTextColorHint;
+ }
+
+ /**
+ * Returns the text color for links.
+ */
+ @Nullable
+ public ColorStateList getTextColorLink() {
+ return mTextColorLink;
+ }
+
+ /**
+ * Returns the max length of text, which is used to set an input filter to constrain the text
+ * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter}
+ * in the Editor.
+ */
+ public int getMaxLength() {
+ return mMaxLength;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TextAppearanceInfo)) return false;
+ TextAppearanceInfo that = (TextAppearanceInfo) o;
+ return Float.compare(that.mTextSize, mTextSize) == 0
+ && mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle
+ && mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0
+ && Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare(
+ that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength
+ && mElegantTextHeight == that.mElegantTextHeight
+ && mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare(
+ that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle
+ && mLineBreakWordStyle == that.mLineBreakWordStyle
+ && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor
+ && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor()
+ && mTextColorHint == that.mTextColorHint && Objects.equals(
+ mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName,
+ that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings,
+ that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings,
+ that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight,
+ mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight,
+ mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings,
+ mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor,
+ mTextColorHint, mTextColorLink, mMaxLength);
+ }
+
+ @Override
+ public String toString() {
+ return "TextAppearanceInfo{"
+ + "mTextSize=" + mTextSize
+ + ", mTextLocales=" + mTextLocales
+ + ", mSystemFontFamilyName='" + mSystemFontFamilyName + '\''
+ + ", mTextFontWeight=" + mTextFontWeight
+ + ", mTextStyle=" + mTextStyle
+ + ", mAllCaps=" + mAllCaps
+ + ", mShadowDx=" + mShadowDx
+ + ", mShadowDy=" + mShadowDy
+ + ", mShadowRadius=" + mShadowRadius
+ + ", mElegantTextHeight=" + mElegantTextHeight
+ + ", mFallbackLineSpacing=" + mFallbackLineSpacing
+ + ", mLetterSpacing=" + mLetterSpacing
+ + ", mFontFeatureSettings='" + mFontFeatureSettings + '\''
+ + ", mFontVariationSettings='" + mFontVariationSettings + '\''
+ + ", mLineBreakStyle=" + mLineBreakStyle
+ + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+ + ", mTextScaleX=" + mTextScaleX
+ + ", mTextColorHighlight=" + mTextColorHighlight
+ + ", mTextColor=" + mTextColor
+ + ", mTextColorHint=" + mTextColorHint
+ + ", mTextColorLink=" + mTextColorLink
+ + ", mMaxLength=" + mMaxLength
+ + '}';
+ }
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8f590f8..56524a2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -125,6 +125,7 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.TextAppearanceInfo;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationManager;
import android.widget.AdapterView.OnItemClickListener;
@@ -4630,14 +4631,17 @@
(filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
boolean includeVisibleLineBounds =
(filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
+ boolean includeTextAppearance =
+ (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
boolean includeAll =
(!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
- && !includeVisibleLineBounds);
+ && !includeVisibleLineBounds && !includeTextAppearance);
includeEditorBounds |= includeAll;
includeCharacterBounds |= includeAll;
includeInsertionMarker |= includeAll;
includeVisibleLineBounds |= includeAll;
+ includeTextAppearance |= includeAll;
final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder;
builder.reset();
@@ -4757,6 +4761,10 @@
}
}
+ if (includeTextAppearance) {
+ TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView);
+ builder.setTextAppearanceInfo(textAppearanceInfo);
+ }
imm.updateCursorAnchorInfo(mTextView, builder.build());
// Drop the immediate flag if any.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5e74381..510a92d 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -116,9 +116,7 @@
* <p class="note">ListView attempts to reuse view objects in order to improve performance and
* avoid a lag in response to user scrolls. To take advantage of this feature, check if the
* {@code convertView} provided to {@code getView(...)} is null before creating or inflating a new
- * view object. See
- * <a href="{@docRoot}training/improving-layouts/smooth-scrolling.html">
- * Making ListView Scrolling Smooth</a> for more ways to ensure a smooth user experience.</p>
+ * view object.</p>
*
* <p>To specify an action when a user clicks or taps on a single list item, see
* <a href="{@docRoot}guide/topics/ui/declaring-layout.html#HandlingUserSelections">
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 36eaf49..57e0ce8 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -105,4 +105,7 @@
/** @return An interface enabling the transition players to report its metrics. */
ITransitionMetricsReporter getTransitionMetricsReporter();
+
+ /** @return The transaction queue token used by WM. */
+ IBinder getApplyToken();
}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 2a80d02..930aaa2 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -26,6 +26,7 @@
import android.os.RemoteException;
import android.util.Singleton;
import android.view.RemoteAnimationAdapter;
+import android.view.SurfaceControl;
/**
* Base class for organizing specific types of windows like Tasks and DisplayAreas
@@ -184,6 +185,26 @@
}
}
+ /**
+ * Use WM's transaction-queue instead of Shell's independent one. This is necessary
+ * if WM and Shell need to coordinate transactions (eg. for shell transitions).
+ * @return true if successful, false otherwise.
+ * @hide
+ */
+ public boolean shareTransactionQueue() {
+ final IBinder wmApplyToken;
+ try {
+ wmApplyToken = getWindowOrganizerController().getApplyToken();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (wmApplyToken == null) {
+ return false;
+ }
+ SurfaceControl.Transaction.setDefaultApplyToken(wmApplyToken);
+ return true;
+ }
+
static IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get();
}
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index b155dd4..330ff8e 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -237,7 +237,9 @@
| InputConnection.CURSOR_UPDATE_MONITOR;
final int knownFilterFlags = InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
| InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
- | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS;
+ | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS
+ | InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS
+ | InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE;
// It is possible that any other bit is used as a valid flag in a future release.
// We should reject the entire request in such a case.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
similarity index 80%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
rename to core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
index 1550ab3..18bd6e5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
@@ -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,6 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package com.android.internal.inputmethod;
-parcelable RemoteTransitionCompat;
+parcelable InputMethodSubtypeHandle;
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644
index 0000000..780c637
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
@@ -0,0 +1,254 @@
+/*
+ * 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.internal.inputmethod;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.security.InvalidParameterException;
+import java.util.Objects;
+
+/**
+ * A stable and serializable identifier for the pair of {@link InputMethodInfo#getId()} and
+ * {@link android.view.inputmethod.InputMethodSubtype}.
+ *
+ * <p>To save {@link InputMethodSubtypeHandle} to storage, call {@link #toStringHandle()} to get a
+ * {@link String} handle and just save it. Once you load a {@link String} handle, you can obtain a
+ * {@link InputMethodSubtypeHandle} instance from {@link #of(String)}.</p>
+ *
+ * <p>For better readability, consider specifying {@link RawHandle} annotation to {@link String}
+ * object when it is a raw {@link String} handle.</p>
+ */
+public final class InputMethodSubtypeHandle implements Parcelable {
+ private static final String SUBTYPE_TAG = "subtype";
+ private static final char DATA_SEPARATOR = ':';
+
+ /**
+ * Can be used to annotate {@link String} object if it is raw handle format.
+ */
+ @Retention(SOURCE)
+ @Target({ElementType.METHOD, ElementType.FIELD, ElementType.LOCAL_VARIABLE,
+ ElementType.PARAMETER})
+ public @interface RawHandle {
+ }
+
+ /**
+ * The main content of this {@link InputMethodSubtypeHandle}. Is designed to be safe to be
+ * saved into storage.
+ */
+ @RawHandle
+ private final String mHandle;
+
+ /**
+ * Encode {@link InputMethodInfo} and {@link InputMethodSubtype#hashCode()} into
+ * {@link RawHandle}.
+ *
+ * @param imeId {@link InputMethodInfo#getId()} to be used.
+ * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be used.
+ * @return The encoded {@link RawHandle} string.
+ */
+ @AnyThread
+ @RawHandle
+ @NonNull
+ private static String encodeHandle(@NonNull String imeId, int subtypeHashCode) {
+ return imeId + DATA_SEPARATOR + SUBTYPE_TAG + DATA_SEPARATOR + subtypeHashCode;
+ }
+
+ private InputMethodSubtypeHandle(@NonNull String handle) {
+ mHandle = handle;
+ }
+
+ /**
+ * Creates {@link InputMethodSubtypeHandle} from {@link InputMethodInfo} and
+ * {@link InputMethodSubtype}.
+ *
+ * @param imi {@link InputMethodInfo} to be used.
+ * @param subtype {@link InputMethodSubtype} to be used.
+ * @return A {@link InputMethodSubtypeHandle} object.
+ */
+ @AnyThread
+ @NonNull
+ public static InputMethodSubtypeHandle of(
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+ final int subtypeHashCode =
+ subtype != null ? subtype.hashCode() : InputMethodSubtype.SUBTYPE_ID_NONE;
+ return new InputMethodSubtypeHandle(encodeHandle(imi.getId(), subtypeHashCode));
+ }
+
+ /**
+ * Creates {@link InputMethodSubtypeHandle} from a {@link RawHandle} {@link String}, which can
+ * be obtained by {@link #toStringHandle()}.
+ *
+ * @param stringHandle {@link RawHandle} {@link String} to be parsed.
+ * @return A {@link InputMethodSubtypeHandle} object.
+ * @throws NullPointerException when {@code stringHandle} is {@code null}
+ * @throws InvalidParameterException when {@code stringHandle} is not a valid {@link RawHandle}.
+ */
+ @AnyThread
+ @NonNull
+ public static InputMethodSubtypeHandle of(@RawHandle @NonNull String stringHandle) {
+ final SimpleStringSplitter splitter = new SimpleStringSplitter(DATA_SEPARATOR);
+ splitter.setString(Objects.requireNonNull(stringHandle));
+ if (!splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final String imeId = splitter.next();
+ final ComponentName componentName = ComponentName.unflattenFromString(imeId);
+ if (componentName == null) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ // TODO: Consolidate IME ID validation logic into one place.
+ if (!Objects.equals(componentName.flattenToShortString(), imeId)) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ if (!splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final String source = splitter.next();
+ if (!Objects.equals(source, SUBTYPE_TAG)) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ if (!splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final String hashCodeStr = splitter.next();
+ if (splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final int subtypeHashCode;
+ try {
+ subtypeHashCode = Integer.parseInt(hashCodeStr);
+ } catch (NumberFormatException ignore) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+
+ // Redundant expressions (e.g. "0001" instead of "1") are not allowed.
+ if (!Objects.equals(encodeHandle(imeId, subtypeHashCode), stringHandle)) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+
+ return new InputMethodSubtypeHandle(stringHandle);
+ }
+
+ /**
+ * @return {@link ComponentName} of the input method.
+ * @see InputMethodInfo#getComponent()
+ */
+ @AnyThread
+ @NonNull
+ public ComponentName getComponentName() {
+ return ComponentName.unflattenFromString(getImeId());
+ }
+
+ /**
+ * @return IME ID.
+ * @see InputMethodInfo#getId()
+ */
+ @AnyThread
+ @NonNull
+ public String getImeId() {
+ return mHandle.substring(0, mHandle.indexOf(DATA_SEPARATOR));
+ }
+
+ /**
+ * @return {@link RawHandle} {@link String} data that should be stable and persistable.
+ * @see #of(String)
+ */
+ @RawHandle
+ @AnyThread
+ @NonNull
+ public String toStringHandle() {
+ return mHandle;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof InputMethodSubtypeHandle)) {
+ return false;
+ }
+ final InputMethodSubtypeHandle that = (InputMethodSubtypeHandle) obj;
+ return Objects.equals(mHandle, that.mHandle);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mHandle);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @NonNull
+ @Override
+ public String toString() {
+ return "InputMethodSubtypeHandle{mHandle=" + mHandle + "}";
+ }
+
+ /**
+ * {@link Creator} for parcelable.
+ */
+ public static final Creator<InputMethodSubtypeHandle> CREATOR = new Creator<>() {
+ @Override
+ public InputMethodSubtypeHandle createFromParcel(Parcel in) {
+ return of(in.readString8());
+ }
+
+ @Override
+ public InputMethodSubtypeHandle[] newArray(int size) {
+ return new InputMethodSubtypeHandle[size];
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(toStringHandle());
+ }
+}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 98d81c9..6870d09 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -51,6 +51,15 @@
// ------ ro.fw.* ------------ //
public static final boolean FW_SYSTEM_USER_SPLIT =
SystemProperties.getBoolean("ro.fw.system_user_split", false);
+ /**
+ * Indicates whether the device should run in headless system user mode,
+ * in which user 0 only runs the system, not a real user.
+ * <p>WARNING about changing this value during an non-wiping update (OTA):
+ * <li>If this value is modified via an update, the change will have no effect, since an
+ * already-existing system user cannot change its mode.
+ * <li>Changing this value during an OTA from a pre-R device is not permitted; attempting to
+ * do so will corrupt the system user.
+ */
public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER =
SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1d4b246..edbdc86 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -23,13 +23,12 @@
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.service.notification.StatusBarNotification;
-import android.view.InsetsVisibilities;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -175,9 +174,9 @@
void setBiometicContextListener(in IBiometricContextListener listener);
/**
- * Sets an instance of IUdfpsHbmListener for UdfpsController.
+ * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
*/
- void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+ void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
/**
* Notifies System UI that the display is ready to show system decorations.
@@ -201,13 +200,13 @@
* stacks.
* @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
* @param behavior the behavior of the focused window.
- * @param requestedVisibilities the collection of the requested visibilities of system insets.
+ * @param requestedVisibleTypes the collection of insets types requested visible.
* @param packageName the package name of the focused app.
* @param letterboxDetails a set of letterbox details of apps visible on the screen.
*/
void onSystemBarAttributesChanged(int displayId, int appearance,
in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- int behavior, in InsetsVisibilities requestedVisibilities, String packageName,
+ int behavior, int requestedVisibleTypes, String packageName,
in LetterboxDetails[] letterboxDetails);
/**
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ef8f2db..d190681 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -23,7 +23,7 @@
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.net.Uri;
@@ -136,9 +136,9 @@
void setBiometicContextListener(in IBiometricContextListener listener);
/**
- * Sets an instance of IUdfpsHbmListener for UdfpsController.
+ * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
*/
- void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+ void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
/**
* Show a warning that the device is about to go to sleep due to user inactivity.
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8b898f0..54221ce 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,7 +21,6 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
import com.android.internal.view.AppearanceRegion;
@@ -40,7 +39,7 @@
public final IBinder mImeToken;
public final boolean mNavbarColorManagedByIme;
public final int mBehavior;
- public final InsetsVisibilities mRequestedVisibilities;
+ public final int mRequestedVisibleTypes;
public final String mPackageName;
public final int[] mTransientBarTypes;
public final LetterboxDetails[] mLetterboxDetails;
@@ -48,7 +47,7 @@
public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
- boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
+ boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
String packageName, @NonNull int[] transientBarTypes,
LetterboxDetails[] letterboxDetails) {
mIcons = new ArrayMap<>(icons);
@@ -62,7 +61,7 @@
mImeToken = imeToken;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
- mRequestedVisibilities = requestedVisibilities;
+ mRequestedVisibleTypes = requestedVisibleTypes;
mPackageName = packageName;
mTransientBarTypes = transientBarTypes;
mLetterboxDetails = letterboxDetails;
@@ -86,7 +85,7 @@
dest.writeStrongBinder(mImeToken);
dest.writeBoolean(mNavbarColorManagedByIme);
dest.writeInt(mBehavior);
- dest.writeTypedObject(mRequestedVisibilities, 0);
+ dest.writeInt(mRequestedVisibleTypes);
dest.writeString(mPackageName);
dest.writeIntArray(mTransientBarTypes);
dest.writeParcelableArray(mLetterboxDetails, flags);
@@ -112,8 +111,7 @@
final IBinder imeToken = source.readStrongBinder();
final boolean navbarColorManagedByIme = source.readBoolean();
final int behavior = source.readInt();
- final InsetsVisibilities requestedVisibilities =
- source.readTypedObject(InsetsVisibilities.CREATOR);
+ final int requestedVisibleTypes = source.readInt();
final String packageName = source.readString();
final int[] transientBarTypes = source.createIntArray();
final LetterboxDetails[] letterboxDetails =
@@ -121,7 +119,7 @@
return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
- requestedVisibilities, packageName, transientBarTypes,
+ requestedVisibleTypes, packageName, transientBarTypes,
letterboxDetails);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index f7bb16e..40c6a05 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -81,8 +81,7 @@
@EnforcePermission("WRITE_SECURE_SETTINGS")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
- void showInputMethodPickerFromSystem(in IInputMethodClient client,
- int auxiliarySubtypeMode, int displayId);
+ void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
@EnforcePermission("TEST_INPUT_METHOD")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 2cd9f89..6762e45 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -22,6 +22,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <linux/fs.h>
#include <nativehelper/ScopedUtfChars.h>
#include <stdlib.h>
#include <string.h>
@@ -250,6 +251,16 @@
return INSTALL_FAILED_CONTAINER_ERROR;
}
+ // If a filesystem like f2fs supports per-file compression, set the compression bit before data
+ // writes
+ unsigned int flags;
+ if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
+ ALOGE("Failed to call FS_IOC_GETFLAGS on %s: %s\n", localTmpFileName, strerror(errno));
+ } else if ((flags & FS_COMPR_FL) == 0) {
+ flags |= FS_COMPR_FL;
+ ioctl(fd, FS_IOC_SETFLAGS, &flags);
+ }
+
if (!zipFile->uncompressEntry(zipEntry, fd)) {
ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName);
close(fd);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 16e0a59..f0fbff1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7211,6 +7211,10 @@
</intent-filter>
</service>
+ <service android:name="com.android.server.art.BackgroundDexOptJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<provider
android:name="com.android.server.textclassifier.IconsContentProvider"
android:authorities="com.android.textclassifier.icons"
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 88bf18c..eaadc20 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -54,6 +54,12 @@
com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
</string>
+ <!-- Component name of the activity used to inform a user about a sensor privacy update from
+ SW/HW privacy switches. -->
+ <string name="config_sensorStateChangedActivity" translatable="false">
+ com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity
+ </string>
+
<!-- Component name of the activity that shows the request for access to a usb device. -->
<string name="config_usbPermissionActivity" translatable="false">
com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 23dd1b4..7d20335 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2456,8 +2456,10 @@
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
<bool name="config_dismissDreamOnActivityStart">false</bool>
- <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
- <string name="config_loggable_dream_prefix" translatable="false"></string>
+ <!-- The prefixes of dream component names that are loggable.
+ Matched against ComponentName#flattenToString() for dream components.
+ If empty, logs "other" for all. -->
+ <string-array name="config_loggable_dream_prefixes"></string-array>
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
@@ -2942,6 +2944,9 @@
<string name="config_usbResolverActivity" translatable="false"
>com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
+ <!-- Component name of the activity used to inform a user about a sensor privacy state chage. -->
+ <string name="config_sensorStateChangedActivity" translatable="false"></string>
+
<!-- Component name of the activity used to inform a user about a sensory being blocked because
of privacy settings. -->
<string name="config_sensorUseStartedActivity" translatable="false"
@@ -4425,13 +4430,13 @@
<string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
<!-- Corner radius of system dialogs -->
- <dimen name="config_dialogCornerRadius">2dp</dimen>
+ <dimen name="config_dialogCornerRadius">28dp</dimen>
<!-- Corner radius of system buttons -->
- <dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen>
+ <dimen name="config_buttonCornerRadius">4dp</dimen>
<!-- Corner radius for bottom sheet system dialogs -->
- <dimen name="config_bottomDialogCornerRadius">@dimen/config_dialogCornerRadius</dimen>
+ <dimen name="config_bottomDialogCornerRadius">16dp</dimen>
<!-- Corner radius of system progress bars -->
- <dimen name="config_progressBarCornerRadius">@dimen/progress_bar_corner_material</dimen>
+ <dimen name="config_progressBarCornerRadius">1000dp</dimen>
<!-- Controls whether system buttons use all caps for text -->
<bool name="config_buttonTextAllCaps">true</bool>
<!-- Name of the font family used for system surfaces where the font should use medium weight -->
@@ -4945,6 +4950,11 @@
<!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
<bool name="config_faceAuthDismissesKeyguard">true</bool>
+ <!-- Default value for whether a SFPS device is required to be
+ {@link KeyguardUpdateMonitor#isDeviceInteractive()} for fingerprint auth
+ to unlock the device. -->
+ <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 1997261..9dbb6a0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -979,9 +979,9 @@
<dimen name="controls_thumbnail_image_max_width">280dp</dimen>
<!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. -->
- <dimen name="system_app_widget_background_radius">16dp</dimen>
+ <dimen name="system_app_widget_background_radius">28dp</dimen>
<!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. -->
- <dimen name="system_app_widget_inner_radius">8dp</dimen>
+ <dimen name="system_app_widget_inner_radius">20dp</dimen>
<!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. @removed -->
<dimen name="__removed_system_app_widget_internal_padding">16dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476d36d..2a17199 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -374,6 +374,7 @@
<java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity_hwToggle" />
+ <java-symbol type="string" name="config_sensorStateChangedActivity" />
<java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
@@ -2246,7 +2247,7 @@
<java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
<java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
<java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
- <java-symbol type="string" name="config_loggable_dream_prefix" />
+ <java-symbol type="array" name="config_loggable_dream_prefixes" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
<java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2725,6 +2726,7 @@
<java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
<java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
<java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+ <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
<!-- Face config -->
<java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
diff --git a/core/tests/coretests/src/android/app/activity/OWNERS b/core/tests/coretests/src/android/app/activity/OWNERS
index 0862c05..7e24aef 100644
--- a/core/tests/coretests/src/android/app/activity/OWNERS
+++ b/core/tests/coretests/src/android/app/activity/OWNERS
@@ -1 +1,2 @@
include /services/core/java/com/android/server/wm/OWNERS
+include /services/core/java/com/android/server/am/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
new file mode 100644
index 0000000..f111bf6f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.internal.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidParameterException;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InputMethodSubtypeHandleTest {
+
+ @Test
+ public void testCreateFromRawHandle() {
+ {
+ final InputMethodSubtypeHandle handle =
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+ assertNotNull(handle);
+ assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+ assertEquals("com.android.test/.Ime1", handle.getImeId());
+ assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+ handle.getComponentName());
+ }
+
+ assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(""));
+
+ // The IME ID must use ComponentName#flattenToShortString(), not #flattenToString().
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/com.android.test.Ime1:subtype:1"));
+
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:0001"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:1!"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:1:"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:1:2"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:a"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:0x01"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:Subtype:a"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "ime1:subtype:1"));
+ }
+
+ @Test
+ public void testCreateFromInputMethodInfo() {
+ final InputMethodInfo imi = new InputMethodInfo(
+ "com.android.test", "com.android.test.Ime1", "TestIME", null);
+ {
+ final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, null);
+ assertNotNull(handle);
+ assertEquals("com.android.test/.Ime1:subtype:0", handle.toStringHandle());
+ assertEquals("com.android.test/.Ime1", handle.getImeId());
+ assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+ handle.getComponentName());
+ }
+
+ final InputMethodSubtype subtype =
+ new InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(1).build();
+ {
+ final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, subtype);
+ assertNotNull(handle);
+ assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+ assertEquals("com.android.test/.Ime1", handle.getImeId());
+ assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+ handle.getComponentName());
+ }
+
+ assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, null));
+ assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, subtype));
+ }
+
+ @Test
+ public void testEquality() {
+ assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"));
+ assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode(),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode());
+
+ assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:2"));
+ assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime2:subtype:1"));
+ }
+
+ @Test
+ public void testParcelablility() {
+ final InputMethodSubtypeHandle original =
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+ final InputMethodSubtypeHandle cloned = cloneHandle(original);
+ assertEquals(original, cloned);
+ assertEquals(original.hashCode(), cloned.hashCode());
+ assertEquals(original.getComponentName(), cloned.getComponentName());
+ assertEquals(original.getImeId(), cloned.getImeId());
+ assertEquals(original.toStringHandle(), cloned.toStringHandle());
+ }
+
+ @Test
+ public void testNoUnnecessaryStringInstantiationInToStringHandle() {
+ final String validHandleStr = "com.android.test/.Ime1:subtype:1";
+ // Verify that toStringHandle() returns the same String object if the input is valid for
+ // an efficient memory usage.
+ assertSame(validHandleStr, InputMethodSubtypeHandle.of(validHandleStr).toStringHandle());
+ }
+
+ @NonNull
+ private static InputMethodSubtypeHandle cloneHandle(
+ @NonNull InputMethodSubtypeHandle original) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return InputMethodSubtypeHandle.CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index c53fb23..048c48b 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -25,7 +25,7 @@
import android.os.Parcel;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -65,7 +65,7 @@
new Binder() /* imeToken */,
true /* navbarColorManagedByIme */,
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- new InsetsVisibilities() /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
"test" /* packageName */,
new int[0] /* transientBarTypes */,
new LetterboxDetails[] {letterboxDetails});
@@ -87,7 +87,7 @@
assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
- assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
+ assertThat(copy.mRequestedVisibleTypes).isEqualTo(original.mRequestedVisibleTypes);
assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
assertThat(copy.mLetterboxDetails).isEqualTo(original.mLetterboxDetails);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e4..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@
@Override
public void onActivityPreCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
@@ -1934,6 +1949,11 @@
@Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1969,7 +1989,11 @@
if (who instanceof Activity) {
// We will check if the new activity should be split with the activity that launched
// it.
- launchingActivity = (Activity) who;
+ final Activity activity = (Activity) who;
+ // For Activity that is child of another Activity (ActivityGroup), treat the parent
+ // Activity as the launching one because it's window will just be a child of the
+ // parent Activity window.
+ launchingActivity = activity.isChild() ? activity.getParent() : activity;
if (isInPictureInPicture(launchingActivity)) {
// We don't embed activity when it is in PIP.
return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 7960323..362f1fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
- if (curSecondaryContainer != null
+ if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
&& (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
// Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
// the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
new file mode 100644
index 0000000..8779cc0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
new file mode 100644
index 0000000..ea0fbb0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
new file mode 100644
index 0000000..c55cbe2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
new file mode 100644
index 0000000..447df43
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
new file mode 100644
index 0000000..c334a54
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:translateX="6.0"
+ android:translateY="8.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
new file mode 100644
index 0000000..e307f00
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="210.0dp"
+ android:height="64.0dp"
+ android:tint="@color/decor_button_light_color"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
new file mode 100644
index 0000000..d9a140b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:id="@+id/handle_menu"
+android:layout_width="wrap_content"
+android:layout_height="wrap_content"
+android:gravity="center_horizontal"
+android:background="@drawable/decor_caption_title">
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/fullscreen_button"
+ android:contentDescription="@string/fullscreen_text"
+ android:background="@drawable/caption_fullscreen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/split_screen_button"
+ android:contentDescription="@string/split_screen_text"
+ android:background="@drawable/caption_split_screen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/floating_button"
+ android:contentDescription="@string/float_button_text"
+ android:background="@drawable/caption_floating_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/desktop_button"
+ android:contentDescription="@string/desktop_text"
+ android:background="@drawable/caption_desktop_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/more_button"
+ android:contentDescription="@string/more_button_text"
+ android:background="@drawable/caption_more_button"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index 38cd570..51e634c 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -19,14 +19,10 @@
android:id="@+id/caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title">
<Button
+ style="@style/CaptionButtonStyle"
android:id="@+id/back_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
android:contentDescription="@string/back_button_text"
android:background="@drawable/decor_back_button_dark"
/>
@@ -39,11 +35,8 @@
android:contentDescription="@string/handle_text"
android:background="@drawable/decor_handle_dark"/>
<Button
+ style="@style/CaptionButtonStyle"
android:id="@+id/close_window"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4807f08..097a567 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -202,4 +202,14 @@
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
<string name="handle_text">Handle</string>
+ <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
+ <string name="fullscreen_text">Fullscreen</string>
+ <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
+ <string name="desktop_text">Desktop Mode</string>
+ <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
+ <string name="split_screen_text">Split Screen</string>
+ <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
+ <string name="more_button_text">More</string>
+ <!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
+ <string name="float_button_text">Float</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 19f7c3e..a859721 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,6 +30,13 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
+ <style name="CaptionButtonStyle">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_margin">5dp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/split_divider_bar_width</item>
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 dc5b9a1e..64220c8 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
@@ -38,6 +38,7 @@
import android.provider.Settings.Global;
import android.util.Log;
import android.util.SparseArray;
+import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowFocusObserver;
import android.view.InputDevice;
@@ -187,6 +188,31 @@
}
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
+
+ initBackAnimationRunners();
+ }
+
+ private void initBackAnimationRunners() {
+ final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
+ final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ // Animation missing. Simply finish animation.
+ finishedCallback.onAnimationFinished();
+ }
+ };
+
+ final BackAnimationRunner dummyBackRunner =
+ new BackAnimationRunner(dummyCallback, dummyRunner);
+ final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
+ new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
+ // TODO (238474994): register cross activity animation when it's completed.
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
+ // TODO (236760237): register dialog close animation when it's completed.
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
}
private void setupAnimationDeveloperSettingsObserver(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
new file mode 100644
index 0000000..2074b6a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the animation of swiping back and returning to another task.
+ *
+ * This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows.
+ * Once the gesture is committed, the second part remains the closing window in place.
+ * The entering window plays the rest of app opening transition to enter full screen.
+ *
+ * This animation is used only for apps that enable back dispatching via
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * navigation to launcher starts.
+ */
+@ShellMainThread
+class CrossTaskBackAnimation {
+ private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+
+ /**
+ * Minimum scale of the entering window.
+ */
+ private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f;
+
+ /**
+ * Minimum scale of the closing window.
+ */
+ private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f;
+
+ /**
+ * Minimum color scale of the closing window.
+ */
+ private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f;
+
+ /**
+ * The margin between the entering window and the closing window
+ */
+ private static final int WINDOW_MARGIN = 35;
+
+ /** Max window translation in the Y axis. */
+ private static final int WINDOW_MAX_DELTA_Y = 160;
+
+ private final Rect mStartTaskRect = new Rect();
+ private final float mCornerRadius;
+
+ // The closing window properties.
+ private final RectF mClosingCurrentRect = new RectF();
+
+ // The entering window properties.
+ private final Rect mEnteringStartRect = new Rect();
+ private final RectF mEnteringCurrentRect = new RectF();
+
+ private final PointF mInitialTouchPos = new PointF();
+ private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+ private final Matrix mTransformMatrix = new Matrix();
+
+ private final float[] mTmpFloat9 = new float[9];
+ private final float[] mTmpTranslate = {0, 0, 0};
+
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private SurfaceControl mBackgroundSurface;
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private boolean mBackInProgress = false;
+
+ private boolean mIsRightEdge;
+ private float mProgress = 0;
+ private PointF mTouchPos = new PointF();
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+
+ private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+
+ final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
+ @Override
+ public void onBackStarted(BackEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossTaskBackAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.reset();
+ finishAnimation();
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ };
+
+ final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+ };
+
+ CrossTaskBackAnimation(Context context) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ }
+
+ private float getInterpolatedProgress(float backProgress) {
+ return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+
+ // Offset start rectangle to align task bounds.
+ mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+ mStartTaskRect.offsetTo(0, 0);
+
+ // Draw background.
+ mBackgroundSurface = new SurfaceControl.Builder()
+ .setName("Background of Back Navigation")
+ .setColorLayer()
+ .setHidden(false)
+ .build();
+ mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
+ .setLayer(mBackgroundSurface, -1);
+ mTransaction.apply();
+ }
+
+ private void updateGestureBackProgress(float progress, BackEvent event) {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ return;
+ }
+
+ float touchX = event.getTouchX();
+ float touchY = event.getTouchY();
+ float dX = Math.abs(touchX - mInitialTouchPos.x);
+
+ // The 'follow width' is the width of the window if it completely matches
+ // the gesture displacement.
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ // The 'progress width' is the width of the window if it strictly linearly interpolates
+ // to minimum scale base on progress.
+ float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE);
+ float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE);
+ float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE);
+
+ // The final width is derived from interpolating between the follow with and progress width
+ // using gesture progress.
+ float enteringWidth = enteringScale * width;
+ float closingWidth = closingScale * width;
+ float enteringHeight = (float) height / width * enteringWidth;
+ float closingHeight = (float) height / width * closingWidth;
+
+ float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ // Base the window movement in the Y axis on the touch movement in the Y axis.
+ float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+ // Move the window along the Y axis.
+ float closingTop = (height - closingHeight) * 0.5f + deltaY;
+ float enteringTop = (height - enteringHeight) * 0.5f + deltaY;
+ // Move the window along the X axis.
+ float right = width - (progress * WINDOW_MARGIN);
+ float left = right - closingWidth;
+
+ mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight);
+ mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop,
+ left - WINDOW_MARGIN, enteringTop + enteringHeight);
+
+ applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
+ applyColorTransform(mClosingTarget.leash, closingColorScale);
+ applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+ mTransaction.apply();
+ }
+
+ private void updatePostCommitClosingAnimation(float progress) {
+ mTransaction.setLayer(mClosingTarget.leash, 0);
+ float alpha = mapRange(progress, 1, 0);
+ mTransaction.setAlpha(mClosingTarget.leash, alpha);
+ }
+
+ private void updatePostCommitEnteringAnimation(float progress) {
+ float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+ float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+ float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+ float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+
+ mEnteringCurrentRect.set(left, top, left + width, top + height);
+ applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+ }
+
+ /** Transform the target window to match the target rect. */
+ private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
+ if (leash == null) {
+ return;
+ }
+
+ final float scale = targetRect.width() / mStartTaskRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+ mTransaction.setMatrix(leash, mTransformMatrix, mTmpFloat9)
+ .setWindowCrop(leash, mStartTaskRect)
+ .setCornerRadius(leash, cornerRadius);
+ }
+
+ private void applyColorTransform(SurfaceControl leash, float colorScale) {
+ if (leash == null) {
+ return;
+ }
+ computeScaleTransformMatrix(colorScale, mTmpFloat9);
+ mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
+ }
+
+ static void computeScaleTransformMatrix(float scale, float[] matrix) {
+ matrix[0] = scale;
+ matrix[1] = 0;
+ matrix[2] = 0;
+ matrix[3] = 0;
+ matrix[4] = scale;
+ matrix[5] = 0;
+ matrix[6] = 0;
+ matrix[7] = 0;
+ matrix[8] = scale;
+ }
+
+ private void finishAnimation() {
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+
+ if (mBackgroundSurface != null) {
+ mBackgroundSurface.release();
+ mBackgroundSurface = null;
+ }
+
+ mBackInProgress = false;
+ mTransformMatrix.reset();
+ mClosingCurrentRect.setEmpty();
+ mInitialTouchPos.set(0, 0);
+
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ }
+
+ private void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (!mBackInProgress) {
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+ mBackInProgress = true;
+ }
+ mProgress = backEvent.getProgress();
+ mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent);
+ }
+
+ private void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ finishAnimation();
+ return;
+ }
+
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase.
+ mEnteringCurrentRect.round(mEnteringStartRect);
+
+ ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300);
+ valueAnimator.setInterpolator(mInterpolator);
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ updatePostCommitEnteringAnimation(progress);
+ updatePostCommitClosingAnimation(progress);
+ mTransaction.apply();
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
new file mode 100644
index 0000000..7237d2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-modules splitscreen owner
+chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5b7d141..295a2e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -86,9 +86,9 @@
private static final int FLING_ENTER_DURATION = 350;
private static final int FLING_EXIT_DURATION = 350;
- private final int mDividerWindowWidth;
- private final int mDividerInsets;
- private final int mDividerSize;
+ private int mDividerWindowWidth;
+ private int mDividerInsets;
+ private int mDividerSize;
private final Rect mTempRect = new Rect();
private final Rect mRootBounds = new Rect();
@@ -131,6 +131,7 @@
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
+ mDensity = configuration.densityDpi;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayImeController = displayImeController;
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
@@ -139,24 +140,22 @@
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
- final Resources resources = context.getResources();
- mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
- mDividerInsets = getDividerInsets(resources, context.getDisplay());
- mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
+ updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
resetDividerPosition();
- mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+ mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
updateInvisibleRect();
}
- private int getDividerInsets(Resources resources, Display display) {
+ private void updateDividerConfig(Context context) {
+ final Resources resources = context.getResources();
+ final Display display = context.getDisplay();
final int dividerInset = resources.getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_insets);
-
int radius = 0;
RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
@@ -167,7 +166,9 @@
corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
- return Math.max(dividerInset, radius);
+ mDividerInsets = Math.max(dividerInset, radius);
+ mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
}
/** Gets bounds of the primary split with screen based coordinate. */
@@ -309,6 +310,7 @@
mRotation = rotation;
mDensity = density;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ updateDividerConfig(mContext);
initDividerPosition(mTempRect);
updateInvisibleRect();
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 f1670cd..4459f57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,6 +27,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -271,11 +272,12 @@
TaskStackListenerImpl taskStackListener,
UiEventLogger uiEventLogger,
InteractionJankMonitor jankMonitor,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
- uiEventLogger, mainExecutor, mainHandler);
+ uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index b310ee2..b5ed509 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -34,6 +34,7 @@
import android.os.Binder;
import android.util.Slog;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.IWindow;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -47,6 +48,7 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
@@ -67,11 +69,14 @@
private SurfaceControl mLeash;
private View mBackgroundView;
private @OneHandedState.State int mCurrentState;
+ private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
- public BackgroundWindowManager(Context context) {
+ public BackgroundWindowManager(Context context,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
super(context.getResources().getConfiguration(), null /* rootSurface */,
null /* hostInputToken */);
mContext = context;
+ mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
mTransactionFactory = SurfaceControl.Transaction::new;
}
@@ -112,6 +117,7 @@
.setOpaque(true)
.setName(TAG)
.setCallsite("BackgroundWindowManager#attachToParentSurface");
+ mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder);
mLeash = builder.build();
b.setParent(mLeash);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 679d4ca..ad135d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -47,6 +47,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -204,12 +205,14 @@
DisplayController displayController, DisplayLayout displayLayout,
TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
ShellExecutor mainExecutor, Handler mainHandler) {
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
OneHandedState oneHandedState = new OneHandedState();
- BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
+ BackgroundWindowManager backgroundWindowManager =
+ new BackgroundWindowManager(context, rootDisplayAreaOrganizer);
OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
settingsUtil, windowManager, backgroundWindowManager);
OneHandedAnimationController animationController =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 0006244..f707363 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.RemoteAction;
@@ -73,6 +74,11 @@
void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
/**
+ * Wait until the next frame to run the given Runnable.
+ */
+ void runWithNextFrame(@NonNull Runnable runnable);
+
+ /**
* Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
* need to synchronize the movements on the same frame as PiP.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..2d7c5ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -179,8 +179,10 @@
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
}
}
@@ -196,6 +198,34 @@
}
};
+ /**
+ * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+ *
+ * This is done to avoid a race condition between the last transaction applied in
+ * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a
+ * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it
+ * gets applied before the transaction created by the last onAnimationUpdate. As a result of
+ * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which
+ * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize
+ * operation until the next frame. This aligns the last onAnimationUpdate transaction with the
+ * WCT application.
+ *
+ * The race only happens when the PiP surface transaction has to be synced with the PiP menu
+ * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and
+ * the PiP menu contents.
+ */
+ private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+ if (!shouldSyncPipTransactionWithMenu()) {
+ finishResizeRunnable.run();
+ return;
+ }
+ mPipMenuController.runWithNextFrame(finishResizeRunnable);
+ }
+
+ private boolean shouldSyncPipTransactionWithMenu() {
+ return mPipMenuController.isMenuVisible();
+ }
+
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +251,7 @@
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1223,7 +1253,7 @@
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1265,7 +1295,7 @@
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index a0a8f9f..531b9de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -305,6 +305,18 @@
showResizeHandle);
}
+ @Override
+ public void runWithNextFrame(Runnable runnable) {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ runnable.run();
+ }
+
+ mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+ mMainHandler.post(runnable);
+ });
+ mPipMenuView.invalidate();
+ }
+
/**
* Move the PiP menu, which does a translation and possibly a scale transformation.
*/
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 616d447..d28a9f3 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
@@ -612,9 +612,24 @@
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
+ int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChanged(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
+ int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+ if (!mEnablePipKeepClearAlgorithm) {
+ int pipTop = mPipBoundsState.getBounds().top;
+ int diff = newMaxMovementBound - oldMaxMovementBound;
+ if (diff < 0 && pipTop > newMaxMovementBound) {
+ // bottom inset has increased, move PiP up if it is too low
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
+ newMaxMovementBound - pipTop);
+ }
+ if (diff > 0 && oldMaxMovementBound == pipTop) {
+ // bottom inset has decreased, move PiP down if it was by the edge
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
+ }
+ }
}
});
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 975d4bb..a9a97be 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
@@ -427,7 +427,7 @@
// If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
// occluded by the IME or shelf.
if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting()) {
+ if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
} else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ab7edbf..c39400a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -421,6 +421,18 @@
}
@Override
+ public void runWithNextFrame(Runnable runnable) {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ runnable.run();
+ }
+
+ mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+ mMainHandler.post(runnable);
+ });
+ mPipMenuView.invalidate();
+ }
+
+ @Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
Rect pipDestBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
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 89205a6..519ec14 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
@@ -175,6 +175,9 @@
}
private void onInit() {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mOrganizer.shareTransactionQueue();
+ }
mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
this::createExternalInterface, this);
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 dca516a..36dd8ed 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
@@ -26,11 +26,16 @@
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.InputChannel;
import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -64,8 +69,11 @@
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
private DesktopModeController mDesktopModeController;
+ private EventReceiver mEventReceiver;
+ private InputMonitor mInputMonitor;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+ private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
public CaptionWindowDecorViewModel(
Context context,
@@ -108,12 +116,19 @@
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
+ TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
+ mDragStartListener);
CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
setupWindowDecorationForTransition(taskInfo, startT, finishT);
+ if (mInputMonitor == null) {
+ mInputMonitor = InputManager.getInstance().monitorGestureInput(
+ "caption-touch", mContext.getDisplayId());
+ mEventReceiver = new EventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ }
return true;
}
@@ -165,6 +180,7 @@
@Override
public void onClick(View v) {
+ CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -176,6 +192,15 @@
}
} else if (id == R.id.back_button) {
injectBackKey();
+ } else if (id == R.id.caption_handle) {
+ decoration.createHandleMenu();
+ } else if (id == R.id.desktop_button) {
+ mDesktopModeController.setDesktopModeActive(true);
+ decoration.closeHandleMenu();
+ } else if (id == R.id.fullscreen_button) {
+ mDesktopModeController.setDesktopModeActive(false);
+ decoration.closeHandleMenu();
+ decoration.setButtonVisibility();
}
}
private void injectBackKey() {
@@ -257,6 +282,36 @@
}
}
+ // InputEventReceiver to listen for touch input outside of caption bounds
+ private class EventReceiver extends InputEventReceiver {
+ EventReceiver(InputChannel channel, Looper looper) {
+ super(channel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ if (event instanceof MotionEvent
+ && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+ handled = true;
+ CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+ }
+ finishInputEvent(event, handled);
+ }
+ }
+
+ // If any input received is outside of caption bounds, turn off handle menu
+ private void handleMotionEvent(MotionEvent ev) {
+ int size = mWindowDecorByTaskId.size();
+ for (int i = 0; i < size; i++) {
+ CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration != null) {
+ decoration.closeHandleMenuIfNeeded(ev);
+ }
+ }
+ }
+
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
@@ -264,4 +319,11 @@
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
+
+ private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
+ @Override
+ public void onDragStart(int taskId) {
+ mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+ }
+ }
}
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 9d61c14..03cad04 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
@@ -20,10 +20,14 @@
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
@@ -58,6 +62,8 @@
private boolean mDesktopActive;
+ private AdditionalWindow mHandleMenu;
+
CaptionWindowDecoration(
Context context,
DisplayController displayController,
@@ -123,7 +129,20 @@
if (isDragResizeable) {
mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
}
+ final Resources resources = mDecorWindowContext.getResources();
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int captionHeight = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionHeightId);
+ final int captionWidth = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionWidthId);
+ final int captionLeft = taskBounds.width() / 2
+ - captionWidth / 2;
+ final int captionTop = taskBounds.top
+ <= captionHeight / 2 ? 0 : -captionHeight / 2;
+ mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
+
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+ taskInfo = null; // Clear it just in case we use it accidentally
mTaskOrganizer.applyTransaction(wct);
@@ -137,15 +156,14 @@
}
// If this task is not focused, do not show caption.
- setCaptionVisibility(taskInfo.isFocused);
+ setCaptionVisibility(mTaskInfo.isFocused);
// Only handle should show if Desktop Mode is inactive.
boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
- if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
+ if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
mDesktopActive = desktopCurrentStatus;
setButtonVisibility();
}
- taskInfo = null; // Clear it just in case we use it accidentally
if (!isDragResizeable) {
closeDragResizeListener();
@@ -184,9 +202,22 @@
back.setOnClickListener(mOnCaptionButtonClickListener);
View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
+ handle.setOnClickListener(mOnCaptionButtonClickListener);
setButtonVisibility();
}
+ private void setupHandleMenu() {
+ View menu = mHandleMenu.mWindowViewHost.getView();
+ View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
+ View desktop = menu.findViewById(R.id.desktop_button);
+ desktop.setOnClickListener(mOnCaptionButtonClickListener);
+ View split = menu.findViewById(R.id.split_screen_button);
+ split.setOnClickListener(mOnCaptionButtonClickListener);
+ View more = menu.findViewById(R.id.more_button);
+ more.setOnClickListener(mOnCaptionButtonClickListener);
+ }
+
/**
* Sets caption visibility based on task focus.
*
@@ -194,8 +225,9 @@
*/
private void setCaptionVisibility(boolean visible) {
int v = visible ? View.VISIBLE : View.GONE;
- View caption = mResult.mRootView.findViewById(R.id.caption);
- caption.setVisibility(v);
+ View captionView = mResult.mRootView.findViewById(R.id.caption);
+ captionView.setVisibility(v);
+ if (!visible) closeHandleMenu();
}
/**
@@ -203,6 +235,7 @@
*
*/
public void setButtonVisibility() {
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
View back = caption.findViewById(R.id.back_button);
@@ -220,6 +253,10 @@
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
+ public boolean isHandleMenuActive() {
+ return mHandleMenu != null;
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
@@ -228,9 +265,67 @@
mDragResizeListener = null;
}
+ /**
+ * Create and display handle menu window
+ */
+ public void createHandleMenu() {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String namePrefix = "Caption Menu";
+ mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+ x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
+ width, height);
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ setupHandleMenu();
+ }
+
+ /**
+ * Close the handle menu window
+ */
+ public void closeHandleMenu() {
+ if (!isHandleMenuActive()) return;
+ mHandleMenu.releaseView();
+ mHandleMenu = null;
+ }
+
+ @Override
+ void releaseViews() {
+ closeHandleMenu();
+ super.releaseViews();
+ }
+
+ /**
+ * Close an open handle menu if input is outside of menu coordinates
+ * @param ev the tapped point to compare against
+ * @return
+ */
+ public void closeHandleMenuIfNeeded(MotionEvent ev) {
+ if (mHandleMenu != null) {
+ Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+ .positionInParent;
+ final Resources resources = mDecorWindowContext.getResources();
+ ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+ ev.offsetLocation(-positionInParent.x, -positionInParent.y);
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ if (!(ev.getX() >= 0 && ev.getY() >= 0
+ && ev.getX() <= width && ev.getY() <= height)) {
+ closeHandleMenu();
+ }
+ }
+ }
+
@Override
public void close() {
closeDragResizeListener();
+ closeHandleMenu();
super.close();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 27c1011..f0f2db7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -42,14 +42,18 @@
private final Rect mResizeTaskBounds = new Rect();
private int mCtrlType;
+ private DragStartListener mDragStartListener;
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
+ mDragStartListener = dragStartListener;
}
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
@@ -97,4 +101,12 @@
mTaskOrganizer.applyTransaction(wct);
}
}
+
+ interface DragStartListener {
+ /**
+ * Inform the implementing class that a drag resize has started
+ * @param taskId id of this positioner's {@link WindowDecoration}
+ */
+ void onDragStart(int taskId);
+ }
}
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 b314163..7ecb3f3 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
@@ -200,16 +200,17 @@
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
final Resources resources = mDecorWindowContext.getResources();
- final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
- final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
+ outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+ outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
+ loadDimensionPixelSize(resources, params.mOutsetRightId)
- - decorContainerOffsetX;
+ - outResult.mDecorContainerOffsetX;
outResult.mHeight = taskBounds.height()
+ loadDimensionPixelSize(resources, params.mOutsetBottomId)
- - decorContainerOffsetY;
+ - outResult.mDecorContainerOffsetY;
startT.setPosition(
- mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+ mDecorationContainerSurface,
+ outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
.setWindowCrop(mDecorationContainerSurface,
outResult.mWidth, outResult.mHeight)
// TODO(b/244455401): Change the z-order when it's better organized
@@ -252,14 +253,11 @@
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
- //Prevent caption from going offscreen if task is too high up
- final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
-
startT.setPosition(
- mCaptionContainerSurface, -decorContainerOffsetX
- + taskBounds.width() / 2 - captionWidth / 2,
- -decorContainerOffsetY - captionYPos)
- .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
+ mCaptionContainerSurface,
+ -outResult.mDecorContainerOffsetX + params.mCaptionX,
+ -outResult.mDecorContainerOffsetY + params.mCaptionY)
+ .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
@@ -292,7 +290,7 @@
// Caption insets
mCaptionInsetsRect.set(taskBounds);
mCaptionInsetsRect.bottom =
- mCaptionInsetsRect.top + captionHeight - captionYPos;
+ mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
CAPTION_INSETS_TYPES);
} else {
@@ -302,10 +300,10 @@
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
mTaskSurfaceCrop.set(
- decorContainerOffsetX,
- decorContainerOffsetY,
- outResult.mWidth + decorContainerOffsetX,
- outResult.mHeight + decorContainerOffsetY);
+ outResult.mDecorContainerOffsetX,
+ outResult.mDecorContainerOffsetY,
+ outResult.mWidth + outResult.mDecorContainerOffsetX,
+ outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
.setCrop(mTaskSurface, mTaskSurfaceCrop);
@@ -326,7 +324,7 @@
return true;
}
- private void releaseViews() {
+ void releaseViews() {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -369,20 +367,60 @@
releaseViews();
}
- private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+ static int loadDimensionPixelSize(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimensionPixelSize(resourceId);
}
- private static float loadDimension(Resources resources, int resourceId) {
+ static float loadDimension(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimension(resourceId);
}
+ /**
+ * Create a window associated with this WindowDecoration.
+ * Note that subclass must dispose of this when the task is hidden/closed.
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
+ * @return
+ */
+ AdditionalWindow addWindow(int layoutId, String namePrefix,
+ SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ SurfaceControl windowSurfaceControl = builder
+ .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setParent(mDecorationContainerSurface)
+ .build();
+ View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+
+ t.setPosition(
+ windowSurfaceControl, xPos, yPos)
+ .setWindowCrop(windowSurfaceControl, width, height)
+ .show(windowSurfaceControl);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+ windowSurfaceControl, null /* hostInputToken */);
+ SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
+ .create(mDecorWindowContext, mDisplay, windowManager);
+ viewHost.setView(v, lp);
+ return new AdditionalWindow(windowSurfaceControl, viewHost,
+ mSurfaceControlTransactionSupplier);
+ }
+
static class RelayoutParams{
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
@@ -395,6 +433,9 @@
int mOutsetLeftId;
int mOutsetRightId;
+ int mCaptionX;
+ int mCaptionY;
+
void setOutsets(int leftId, int topId, int rightId, int bottomId) {
mOutsetLeftId = leftId;
mOutsetTopId = topId;
@@ -402,6 +443,11 @@
mOutsetBottomId = bottomId;
}
+ void setCaptionPosition(int left, int top) {
+ mCaptionX = left;
+ mCaptionY = top;
+ }
+
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
@@ -412,6 +458,9 @@
mOutsetBottomId = Resources.ID_NULL;
mOutsetLeftId = Resources.ID_NULL;
mOutsetRightId = Resources.ID_NULL;
+
+ mCaptionX = 0;
+ mCaptionY = 0;
}
}
@@ -419,10 +468,14 @@
int mWidth;
int mHeight;
T mRootView;
+ int mDecorContainerOffsetX;
+ int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
+ mDecorContainerOffsetX = 0;
+ mDecorContainerOffsetY = 0;
mRootView = null;
}
}
@@ -432,4 +485,41 @@
return new SurfaceControlViewHost(c, d, wmm);
}
}
+
+ /**
+ * Subclass for additional windows associated with this WindowDecoration
+ */
+ static class AdditionalWindow {
+ SurfaceControl mWindowSurface;
+ SurfaceControlViewHost mWindowViewHost;
+ Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+ private AdditionalWindow(SurfaceControl surfaceControl,
+ SurfaceControlViewHost surfaceControlViewHost,
+ Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ mWindowSurface = surfaceControl;
+ mWindowViewHost = surfaceControlViewHost;
+ mTransactionSupplier = transactionSupplier;
+ }
+
+ void releaseView() {
+ WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
+
+ if (mWindowViewHost != null) {
+ mWindowViewHost.release();
+ mWindowViewHost = null;
+ }
+ windowManager = null;
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ boolean released = false;
+ if (mWindowSurface != null) {
+ t.remove(mWindowSurface);
+ mWindowSurface = null;
+ released = true;
+ }
+ if (released) {
+ t.apply();
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index f4efc37..1c28c3d 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -6,3 +6,4 @@
lbill@google.com
madym@google.com
hwwang@google.com
+chenghsiuchang@google.com
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 6f1ff99..8765ad1 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
@@ -45,14 +45,23 @@
fun FlickerTestParameter.splitScreenEntered(
component1: IComponentMatcher,
component2: IComponentMatcher,
- fromOtherApp: Boolean
+ fromOtherApp: Boolean,
+ appExistAtStart: Boolean = true
) {
if (fromOtherApp) {
- appWindowIsInvisibleAtStart(component1)
+ if (appExistAtStart) {
+ appWindowIsInvisibleAtStart(component1)
+ } else {
+ appWindowIsNotContainAtStart(component1)
+ }
} else {
appWindowIsVisibleAtStart(component1)
}
- appWindowIsInvisibleAtStart(component2)
+ if (appExistAtStart) {
+ appWindowIsInvisibleAtStart(component2)
+ } else {
+ appWindowIsNotContainAtStart(component2)
+ }
splitScreenDividerIsInvisibleAtStart()
appWindowIsVisibleAtEnd(component1)
@@ -315,6 +324,10 @@
assertWmEnd { this.isAppWindowInvisible(component) }
}
+fun FlickerTestParameter.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+ assertWmStart { this.notContains(component) }
+}
+
fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 0000000..e9c765a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.view.WindowManagerPolicyConstants
+import android.platform.test.annotations.Postsubmit
+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.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+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 enter split screen by dragging a shortcut.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenByDragFromShortcut(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(testSpec.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ tapl.goHome()
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ tapl.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .openDeepShortcutMenu()
+ .getMenuItem("Split Screen Secondary Activity")
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
+ fromOtherApp = false, appExistAtStart = false)
+
+ @Postsubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Postsubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+ @Postsubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @Postsubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, landscapePosLeft = false, portraitPosTop = false)
+
+ @Postsubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
+ secondaryApp)
+
+ @Postsubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+
+ @Postsubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.notContains(secondaryApp)
+ .then()
+ .isAppWindowInvisible(secondaryApp, isOptional = true)
+ .then()
+ .isAppWindowVisible(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(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
index f3f7067..11948dbf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -24,6 +24,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -41,11 +42,13 @@
private BackgroundWindowManager mBackgroundWindowManager;
@Mock
private DisplayLayout mMockDisplayLayout;
+ @Mock
+ private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mBackgroundWindowManager = new BackgroundWindowManager(mContext);
+ mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer);
mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
}
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 4d37e5d..15181b1 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
@@ -35,6 +35,7 @@
import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -64,6 +65,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -102,12 +104,14 @@
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+ private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
@Before
public void setUp() {
mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
+ mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
@@ -227,8 +231,8 @@
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
- verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -242,7 +246,7 @@
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction)
.addRectInsetsProvider(taskInfo.token,
- new Rect(100, 300, 400, 332),
+ new Rect(100, 300, 400, 364),
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
@@ -366,6 +370,71 @@
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
+ @Test
+ public void testAddWindow() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ windowDecor.relayout(taskInfo);
+
+ final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder additionalWindowSurfaceBuilder =
+ createMockSurfaceControlBuilder(additionalWindowSurface);
+ mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
+
+ WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
+
+ verify(additionalWindowSurfaceBuilder).setContainerLayer();
+ verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
+ verify(additionalWindowSurfaceBuilder).build();
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+ verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
+ verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
+ .create(any(), eq(defaultDisplay), any());
+ assertThat(additionalWindow.mWindowViewHost).isNotNull();
+
+ additionalWindow.releaseView();
+
+ assertThat(additionalWindow.mWindowViewHost).isNull();
+ assertThat(additionalWindow.mWindowSurface).isNull();
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -429,5 +498,20 @@
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
+
+ private WindowDecoration.AdditionalWindow addTestWindow() {
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String name = "Test Window";
+ WindowDecoration.AdditionalWindow additionalWindow =
+ addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+ x - mRelayoutResult.mDecorContainerOffsetX,
+ y - mRelayoutResult.mDecorContainerOffsetY,
+ width, height);
+ return additionalWindow;
+ }
}
}
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index 15be80c..d1cfd03 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <memory>
#include <utility>
#include "androidfw/CursorWindow.h"
@@ -184,7 +185,7 @@
ASSERT_EQ(w->allocRow(), OK);
// Scratch buffer that will fit before inflation
- void* buf = malloc(kHalfInlineSize);
+ char buf[kHalfInlineSize];
// Store simple value
ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -262,7 +263,7 @@
ASSERT_EQ(w->allocRow(), OK);
// Scratch buffer that will fit before inflation
- void* buf = malloc(kHalfInlineSize);
+ char buf[kHalfInlineSize];
// Store simple value
ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -322,7 +323,8 @@
ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
// Store object that forces inflation
- void* buf = malloc(kGiantSize);
+ std::unique_ptr<char> bufPtr(new char[kGiantSize]);
+ void* buf = bufPtr.get();
memset(buf, 42, kGiantSize);
ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 29f3773..cccc0f81 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -678,6 +678,7 @@
srcs: [
"tests/unit/main.cpp",
"tests/unit/ABitmapTests.cpp",
+ "tests/unit/AutoBackendTextureReleaseTests.cpp",
"tests/unit/CacheManagerTests.cpp",
"tests/unit/CanvasContextTests.cpp",
"tests/unit/CanvasOpTests.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index ef5eacb..b656b6a 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -32,9 +32,17 @@
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
GrBackendFormat backendFormat =
GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+ LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
+ __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
+ ", AHardwareBuffer_Format==%" PRIu32 ".",
+ static_cast<int>(context->backend()), desc.format);
mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
createProtectedImage, backendFormat, false);
+ LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
+ __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
+ ", protected==%d",
+ desc.width, desc.height, createProtectedImage);
}
void AutoBackendTextureRelease::unref(bool releaseImage) {
@@ -74,13 +82,13 @@
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+ // The following ref will be counteracted by Skia calling releaseProc, either during
+ // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
+ // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+ ref();
mImage = SkImage::MakeFromTexture(
context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
- if (mImage.get()) {
- // The following ref will be counteracted by releaseProc, when SkImage is discarded.
- ref();
- }
}
void AutoBackendTextureRelease::newBufferContent(GrDirectContext* context) {
diff --git a/libs/hwui/AutoBackendTextureRelease.h b/libs/hwui/AutoBackendTextureRelease.h
index c9bb767..f0eb2a8 100644
--- a/libs/hwui/AutoBackendTextureRelease.h
+++ b/libs/hwui/AutoBackendTextureRelease.h
@@ -25,6 +25,9 @@
namespace android {
namespace uirenderer {
+// Friend TestUtils serves as a proxy for any test cases that require access to private members.
+class TestUtils;
+
/**
* AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object
* that keeps GPU resources alive until the last SkImage object using them is destroyed.
@@ -66,6 +69,9 @@
// mImage is the SkImage created from mBackendTexture.
sk_sp<SkImage> mImage;
+
+ // Friend TestUtils serves as a proxy for any test cases that require access to private members.
+ friend class TestUtils;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 35258a3..0513447 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -20,7 +20,6 @@
#include <android/api-level.h>
#else
#define __ANDROID_API_P__ 28
-#define __ANDROID_API_U__ 34
#endif
#include <androidfw/ResourceTypes.h>
#include <hwui/Canvas.h>
@@ -31,9 +30,8 @@
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
-#include "Bitmap.h"
#include "FontUtils.h"
-#include "SkAndroidFrameworkUtils.h"
+#include "Bitmap.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
#include "SkClipOp.h"
@@ -44,10 +42,10 @@
#include "SkMatrix.h"
#include "SkPath.h"
#include "SkPoint.h"
-#include "SkRRect.h"
#include "SkRect.h"
#include "SkRefCnt.h"
#include "SkRegion.h"
+#include "SkRRect.h"
#include "SkScalar.h"
#include "SkVertices.h"
@@ -712,9 +710,6 @@
static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) {
Canvas::setCompatibilityVersion(apiLevel);
- if (apiLevel < __ANDROID_API_U__) {
- SkAndroidFrameworkUtils::UseLegacyLocalMatrixConcatenation();
- }
}
static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat top, jfloat right,
@@ -805,6 +800,7 @@
ret |= RegisterMethodsOrDie(env, "android/graphics/BaseCanvas", gDrawMethods, NELEM(gDrawMethods));
ret |= RegisterMethodsOrDie(env, "android/graphics/BaseRecordingCanvas", gDrawMethods, NELEM(gDrawMethods));
return ret;
+
}
}; // namespace android
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index f2282e66..1a47db5 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -229,10 +229,10 @@
// TODO should we let the bound of the drawable do this for us?
const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
- auto clipBounds = canvas->getLocalClipBounds();
- SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
- SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
if (!quickRejected) {
+ auto clipBounds = canvas->getLocalClipBounds();
+ SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
+ SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();
const LayerProperties& layerProperties = properties.layerProperties();
// composing a hardware layer
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 75865c7..9d5c13e 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -16,6 +16,7 @@
#pragma once
+#include <AutoBackendTextureRelease.h>
#include <DisplayList.h>
#include <Matrix.h>
#include <Properties.h>
@@ -293,6 +294,11 @@
static SkRect getClipBounds(const SkCanvas* canvas);
static SkRect getLocalClipBounds(const SkCanvas* canvas);
+ static int getUsageCount(const AutoBackendTextureRelease* textureRelease) {
+ EXPECT_NE(nullptr, textureRelease);
+ return textureRelease->mUsageCount;
+ }
+
struct CallCounts {
int sync = 0;
int contextDestroyed = 0;
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
new file mode 100644
index 0000000..2ec78a4
--- /dev/null
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "AutoBackendTextureRelease.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+AHardwareBuffer* allocHardwareBuffer() {
+ AHardwareBuffer* buffer;
+ AHardwareBuffer_Desc desc = {
+ .width = 16,
+ .height = 16,
+ .layers = 1,
+ .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+ };
+ constexpr int kSucceeded = 0;
+ int status = AHardwareBuffer_allocate(&desc, &buffer);
+ EXPECT_EQ(kSucceeded, status);
+ return buffer;
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_invalid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) {
+ AHardwareBuffer* buffer = allocHardwareBuffer();
+ AutoBackendTextureRelease* textureRelease =
+ new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+ EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+ // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+ textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
+
+ EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+ textureRelease->unref(true);
+ AHardwareBuffer_release(buffer);
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_valid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_valid) {
+ AHardwareBuffer* buffer = allocHardwareBuffer();
+ AutoBackendTextureRelease* textureRelease =
+ new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+ EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+ textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, renderThread.getGrContext());
+
+ EXPECT_EQ(2, TestUtils::getUsageCount(textureRelease));
+
+ textureRelease->unref(true);
+ AHardwareBuffer_release(buffer);
+}
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index b38f9ea..98578b2 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -24,6 +24,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -123,20 +125,23 @@
* @hide
*/
public static GnssCapabilities empty() {
- return new GnssCapabilities(0, 0, 0);
+ return new GnssCapabilities(0, 0, 0, new ArrayList<>());
}
private final @TopHalCapabilityFlags int mTopFlags;
private final @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
private final @SubHalPowerCapabilityFlags int mPowerFlags;
+ private final @NonNull List<GnssSignalType> mGnssSignalTypes;
private GnssCapabilities(
@TopHalCapabilityFlags int topFlags,
@SubHalMeasurementCorrectionsCapabilityFlags int measurementCorrectionsFlags,
- @SubHalPowerCapabilityFlags int powerFlags) {
+ @SubHalPowerCapabilityFlags int powerFlags,
+ @NonNull List<GnssSignalType> gnssSignalTypes) {
mTopFlags = topFlags;
mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
mPowerFlags = powerFlags;
+ mGnssSignalTypes = gnssSignalTypes;
}
/**
@@ -148,7 +153,8 @@
if (mTopFlags == flags) {
return this;
} else {
- return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags);
+ return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
+ new ArrayList<>(mGnssSignalTypes));
}
}
@@ -163,7 +169,8 @@
if (mMeasurementCorrectionsFlags == flags) {
return this;
} else {
- return new GnssCapabilities(mTopFlags, flags, mPowerFlags);
+ return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
+ new ArrayList<>(mGnssSignalTypes));
}
}
@@ -177,7 +184,8 @@
if (mPowerFlags == flags) {
return this;
} else {
- return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags);
+ return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
+ new ArrayList<>(mGnssSignalTypes));
}
}
@@ -424,6 +432,14 @@
return (mPowerFlags & SUB_HAL_POWER_CAPABILITY_OTHER_MODES) != 0;
}
+ /**
+ * Returns the list of {@link GnssSignalType}s that the GNSS chipset supports.
+ */
+ @NonNull
+ public List<GnssSignalType> getGnssSignalTypes() {
+ return mGnssSignalTypes;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -441,14 +457,15 @@
@Override
public int hashCode() {
- return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+ return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags, mGnssSignalTypes);
}
public static final @NonNull Creator<GnssCapabilities> CREATOR =
new Creator<GnssCapabilities>() {
@Override
public GnssCapabilities createFromParcel(Parcel in) {
- return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt());
+ return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt(),
+ in.createTypedArrayList(GnssSignalType.CREATOR));
}
@Override
@@ -467,6 +484,7 @@
parcel.writeInt(mTopFlags);
parcel.writeInt(mMeasurementCorrectionsFlags);
parcel.writeInt(mPowerFlags);
+ parcel.writeTypedList(mGnssSignalTypes);
}
@Override
@@ -545,6 +563,9 @@
if (hasPowerOtherModes()) {
builder.append("OTHER_MODES_POWER ");
}
+ if (!mGnssSignalTypes.isEmpty()) {
+ builder.append("signalTypes=").append(mGnssSignalTypes).append(" ");
+ }
if (builder.length() > 1) {
builder.setLength(builder.length() - 1);
} else {
@@ -562,17 +583,20 @@
private @TopHalCapabilityFlags int mTopFlags;
private @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
private @SubHalPowerCapabilityFlags int mPowerFlags;
+ private @NonNull List<GnssSignalType> mGnssSignalTypes;
public Builder() {
mTopFlags = 0;
mMeasurementCorrectionsFlags = 0;
mPowerFlags = 0;
+ mGnssSignalTypes = new ArrayList<>();
}
public Builder(@NonNull GnssCapabilities capabilities) {
mTopFlags = capabilities.mTopFlags;
mMeasurementCorrectionsFlags = capabilities.mMeasurementCorrectionsFlags;
mPowerFlags = capabilities.mPowerFlags;
+ mGnssSignalTypes = capabilities.mGnssSignalTypes;
}
/**
@@ -779,10 +803,19 @@
}
/**
+ * Sets a list of {@link GnssSignalType}.
+ */
+ public @NonNull Builder setGnssSignalTypes(@NonNull List<GnssSignalType> gnssSignalTypes) {
+ mGnssSignalTypes = gnssSignalTypes;
+ return this;
+ }
+
+ /**
* Builds a new GnssCapabilities.
*/
public @NonNull GnssCapabilities build() {
- return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+ return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
+ new ArrayList<>(mGnssSignalTypes));
}
private static int setFlag(int value, int flag, boolean set) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/location/java/android/location/GnssSignalType.aidl
similarity index 73%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to location/java/android/location/GnssSignalType.aidl
index 1550ab3..1c43fe5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/location/java/android/location/GnssSignalType.aidl
@@ -1,11 +1,11 @@
/*
- * 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package android.location;
-parcelable RemoteTransitionCompat;
+parcelable GnssSignalType;
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
new file mode 100644
index 0000000..f9c6e72
--- /dev/null
+++ b/location/java/android/location/GnssSignalType.java
@@ -0,0 +1,152 @@
+/*
+ * 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.location;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * This class represents a GNSS signal type.
+ */
+public final class GnssSignalType implements Parcelable {
+
+ /**
+ * Creates a {@link GnssSignalType} with a full list of parameters.
+ *
+ * @param constellationType the constellation type as defined in
+ * {@link GnssStatus.ConstellationType}
+ * @param carrierFrequencyHz the carrier frequency in Hz
+ * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
+ */
+ @NonNull
+ public static GnssSignalType create(@GnssStatus.ConstellationType int constellationType,
+ @FloatRange(from = 0.0f, fromInclusive = false) double carrierFrequencyHz,
+ @NonNull String codeType) {
+ Preconditions.checkArgument(carrierFrequencyHz > 0,
+ "carrierFrequencyHz must be greater than 0.");
+ Objects.requireNonNull(codeType);
+ return new GnssSignalType(constellationType, carrierFrequencyHz, codeType);
+ }
+
+ @GnssStatus.ConstellationType
+ private final int mConstellationType;
+ @FloatRange(from = 0.0f, fromInclusive = false)
+ private final double mCarrierFrequencyHz;
+ @NonNull
+ private final String mCodeType;
+
+ /**
+ * Creates a {@link GnssSignalType} with a full list of parameters.
+ */
+ private GnssSignalType(@GnssStatus.ConstellationType int constellationType,
+ double carrierFrequencyHz, @NonNull String codeType) {
+ this.mConstellationType = constellationType;
+ this.mCarrierFrequencyHz = carrierFrequencyHz;
+ this.mCodeType = codeType;
+ }
+
+ /** Returns the {@link GnssStatus.ConstellationType}. */
+ @GnssStatus.ConstellationType
+ public int getConstellationType() {
+ return mConstellationType;
+ }
+
+ /** Returns the carrier frequency in Hz. */
+ @FloatRange(from = 0.0f, fromInclusive = false)
+ public double getCarrierFrequencyHz() {
+ return mCarrierFrequencyHz;
+ }
+
+ /**
+ * Return the code type.
+ *
+ * @see GnssMeasurement#getCodeType()
+ */
+ @NonNull
+ public String getCodeType() {
+ return mCodeType;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<GnssSignalType> CREATOR =
+ new Parcelable.Creator<GnssSignalType>() {
+ @Override
+ @NonNull
+ public GnssSignalType createFromParcel(@NonNull Parcel parcel) {
+ return new GnssSignalType(parcel.readInt(), parcel.readDouble(),
+ parcel.readString());
+ }
+
+ @Override
+ public GnssSignalType[] newArray(int i) {
+ return new GnssSignalType[i];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mConstellationType);
+ parcel.writeDouble(mCarrierFrequencyHz);
+ parcel.writeString(mCodeType);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append("GnssSignalType[");
+ s.append(" Constellation=").append(mConstellationType);
+ s.append(", CarrierFrequencyHz=").append(mCarrierFrequencyHz);
+ s.append(", CodeType=").append(mCodeType);
+ s.append(']');
+ return s.toString();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof GnssSignalType) {
+ GnssSignalType other = (GnssSignalType) obj;
+ return mConstellationType == other.mConstellationType
+ && Double.compare(mCarrierFrequencyHz, other.mCarrierFrequencyHz) == 0
+ && mCodeType.equals(other.mCodeType);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConstellationType, mCarrierFrequencyHz, mCodeType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 60e3a306..980f63b 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -23,6 +23,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.IBinder;
@@ -220,46 +221,59 @@
/**
* @hide
- * Mute state used for anonymization.
+ * Mute state used for the initial state and when API is accessed without permission.
*/
- public static final int PLAYER_MUTE_INVALID = -1;
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_UNKNOWN = -1;
/**
* @hide
* Flag used when muted by master volume.
*/
- public static final int PLAYER_MUTE_MASTER = (1 << 0);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_MASTER = (1 << 0);
/**
* @hide
* Flag used when muted by stream volume.
*/
- public static final int PLAYER_MUTE_STREAM_VOLUME = (1 << 1);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_STREAM_VOLUME = (1 << 1);
/**
* @hide
* Flag used when muted by stream mute.
*/
- public static final int PLAYER_MUTE_STREAM_MUTED = (1 << 2);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_STREAM_MUTED = (1 << 2);
/**
* @hide
- * Flag used when playback is restricted by AppOps manager with OP_PLAY_AUDIO.
+ * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
*/
- public static final int PLAYER_MUTE_PLAYBACK_RESTRICTED = (1 << 3);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_APP_OPS = (1 << 3);
/**
* @hide
* Flag used when muted by client volume.
*/
- public static final int PLAYER_MUTE_CLIENT_VOLUME = (1 << 4);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_CLIENT_VOLUME = (1 << 4);
/**
* @hide
* Flag used when muted by volume shaper.
*/
- public static final int PLAYER_MUTE_VOLUME_SHAPER = (1 << 5);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5);
/** @hide */
@IntDef(
flag = true,
- value = {PLAYER_MUTE_MASTER, PLAYER_MUTE_STREAM_VOLUME, PLAYER_MUTE_STREAM_MUTED,
- PLAYER_MUTE_PLAYBACK_RESTRICTED, PLAYER_MUTE_CLIENT_VOLUME,
- PLAYER_MUTE_VOLUME_SHAPER})
+ value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
+ MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerMuteEvent {
}
@@ -303,7 +317,7 @@
mPlayerType = pic.mPlayerType;
mClientUid = uid;
mClientPid = pid;
- mMutedState = PLAYER_MUTE_INVALID;
+ mMutedState = MUTED_BY_UNKNOWN;
mDeviceId = PLAYER_DEVICEID_INVALID;
mPlayerState = PLAYER_STATE_IDLE;
mPlayerAttr = pic.mAttributes;
@@ -352,7 +366,7 @@
anonymCopy.mPlayerAttr = builder.build();
anonymCopy.mDeviceId = in.mDeviceId;
// anonymized data
- anonymCopy.mMutedState = PLAYER_MUTE_INVALID;
+ anonymCopy.mMutedState = MUTED_BY_UNKNOWN;
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -413,9 +427,27 @@
/**
* @hide
- * @return the mute state as a combination of {@link PlayerMuteEvent} flags
+ * Used for determining if the current player is muted.
+ * <br>Note that if this result is true then {@link #getMutedBy} will be > 0.
+ * @return {@code true} if the player associated with this configuration has been muted (by any
+ * given MUTED_BY_* source event) or {@code false} otherwise.
*/
- @PlayerMuteEvent public int getMutedState() {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public boolean isMuted() {
+ return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN;
+ }
+
+ /**
+ * @hide
+ * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags.
+ * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A
+ * value of 0 represents a player which is not muted.
+ * @return the mute state.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @PlayerMuteEvent public int getMutedBy() {
return mMutedState;
}
@@ -570,14 +602,14 @@
}
private boolean isMuteAffectingActiveState() {
- if (mMutedState == PLAYER_MUTE_INVALID) {
+ if (mMutedState == MUTED_BY_UNKNOWN) {
// mute state is not set, therefore it will not affect the active state
return false;
}
- return (mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0
- || (mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0
- || (mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0;
+ return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
+ || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
+ || (mMutedState & MUTED_BY_APP_OPS) != 0;
}
/**
@@ -694,27 +726,27 @@
"/").append(mClientPid).append(" state:").append(
toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
" sessionId:").append(mSessionId).append(" mutedState:");
- if (mMutedState == PLAYER_MUTE_INVALID) {
- apcToString.append("invalid ");
+ if (mMutedState == MUTED_BY_UNKNOWN) {
+ apcToString.append("unknown ");
} else if (mMutedState == 0) {
apcToString.append("none ");
} else {
- if ((mMutedState & PLAYER_MUTE_MASTER) != 0) {
+ if ((mMutedState & MUTED_BY_MASTER) != 0) {
apcToString.append("master ");
}
- if ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+ if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
apcToString.append("streamVolume ");
}
- if ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0) {
+ if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
apcToString.append("streamMute ");
}
- if ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
- apcToString.append("playbackRestricted ");
+ if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
+ apcToString.append("appOps ");
}
- if ((mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+ if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
apcToString.append("clientVolume ");
}
- if ((mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+ if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
apcToString.append("volumeShaper ");
}
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 95599bd..1183ca3 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -174,6 +174,12 @@
jfieldID typeId;
} gDescriptorInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctorId;
+ jmethodID setId;
+} gBufferInfo;
+
struct fields_t {
jmethodID postEventFromNativeID;
jmethodID lockAndGetContextID;
@@ -460,11 +466,7 @@
return err;
}
- ScopedLocalRef<jclass> clazz(
- env, env->FindClass("android/media/MediaCodec$BufferInfo"));
-
- jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
- env->CallVoidMethod(bufferInfo, method, (jint)offset, (jint)size, timeUs, flags);
+ env->CallVoidMethod(bufferInfo, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
return OK;
}
@@ -1091,13 +1093,7 @@
CHECK(msg->findInt64("timeUs", &timeUs));
CHECK(msg->findInt32("flags", (int32_t *)&flags));
- ScopedLocalRef<jclass> clazz(
- env, env->FindClass("android/media/MediaCodec$BufferInfo"));
- jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V");
- jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
-
- obj = env->NewObject(clazz.get(), ctor);
-
+ obj = env->NewObject(gBufferInfo.clazz, gBufferInfo.ctorId);
if (obj == NULL) {
if (env->ExceptionCheck()) {
ALOGE("Could not create MediaCodec.BufferInfo.");
@@ -1107,7 +1103,7 @@
return;
}
- env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
+ env->CallVoidMethod(obj, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
break;
}
@@ -3235,6 +3231,16 @@
gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I");
CHECK(gDescriptorInfo.typeId != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaCodec$BufferInfo"));
+ CHECK(clazz.get() != NULL);
+ gBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ gBufferInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gBufferInfo.ctorId != NULL);
+
+ gBufferInfo.setId = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
+ CHECK(gBufferInfo.setId != NULL);
}
static void android_media_MediaCodec_native_setup(
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
new file mode 100644
index 0000000..ae65940
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_profile.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="46"
+ android:viewportHeight="46"
+ android:width="46dp"
+ android:height="46dp">
+ <path
+ android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
+ android:strokeColor="#202124"
+ android:strokeAlpha="0.13"
+ android:strokeWidth="1" />
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index c6779fa..08ab1b4 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -22,4 +22,9 @@
<string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
<string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
<string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
+ <string name="more_options_usage_data"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords and <xliff:g id="passkeyssNumber">%2$s</xliff:g> passkeys saved</string>
+ <string name="passkeys">passkeys</string>
+ <string name="passwords">passwords</string>
+ <string name="sign_ins">sign-ins</string>
+ <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 01348e4..3d1fc92 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -33,8 +33,10 @@
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
+import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
@@ -119,6 +121,10 @@
val providerList = CreateFlowUtils.toProviderList(
// Handle runtime cast error
providerList as List<CreateCredentialProviderData>, context)
+ var hasDefault = false
+ var defaultProvider: ProviderInfo = providerList.first()
+ providerList.forEach{providerInfo ->
+ if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
// TODO: covert from real requestInfo
val requestDisplayInfo = RequestDisplayInfo(
"Elisa Beckett",
@@ -127,8 +133,12 @@
"tribank")
return CreatePasskeyUiState(
providers = providerList,
- currentScreenState = CreateScreenState.PASSKEY_INTRO,
+ if (hasDefault)
+ {CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
requestDisplayInfo,
+ if (hasDefault) {
+ ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+ } else null
)
}
@@ -150,38 +160,39 @@
// TODO: below are prototype functionalities. To be removed for productionization.
private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
return listOf(
- CreateCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
+ CreateCredentialProviderData
+ .Builder("com.google/com.google.CredentialManagerService")
.setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 10000),
newEntry("key1", "subkey-2", "elisa.work@google.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 11000),
)
)
.setActionChips(
listOf<Entry>(
- newEntry("key2", "subkey-1", "Go to Settings", "",
- "20 passwords and 7 passkeys saved"),
- newEntry("key2", "subkey-2", "Switch Account", "",
- "20 passwords and 7 passkeys saved"),
+ newEntry("key2", "subkey-1", "Go to Settings",
+ 20, 7, 27, 20000),
+ newEntry("key2", "subkey-2", "Switch Account",
+ 20, 7, 27, 21000),
),
)
- .setIsDefaultProvider(true)
+ .setIsDefaultProvider(false)
.build(),
- CreateCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
+ CreateCredentialProviderData
+ .Builder("com.dashlane/com.dashlane.CredentialManagerService")
.setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 30000),
newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 31000),
)
).setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-3", "Manage Accounts",
- "Manage your accounts in the dashlane app",
- "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 32000),
),
).build(),
)
@@ -193,31 +204,30 @@
.setCredentialEntries(
listOf<Entry>(
newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 10000),
newEntry("key1", "subkey-2", "elisa.work@google.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 11000),
)
).setActionChips(
listOf<Entry>(
- newEntry("key2", "subkey-1", "Go to Settings", "",
- "20 passwords and 7 passkeys saved"),
- newEntry("key2", "subkey-2", "Switch Account", "",
- "20 passwords and 7 passkeys saved"),
+ newEntry("key2", "subkey-1", "Go to Settings",
+ 20, 7, 27, 20000),
+ newEntry("key2", "subkey-2", "Switch Account",
+ 20, 7, 27, 21000),
),
).build(),
GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
.setCredentialEntries(
listOf<Entry>(
newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 30000),
newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 31000),
)
).setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-3", "Manage Accounts",
- "Manage your accounts in the dashlane app",
- "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 40000),
),
).build(),
)
@@ -226,20 +236,32 @@
private fun newEntry(
key: String,
subkey: String,
- title: String,
- subtitle: String,
- usageData: String
+ providerDisplayName: String,
+ passwordCount: Int,
+ passkeyCount: Int,
+ totalCredentialCount: Int,
+ lastUsedTimeMillis: Long,
): Entry {
val slice = Slice.Builder(
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
)
- .addText(title, null, listOf(Entry.HINT_TITLE))
- .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+ .addText(
+ providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
.addIcon(
Icon.createWithResource(context, R.drawable.ic_passkey),
null,
- listOf(Entry.HINT_ICON))
- .addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE))
+ listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
+ .addIcon(
+ Icon.createWithResource(context, R.drawable.ic_profile),
+ null,
+ listOf(Entry.HINT_PROFILE_ICON))
+ .addInt(
+ passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
+ .addInt(
+ passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
+ .addInt(
+ totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
+ .addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
.build()
return Entry(
key,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index bf0dba2..3a8e975 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -23,6 +23,7 @@
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.getflow.CredentialOptionInfo
import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.jetpack.provider.SaveEntryUi
/** Utility functions for converting CredentialManager data structures to or from UI formats. */
class GetFlowUtils {
@@ -59,8 +60,6 @@
// TODO: remove fallbacks
icon = credentialEntryUi.icon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_passkey)!!,
- title = credentialEntryUi.userName.toString(),
- subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
entryKey = it.key,
entrySubkey = it.subkey,
usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
@@ -82,9 +81,7 @@
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
- // TODO: get the service display name and icon from the component name.
displayName = it.providerFlattenedComponentName,
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
createOptions = toCreationOptionInfoList(it.saveEntries, context),
isDefault = it.isDefaultProvider,
)
@@ -100,13 +97,17 @@
return@map CreateOptionInfo(
// TODO: remove fallbacks
- icon = saveEntryUi.icon?.loadDrawable(context)
- ?: context.getDrawable(R.drawable.ic_passkey)!!,
- title = saveEntryUi.title.toString(),
- subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
entryKey = it.key,
entrySubkey = it.subkey,
- usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
+ userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
+ credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_passkey)!!,
+ profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_profile)!!,
+ passwordCount = saveEntryUi.passwordCount ?: 0,
+ passkeyCount = saveEntryUi.passkeyCount ?: 0,
+ totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
+ lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
deleted file mode 100644
index cd52197..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a save entry used during the create credential flow.
- *
- * TODO: move to jetpack.
- */
-class SaveEntryUi(
- val title: CharSequence,
- val subTitle: CharSequence?,
- val icon: Icon?,
- val usageData: CharSequence?,
- // TODO: add
-) {
- companion object {
- fun fromSlice(slice: Slice): SaveEntryUi {
- val items = slice.items
-
- var title: String? = null
- var subTitle: String? = null
- var icon: Icon? = null
- var usageData: String? = null
-
- items.forEach {
- if (it.hasHint(Entry.HINT_ICON)) {
- icon = it.icon
- } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
- subTitle = it.text.toString()
- } else if (it.hasHint(Entry.HINT_TITLE)) {
- title = it.text.toString()
- } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
- usageData = it.text.toString()
- }
- }
- // TODO: fail NPE more elegantly.
- return SaveEntryUi(title!!, subTitle, icon, usageData)
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index db0f337e..d7e5ee8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -22,20 +22,27 @@
val icon: Drawable,
val name: String,
val displayName: String,
- val credentialTypeIcon: Drawable,
val createOptions: List<CreateOptionInfo>,
val isDefault: Boolean,
)
-data class CreateOptionInfo(
- val icon: Drawable,
- val title: String,
- val subtitle: String,
+open class EntryInfo (
val entryKey: String,
val entrySubkey: String,
- val usageData: String
)
+class CreateOptionInfo(
+ entryKey: String,
+ entrySubkey: String,
+ val userProviderDisplayName: String,
+ val credentialTypeIcon: Drawable,
+ val profileIcon: Drawable,
+ val passwordCount: Int,
+ val passkeyCount: Int,
+ val totalCredentialCount: Int,
+ val lastUsedTimeMillis: Long?,
+) : EntryInfo(entryKey, entrySubkey)
+
data class RequestDisplayInfo(
val userName: String,
val displayName: String,
@@ -49,7 +56,7 @@
*/
data class ActiveEntry (
val activeProvider: ProviderInfo,
- val activeCreateOptionInfo: CreateOptionInfo,
+ val activeEntryInfo: EntryInfo,
)
/** The name of the current screen. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index aeea46a..13a892f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -74,7 +74,7 @@
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerInfo = uiState.activeEntry?.activeProvider!!,
- createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
+ createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
onCancel = viewModel::onCancel,
@@ -408,7 +408,7 @@
) {
Column() {
Icon(
- bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
@@ -436,12 +436,12 @@
text = stringResource(
R.string.choose_create_option_description,
when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
- TYPE_PASSWORD_CREDENTIAL -> "passwords"
- else -> "sign-ins"
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
+ else -> stringResource(R.string.sign_ins)
},
providerInfo.displayName,
- createOptionInfo.title),
+ createOptionInfo.userProviderDisplayName),
style = Typography.body1,
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
@@ -455,8 +455,11 @@
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
item {
- PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
- onOptionSelected = onOptionSelected)
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ createOptionInfo = createOptionInfo,
+ onOptionSelected = onOptionSelected
+ )
}
}
}
@@ -506,12 +509,17 @@
@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
+ createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
Chip(
modifier = Modifier.fillMaxWidth(),
onClick = onOptionSelected,
- // TODO: Add an icon generated by provider according to requestDisplayInfo type
+ leadingIcon = {
+ Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ contentDescription = stringResource(R.string.createOptionInfo_icon_description))
+ },
colors = ChipDefaults.chipColors(
backgroundColor = Grey100,
leadingIconContentColor = Grey100
@@ -545,7 +553,7 @@
onClick = onOptionSelected,
leadingIcon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+ bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
// painter = painterResource(R.drawable.ic_passkey),
// TODO: add description.
contentDescription = "")
@@ -561,14 +569,15 @@
text =
if (providerInfo.createOptions.size > 1)
{stringResource(R.string.more_options_title_multiple_options,
- providerInfo.displayName, createOptionInfo.title)} else {
+ providerInfo.displayName, createOptionInfo.userProviderDisplayName)} else {
stringResource(R.string.more_options_title_one_option,
providerInfo.displayName)},
style = Typography.h6,
modifier = Modifier.padding(top = 16.dp)
)
Text(
- text = createOptionInfo.usageData,
+ text = stringResource(R.string.more_options_usage_data,
+ createOptionInfo.passwordCount, createOptionInfo.passkeyCount),
style = Typography.body2,
modifier = Modifier.padding(bottom = 16.dp)
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 615da4e..2e9758a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -73,21 +73,6 @@
)
}
- fun onCreateOptionSelected(entryKey: String, entrySubkey: String) {
- Log.d(
- "Account Selector",
- "Option selected for creation: {key = $entryKey, subkey = $entrySubkey}"
- )
- CredentialManagerRepo.getInstance().onOptionSelected(
- uiState.activeEntry?.activeProvider!!.name,
- entryKey,
- entrySubkey
- )
- dialogResult.value = DialogResult(
- ResultState.COMPLETE,
- )
- }
-
fun getProviderInfoByName(providerName: String): ProviderInfo {
return uiState.providers.single {
it.name.equals(providerName)
@@ -126,18 +111,18 @@
}
fun onPrimaryCreateOptionInfoSelected() {
- var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
- var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
+ val entryKey = uiState.activeEntry?.activeEntryInfo?.entryKey
+ val entrySubkey = uiState.activeEntry?.activeEntryInfo?.entrySubkey
Log.d(
"Account Selector",
"Option selected for creation: " +
- "{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
+ "{key = $entryKey, subkey = $entrySubkey}"
)
- if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
+ if (entryKey != null && entrySubkey != null) {
CredentialManagerRepo.getInstance().onOptionSelected(
uiState.activeEntry?.activeProvider!!.name,
- createOptionEntryKey,
- createOptionEntrySubkey
+ entryKey,
+ entrySubkey
)
} else {
TODO("Gracefully handle illegal state.")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index e3398c0..8b81083 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -184,12 +184,12 @@
) {
Column() {
Text(
- text = credentialOptionInfo.title,
+ text = credentialOptionInfo.entryKey,
style = Typography.h6,
modifier = Modifier.padding(top = 16.dp)
)
Text(
- text = credentialOptionInfo.subtitle,
+ text = credentialOptionInfo.entrySubkey,
style = Typography.body2,
modifier = Modifier.padding(bottom = 16.dp)
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b6ecd37..b427de6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -24,17 +24,21 @@
val displayName: String,
val credentialTypeIcon: Drawable,
val credentialOptions: List<CredentialOptionInfo>,
+ // TODO: Add the authenticationOption
)
-data class CredentialOptionInfo(
- val icon: Drawable,
- val title: String,
- val subtitle: String,
+open class EntryInfo (
val entryKey: String,
val entrySubkey: String,
- val usageData: String
)
+class CredentialOptionInfo(
+ entryKey: String,
+ entrySubkey: String,
+ val icon: Drawable,
+ val usageData: String,
+) : EntryInfo(entryKey, entrySubkey)
+
data class RequestDisplayInfo(
val userName: String,
val displayName: String,
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 20dd707..e04a9be 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -57,5 +57,5 @@
}
dependencies {
- implementation(project(":spa"))
+ implementation project(":spa")
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 92f4fe4..742e271 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -24,6 +24,7 @@
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
+import com.android.settingslib.spa.gallery.page.ChartPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
@@ -69,6 +70,7 @@
CategoryPageProvider,
ActionButtonPageProvider,
ProgressBarPageProvider,
+ ChartPageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 04d0fe0..83c72c7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -29,6 +29,7 @@
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
+import com.android.settingslib.spa.gallery.page.ChartPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
@@ -56,6 +57,7 @@
CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
new file mode 100644
index 0000000..160e77b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.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.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.BarChartModel
+import com.android.settingslib.spa.widget.chart.LineChart
+import com.android.settingslib.spa.widget.chart.LineChartData
+import com.android.settingslib.spa.widget.chart.LineChartModel
+import com.android.settingslib.spa.widget.chart.PieChart
+import com.android.settingslib.spa.widget.chart.PieChartData
+import com.android.settingslib.spa.widget.chart.PieChartModel
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+import java.text.NumberFormat
+
+private enum class WeekDay(val num: Int) {
+ Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
+}
+private const val TITLE = "Sample Chart"
+
+object ChartPageProvider : SettingsPageProvider {
+ override val name = "Chart"
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = SettingsPage.create(name)
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("Line Chart", owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = "Line Chart"
+ })
+ LineChart(
+ lineChartModel = object : LineChartModel {
+ override val chartDataList = listOf(
+ LineChartData(x = 0f, y = 0f),
+ LineChartData(x = 1f, y = 0.1f),
+ LineChartData(x = 2f, y = 0.2f),
+ LineChartData(x = 3f, y = 0.6f),
+ LineChartData(x = 4f, y = 0.9f),
+ LineChartData(x = 5f, y = 1.0f),
+ LineChartData(x = 6f, y = 0.8f),
+ )
+ override val xValueFormatter =
+ IAxisValueFormatter { value, _ ->
+ "${WeekDay.values()[value.toInt()]}"
+ }
+ override val yValueFormatter =
+ IAxisValueFormatter { value, _ ->
+ NumberFormat.getPercentInstance().format(value)
+ }
+ }
+ )
+ }.build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Bar Chart", owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = "Bar Chart"
+ })
+ BarChart(
+ barChartModel = object : BarChartModel {
+ override val chartDataList = listOf(
+ BarChartData(x = 0f, y = 12f),
+ BarChartData(x = 1f, y = 5f),
+ BarChartData(x = 2f, y = 21f),
+ BarChartData(x = 3f, y = 5f),
+ BarChartData(x = 4f, y = 10f),
+ BarChartData(x = 5f, y = 9f),
+ BarChartData(x = 6f, y = 1f),
+ )
+ override val xValueFormatter =
+ IAxisValueFormatter { value, _ ->
+ "${WeekDay.values()[value.toInt()]}"
+ }
+ override val yValueFormatter =
+ IAxisValueFormatter { value, _ ->
+ "${value.toInt()}m"
+ }
+ override val yAxisMaxValue = 30f
+ }
+ )
+ }.build()
+ )
+ entryList.add(
+ SettingsEntryBuilder.create("Pie Chart", owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = "Pie Chart"
+ })
+ PieChart(
+ pieChartModel = object : PieChartModel {
+ override val chartDataList = listOf(
+ PieChartData(label = "Settings", value = 20f),
+ PieChartData(label = "Chrome", value = 5f),
+ PieChartData(label = "Gmail", value = 3f),
+ )
+ override val centerText = "Today"
+ }
+ )
+ }.build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+ .setIsAllowSearch(true)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(title = TITLE) {
+ for (entry in buildEntry(arguments)) {
+ entry.UiLayout(arguments)
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ChartPagePreview() {
+ SettingsTheme {
+ ChartPageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index 897fa67..cef79c1 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -26,6 +26,7 @@
repositories {
google()
mavenCentral()
+ maven { url "https://jitpack.io"}
}
}
rootProject.name = "SpaLib"
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 8b29366..037b45e 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -33,6 +33,7 @@
"androidx.navigation_navigation-compose",
"com.google.android.material_material",
"lottie_compose",
+ "MPAndroidChart",
],
kotlincflags: [
"-Xjvm-default=all",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 3b159e9..7a20c747 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -60,7 +60,8 @@
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
- api "androidx.navigation:navigation-compose:2.5.0"
+ api "androidx.navigation:navigation-compose:2.6.0-alpha03"
+ api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
implementation "com.airbnb.android:lottie-compose:5.2.0"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index e63e4c9..14b1629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -30,10 +30,14 @@
val injectEntry: SettingsEntry,
)
+private fun SettingsPage.getTitle(sppRepository: SettingsPageProviderRepository): String {
+ return sppRepository.getProviderOrNull(sppName)!!.getTitle(arguments)
+}
+
/**
* The repository to maintain all Settings entries
*/
-class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
+class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRepository) {
// Map of entry unique Id to entry
private val entryMap: Map<String, SettingsEntry>
@@ -118,8 +122,9 @@
return entryPath.map {
if (it.toPage == null)
defaultTitle
- else
- it.toPage.getTitle()!!
+ else {
+ it.toPage.getTitle(sppRepository)
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2fa9229..bb287d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -95,11 +95,6 @@
return false
}
- fun getTitle(): String? {
- val sppRepository by SpaEnvironmentFactory.instance.pageProviderRepository
- return sppRepository.getProviderOrNull(sppName)?.getTitle(arguments)
- }
-
fun enterPage() {
SpaEnvironmentFactory.instance.logger.event(
id,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
index 8e8805a..9479228 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
@@ -21,45 +21,51 @@
import android.annotation.SuppressLint
import android.content.Context
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.font.DeviceFontFamilyName
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
+import com.android.settingslib.spa.framework.compose.rememberContext
internal data class SettingsFontFamily(
- val brand: FontFamily = FontFamily.Default,
- val plain: FontFamily = FontFamily.Default,
+ val brand: FontFamily,
+ val plain: FontFamily,
)
-private fun Context.getSettingsFontFamily(inInspection: Boolean): SettingsFontFamily {
- if (inInspection) {
- return SettingsFontFamily()
- }
+private fun Context.getSettingsFontFamily(): SettingsFontFamily {
return SettingsFontFamily(
- brand = FontFamily(
- Font(getFontFamilyName("config_headlineFontFamily"), FontWeight.Normal),
- Font(getFontFamilyName("config_headlineFontFamilyMedium"), FontWeight.Medium),
+ brand = getFontFamily(
+ configFontFamilyNormal = "config_headlineFontFamily",
+ configFontFamilyMedium = "config_headlineFontFamilyMedium",
),
- plain = FontFamily(
- Font(getFontFamilyName("config_bodyFontFamily"), FontWeight.Normal),
- Font(getFontFamilyName("config_bodyFontFamilyMedium"), FontWeight.Medium),
+ plain = getFontFamily(
+ configFontFamilyNormal = "config_bodyFontFamily",
+ configFontFamilyMedium = "config_bodyFontFamilyMedium",
),
)
}
-private fun Context.getFontFamilyName(configName: String): DeviceFontFamilyName {
+private fun Context.getFontFamily(
+ configFontFamilyNormal: String,
+ configFontFamilyMedium: String,
+): FontFamily {
+ val fontFamilyNormal = getAndroidConfig(configFontFamilyNormal)
+ val fontFamilyMedium = getAndroidConfig(configFontFamilyMedium)
+ if (fontFamilyNormal.isEmpty() || fontFamilyMedium.isEmpty()) return FontFamily.Default
+ return FontFamily(
+ Font(DeviceFontFamilyName(fontFamilyNormal), FontWeight.Normal),
+ Font(DeviceFontFamilyName(fontFamilyMedium), FontWeight.Medium),
+ )
+}
+
+private fun Context.getAndroidConfig(configName: String): String {
@SuppressLint("DiscouragedApi")
val configId = resources.getIdentifier(configName, "string", "android")
- return DeviceFontFamilyName(resources.getString(configId))
+ return resources.getString(configId)
}
@Composable
internal fun rememberSettingsFontFamily(): SettingsFontFamily {
- val context = LocalContext.current
- val inInspection = LocalInspectionMode.current
- return remember { context.getSettingsFontFamily(inInspection) }
+ return rememberContext(Context::getSettingsFontFamily)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
new file mode 100644
index 0000000..0b0f07e
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.settingslib.spa.framework.theme.divider
+import com.github.mikephil.charting.charts.BarChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.BarData
+import com.github.mikephil.charting.data.BarDataSet
+import com.github.mikephil.charting.data.BarEntry
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+/**
+ * The chart settings model for [BarChart].
+ */
+interface BarChartModel {
+ /**
+ * The chart data list for [BarChart].
+ */
+ val chartDataList: List<BarChartData>
+
+ /**
+ * The label text formatter for x value.
+ */
+ val xValueFormatter: IAxisValueFormatter?
+ get() = null
+
+ /**
+ * The label text formatter for y value.
+ */
+ val yValueFormatter: IAxisValueFormatter?
+ get() = null
+
+ /**
+ * The minimum value for y-axis.
+ */
+ val yAxisMinValue: Float
+ get() = 0f
+
+ /**
+ * The maximum value for y-axis.
+ */
+ val yAxisMaxValue: Float
+ get() = 1f
+
+ /**
+ * The label count for y-axis.
+ */
+ val yAxisLabelCount: Int
+ get() = 3
+}
+
+data class BarChartData(
+ var x: Float?,
+ var y: Float?,
+)
+
+@Composable
+fun BarChart(barChartModel: BarChartModel) {
+ BarChart(
+ chartDataList = barChartModel.chartDataList,
+ xValueFormatter = barChartModel.xValueFormatter,
+ yValueFormatter = barChartModel.yValueFormatter,
+ yAxisMinValue = barChartModel.yAxisMinValue,
+ yAxisMaxValue = barChartModel.yAxisMaxValue,
+ yAxisLabelCount = barChartModel.yAxisLabelCount,
+ )
+}
+
+@Composable
+fun BarChart(
+ chartDataList: List<BarChartData>,
+ modifier: Modifier = Modifier,
+ xValueFormatter: IAxisValueFormatter? = null,
+ yValueFormatter: IAxisValueFormatter? = null,
+ yAxisMinValue: Float = 0f,
+ yAxisMaxValue: Float = 30f,
+ yAxisLabelCount: Int = 4,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .height(170.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ val colorScheme = MaterialTheme.colorScheme
+ val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+ val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+ Crossfade(targetState = chartDataList) { barChartData ->
+ AndroidView(factory = { context ->
+ BarChart(context).apply {
+ // Fixed Settings.
+ layoutParams = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ )
+ this.description.isEnabled = false
+ this.legend.isEnabled = false
+ this.extraBottomOffset = 4f
+ this.setScaleEnabled(false)
+
+ this.xAxis.position = XAxis.XAxisPosition.BOTTOM
+ this.xAxis.setDrawGridLines(false)
+ this.xAxis.setDrawAxisLine(false)
+ this.xAxis.textColor = labelTextColor
+ this.xAxis.textSize = labelTextSize
+ this.xAxis.yOffset = 10f
+
+ this.axisLeft.isEnabled = false
+ this.axisRight.setDrawAxisLine(false)
+ this.axisRight.textSize = labelTextSize
+ this.axisRight.textColor = labelTextColor
+ this.axisRight.gridColor = colorScheme.divider.toArgb()
+ this.axisRight.xOffset = 10f
+
+ // Customizable Settings.
+ this.xAxis.valueFormatter = xValueFormatter
+ this.axisRight.valueFormatter = yValueFormatter
+
+ this.axisLeft.axisMinimum = yAxisMinValue
+ this.axisLeft.axisMaximum = yAxisMaxValue
+ this.axisRight.axisMinimum = yAxisMinValue
+ this.axisRight.axisMaximum = yAxisMaxValue
+
+ this.axisRight.setLabelCount(yAxisLabelCount, true)
+ }
+ },
+ modifier = Modifier
+ .wrapContentSize()
+ .padding(4.dp),
+ update = {
+ updateBarChartWithData(it, barChartData, colorScheme)
+ }
+ )
+ }
+ }
+ }
+}
+
+fun updateBarChartWithData(
+ chart: BarChart,
+ data: List<BarChartData>,
+ colorScheme: ColorScheme
+) {
+ val entries = ArrayList<BarEntry>()
+ for (i in data.indices) {
+ val item = data[i]
+ entries.add(BarEntry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+ }
+
+ val ds = BarDataSet(entries, "")
+ ds.colors = arrayListOf(colorScheme.surfaceVariant.toArgb())
+ ds.setDrawValues(false)
+ ds.isHighlightEnabled = true
+ ds.highLightColor = colorScheme.primary.toArgb()
+ ds.highLightAlpha = 255
+ // TODO: Sets round corners for bars.
+
+ val d = BarData(ds)
+ chart.data = d
+ chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
new file mode 100644
index 0000000..70bc017
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.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.settingslib.spa.widget.chart
+
+import androidx.compose.ui.graphics.Color
+
+object ColorPalette {
+ // Alpha = 1
+ val red: Color = Color(0xffd93025)
+ val orange: Color = Color(0xffe8710a)
+ val yellow: Color = Color(0xfff9ab00)
+ val green: Color = Color(0xff1e8e3e)
+ val cyan: Color = Color(0xff12b5cb)
+ val blue: Color = Color(0xff1a73e8)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt
new file mode 100644
index 0000000..7d48265
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.settingslib.spa.framework.theme.divider
+import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.Entry
+import com.github.mikephil.charting.data.LineData
+import com.github.mikephil.charting.data.LineDataSet
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+/**
+ * The chart settings model for [LineChart].
+ */
+interface LineChartModel {
+ /**
+ * The chart data list for [LineChart].
+ */
+ val chartDataList: List<LineChartData>
+
+ /**
+ * The label text formatter for x value.
+ */
+ val xValueFormatter: IAxisValueFormatter?
+ get() = null
+
+ /**
+ * The label text formatter for y value.
+ */
+ val yValueFormatter: IAxisValueFormatter?
+ get() = null
+
+ /**
+ * The minimum value for y-axis.
+ */
+ val yAxisMinValue: Float
+ get() = 0f
+
+ /**
+ * The maximum value for y-axis.
+ */
+ val yAxisMaxValue: Float
+ get() = 1f
+
+ /**
+ * The label count for y-axis.
+ */
+ val yAxisLabelCount: Int
+ get() = 3
+
+ /**
+ * Indicates whether to smooth the line.
+ */
+ val showSmoothLine: Boolean
+ get() = true
+}
+
+data class LineChartData(
+ var x: Float?,
+ var y: Float?,
+)
+
+@Composable
+fun LineChart(lineChartModel: LineChartModel) {
+ LineChart(
+ chartDataList = lineChartModel.chartDataList,
+ xValueFormatter = lineChartModel.xValueFormatter,
+ yValueFormatter = lineChartModel.yValueFormatter,
+ yAxisMinValue = lineChartModel.yAxisMinValue,
+ yAxisMaxValue = lineChartModel.yAxisMaxValue,
+ yAxisLabelCount = lineChartModel.yAxisLabelCount,
+ showSmoothLine = lineChartModel.showSmoothLine,
+ )
+}
+
+@Composable
+fun LineChart(
+ chartDataList: List<LineChartData>,
+ modifier: Modifier = Modifier,
+ xValueFormatter: IAxisValueFormatter? = null,
+ yValueFormatter: IAxisValueFormatter? = null,
+ yAxisMinValue: Float = 0f,
+ yAxisMaxValue: Float = 1f,
+ yAxisLabelCount: Int = 3,
+ showSmoothLine: Boolean = true,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .height(170.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ val colorScheme = MaterialTheme.colorScheme
+ val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+ val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+ Crossfade(targetState = chartDataList) { lineChartData ->
+ AndroidView(factory = { context ->
+ LineChart(context).apply {
+ // Fixed Settings.
+ layoutParams = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ )
+ this.description.isEnabled = false
+ this.legend.isEnabled = false
+ this.extraBottomOffset = 4f
+ this.setTouchEnabled(false)
+
+ this.xAxis.position = XAxis.XAxisPosition.BOTTOM
+ this.xAxis.setDrawGridLines(false)
+ this.xAxis.setDrawAxisLine(false)
+ this.xAxis.textColor = labelTextColor
+ this.xAxis.textSize = labelTextSize
+ this.xAxis.yOffset = 10f
+
+ this.axisLeft.isEnabled = false
+
+ this.axisRight.setDrawAxisLine(false)
+ this.axisRight.textSize = labelTextSize
+ this.axisRight.textColor = labelTextColor
+ this.axisRight.gridColor = colorScheme.divider.toArgb()
+ this.axisRight.xOffset = 10f
+ this.axisRight.isGranularityEnabled = true
+
+ // Customizable Settings.
+ this.xAxis.valueFormatter = xValueFormatter
+ this.axisRight.valueFormatter = yValueFormatter
+
+ this.axisLeft.axisMinimum =
+ yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue)
+ this.axisRight.axisMinimum =
+ yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue)
+ this.axisRight.granularity =
+ (yAxisMaxValue - yAxisMinValue) / (yAxisLabelCount - 1)
+ }
+ },
+ modifier = Modifier
+ .wrapContentSize()
+ .padding(4.dp),
+ update = {
+ updateLineChartWithData(it, lineChartData, colorScheme, showSmoothLine)
+ })
+ }
+ }
+ }
+}
+
+fun updateLineChartWithData(
+ chart: LineChart,
+ data: List<LineChartData>,
+ colorScheme: ColorScheme,
+ showSmoothLine: Boolean
+) {
+ val entries = ArrayList<Entry>()
+ for (i in data.indices) {
+ val item = data[i]
+ entries.add(Entry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+ }
+
+ val ds = LineDataSet(entries, "")
+ ds.colors = arrayListOf(colorScheme.primary.toArgb())
+ ds.lineWidth = 2f
+ if (showSmoothLine) {
+ ds.mode = LineDataSet.Mode.CUBIC_BEZIER
+ }
+ ds.setDrawValues(false)
+ ds.setDrawCircles(false)
+ ds.setDrawFilled(true)
+ ds.fillColor = colorScheme.primary.toArgb()
+ ds.fillAlpha = 38
+ // TODO: enable gradient fill color for line chart.
+
+ val d = LineData(ds)
+ chart.data = d
+ chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt
new file mode 100644
index 0000000..51a8d0d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.github.mikephil.charting.charts.PieChart
+import com.github.mikephil.charting.data.PieData
+import com.github.mikephil.charting.data.PieDataSet
+import com.github.mikephil.charting.data.PieEntry
+
+/**
+ * The chart settings model for [PieChart].
+ */
+interface PieChartModel {
+ /**
+ * The chart data list for [PieChart].
+ */
+ val chartDataList: List<PieChartData>
+
+ /**
+ * The center text in the hole of [PieChart].
+ */
+ val centerText: String?
+ get() = null
+}
+
+val colorPalette = arrayListOf(
+ ColorPalette.blue.toArgb(),
+ ColorPalette.red.toArgb(),
+ ColorPalette.yellow.toArgb(),
+ ColorPalette.green.toArgb(),
+ ColorPalette.orange.toArgb(),
+ ColorPalette.cyan.toArgb(),
+ Color.Blue.toArgb()
+)
+
+data class PieChartData(
+ var value: Float?,
+ var label: String?,
+)
+
+@Composable
+fun PieChart(pieChartModel: PieChartModel) {
+ PieChart(
+ chartDataList = pieChartModel.chartDataList,
+ centerText = pieChartModel.centerText,
+ )
+}
+
+@Composable
+fun PieChart(
+ chartDataList: List<PieChartData>,
+ modifier: Modifier = Modifier,
+ centerText: String? = null,
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .height(280.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ val colorScheme = MaterialTheme.colorScheme
+ val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+ val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+ val centerTextSize = MaterialTheme.typography.titleLarge.fontSize.value
+ Crossfade(targetState = chartDataList) { pieChartData ->
+ AndroidView(factory = { context ->
+ PieChart(context).apply {
+ // Fixed settings.`
+ layoutParams = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ )
+ this.isRotationEnabled = false
+ this.description.isEnabled = false
+ this.legend.isEnabled = false
+ this.setTouchEnabled(false)
+
+ this.isDrawHoleEnabled = true
+ this.holeRadius = 90.0f
+ this.setHoleColor(Color.Transparent.toArgb())
+ this.setEntryLabelColor(labelTextColor)
+ this.setEntryLabelTextSize(labelTextSize)
+ this.setCenterTextSize(centerTextSize)
+ this.setCenterTextColor(colorScheme.onSurface.toArgb())
+
+ // Customizable settings.
+ this.centerText = centerText
+ }
+ },
+ modifier = Modifier
+ .wrapContentSize()
+ .padding(4.dp), update = {
+ updatePieChartWithData(it, pieChartData)
+ })
+ }
+ }
+ }
+}
+
+fun updatePieChartWithData(
+ chart: PieChart,
+ data: List<PieChartData>,
+) {
+ val entries = ArrayList<PieEntry>()
+ for (i in data.indices) {
+ val item = data[i]
+ entries.add(PieEntry(item.value ?: 0.toFloat(), item.label ?: ""))
+ }
+
+ val ds = PieDataSet(entries, "")
+ ds.setDrawValues(false)
+ ds.colors = colorPalette
+ ds.sliceSpace = 2f
+ ds.yValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE
+ ds.xValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE
+ ds.valueLineColor = Color.Transparent.toArgb()
+ ds.valueLinePart1Length = 0.1f
+ ds.valueLinePart2Length = 0f
+
+ val d = PieData(ds)
+ chart.data = d
+ chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index 7491045..2449dec 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -31,6 +31,7 @@
"androidx.compose.runtime_runtime",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
+ "mockito-target-minus-junit4",
"truth-prebuilt",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 4b4c6a3..5261091 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -58,9 +58,10 @@
}
dependencies {
- androidTestImplementation(project(":spa"))
- androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
- androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
- androidTestImplementation 'com.google.truth:truth:1.1.3'
+ androidTestImplementation project(":spa")
+ androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"
+ androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
+ androidTestImplementation "com.google.truth:truth:1.1.3"
+ androidTestImplementation "org.mockito:mockito-android:3.4.6"
androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
new file mode 100644
index 0000000..9419161
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryRepositoryTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment = SpaEnvironmentForTest(context)
+ private val entryRepository by spaEnvironment.entryRepository
+
+ @Test
+ fun testGetPageWithEntry() {
+ val pageWithEntry = entryRepository.getAllPageWithEntry()
+ assertThat(pageWithEntry.size).isEqualTo(3)
+ assertThat(
+ entryRepository.getPageWithEntry(getUniquePageId("SppHome"))
+ ?.entries?.size
+ ).isEqualTo(1)
+ assertThat(
+ entryRepository.getPageWithEntry(getUniquePageId("SppLayer1"))
+ ?.entries?.size
+ ).isEqualTo(3)
+ assertThat(
+ entryRepository.getPageWithEntry(getUniquePageId("SppLayer2"))
+ ?.entries?.size
+ ).isEqualTo(2)
+ assertThat(entryRepository.getPageWithEntry(getUniquePageId("SppWithParam"))).isNull()
+ }
+
+ @Test
+ fun testGetEntry() {
+ val entry = entryRepository.getAllEntries()
+ assertThat(entry.size).isEqualTo(7)
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId(
+ "ROOT",
+ SppHome.createSettingsPage(),
+ SettingsPage.createNull(),
+ SppHome.createSettingsPage(),
+ )
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer1.createSettingsPage(),
+ SppHome.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ )
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer2.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ SppLayer2.createSettingsPage(),
+ )
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
+ )
+ ).isNotNull()
+ }
+
+ @Test
+ fun testGetEntryPath() {
+ assertThat(
+ entryRepository.getEntryPathWithDisplayName(
+ getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ )
+ ).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
+ .inOrder()
+
+ assertThat(
+ entryRepository.getEntryPathWithTitle(
+ getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
+ "entryTitle"
+ )
+ ).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+
+ assertThat(
+ entryRepository.getEntryPathWithDisplayName(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer1.createSettingsPage(),
+ SppHome.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ )
+ )
+ ).containsExactly("INJECT_SppLayer1", "ROOT_SppHome").inOrder()
+
+ assertThat(
+ entryRepository.getEntryPathWithTitle(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer2.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ SppLayer2.createSettingsPage(),
+ ),
+ "defaultTitle"
+ )
+ ).containsExactly("SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
new file mode 100644
index 0000000..31d2ae4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.core.os.bundleOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val INJECT_ENTRY_NAME = "INJECT"
+const val ROOT_ENTRY_NAME = "ROOT"
+
+class MacroForTest(private val pageId: String, private val entryId: String) : EntryMacro {
+ @Composable
+ override fun UiLayout() {
+ val entryData = LocalEntryDataProvider.current
+ assertThat(entryData.isHighlighted).isFalse()
+ assertThat(entryData.pageId).isEqualTo(pageId)
+ assertThat(entryData.entryId).isEqualTo(entryId)
+ }
+
+ override fun getSearchData(): EntrySearchData {
+ return EntrySearchData("myTitle")
+ }
+
+ override fun getStatusData(): EntryStatusData {
+ return EntryStatusData(isDisabled = true, isSwitchOff = true)
+ }
+}
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testBuildBasic() {
+ val owner = SettingsPage.create("mySpp")
+ val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
+ assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.displayName).isEqualTo("myEntry")
+ assertThat(entry.owner.sppName).isEqualTo("mySpp")
+ assertThat(entry.owner.displayName).isEqualTo("mySpp")
+ assertThat(entry.fromPage).isNull()
+ assertThat(entry.toPage).isNull()
+ assertThat(entry.isAllowSearch).isFalse()
+ assertThat(entry.isSearchDataDynamic).isFalse()
+ assertThat(entry.mutableStatus).isFalse()
+ }
+
+ @Test
+ fun testBuildWithLink() {
+ val owner = SettingsPage.create("mySpp")
+ val fromPage = SettingsPage.create("fromSpp")
+ val toPage = SettingsPage.create("toSpp")
+ val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
+ .setLink(toPage = toPage).build()
+ assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
+ assertThat(entryFrom.displayName).isEqualTo("myEntry")
+ assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
+ assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
+
+ val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
+ .setLink(fromPage = fromPage).build()
+ assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
+ assertThat(entryTo.displayName).isEqualTo("myEntry")
+ assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
+ assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
+ }
+
+ @Test
+ fun testBuildInject() {
+ val owner = SettingsPage.create("mySpp")
+ val entryInject = SettingsEntryBuilder.createInject(owner).build()
+ assertThat(entryInject.id).isEqualTo(
+ getUniqueEntryId(
+ INJECT_ENTRY_NAME,
+ owner,
+ toPage = owner
+ )
+ )
+ assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME}_mySpp")
+ assertThat(entryInject.fromPage).isNull()
+ assertThat(entryInject.toPage).isNotNull()
+ }
+
+ @Test
+ fun testBuildRoot() {
+ val owner = SettingsPage.create("mySpp")
+ val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
+ assertThat(entryInject.id).isEqualTo(
+ getUniqueEntryId(
+ ROOT_ENTRY_NAME,
+ owner,
+ toPage = owner
+ )
+ )
+ assertThat(entryInject.displayName).isEqualTo("myRootEntry")
+ assertThat(entryInject.fromPage).isNull()
+ assertThat(entryInject.toPage).isNotNull()
+ }
+
+ @Test
+ fun testSetAttributes() {
+ val owner = SettingsPage.create("mySpp")
+ val entry = SettingsEntryBuilder.create(owner, "myEntry")
+ .setDisplayName("myEntryDisplay")
+ .setIsAllowSearch(true)
+ .setIsSearchDataDynamic(false)
+ .setHasMutableStatus(true)
+ .build()
+ assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.displayName).isEqualTo("myEntryDisplay")
+ assertThat(entry.fromPage).isNull()
+ assertThat(entry.toPage).isNull()
+ assertThat(entry.isAllowSearch).isTrue()
+ assertThat(entry.isSearchDataDynamic).isFalse()
+ assertThat(entry.mutableStatus).isTrue()
+ }
+
+ @Test
+ fun testSetMarco() {
+ val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
+ val entry = SettingsEntryBuilder.create(owner, "myEntry")
+ .setMacro {
+ assertThat(it?.getString("param")).isEqualTo("v1")
+ assertThat(it?.getString("rtParam")).isEqualTo("v2")
+ assertThat(it?.getString("unknown")).isNull()
+ MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
+ }
+ .build()
+
+ val rtArguments = bundleOf("rtParam" to "v2")
+ composeTestRule.setContent { entry.UiLayout(rtArguments) }
+ val searchData = entry.getSearchData(rtArguments)
+ val statusData = entry.getStatusData(rtArguments)
+ assertThat(searchData?.title).isEqualTo("myTitle")
+ assertThat(searchData?.keyword).isEmpty()
+ assertThat(statusData?.isDisabled).isTrue()
+ assertThat(statusData?.isSwitchOff).isTrue()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
new file mode 100644
index 0000000..6c0c652
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageProviderRepositoryTest {
+ @Test
+ fun getStartPageTest() {
+ val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+ assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
+ assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
+
+ val sppRepoNull =
+ SettingsPageProviderRepository(emptyList(), listOf(SettingsPage.createNull()))
+ assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
+ assertThat(sppRepoNull.getAllRootPages()).contains(SettingsPage.createNull())
+
+ val rootPage1 = SettingsPage.create(name = "Spp1", displayName = "Spp1")
+ val rootPage2 = SettingsPage.create(name = "Spp2", displayName = "Spp2")
+ val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
+ val allRoots = sppRepo.getAllRootPages()
+ assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
+ assertThat(allRoots.size).isEqualTo(2)
+ assertThat(allRoots).contains(rootPage1)
+ assertThat(allRoots).contains(rootPage2)
+ }
+
+ @Test
+ fun getProviderTest() {
+ val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+ assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
+ assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
+
+ val sppRepo = SettingsPageProviderRepository(listOf(
+ object : SettingsPageProvider {
+ override val name = "Spp"
+ }
+ ), emptyList())
+ assertThat(sppRepo.getAllProviders().size).isEqualTo(1)
+ assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
+ assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
new file mode 100644
index 0000000..539e56b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaLogger = SpaLoggerForTest()
+ private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+
+ @Test
+ fun testNullPage() {
+ val page = SettingsPage.createNull()
+ assertThat(page.id).isEqualTo(getUniquePageId("NULL"))
+ assertThat(page.sppName).isEqualTo("NULL")
+ assertThat(page.displayName).isEqualTo("NULL")
+ assertThat(page.buildRoute()).isEqualTo("NULL")
+ assertThat(page.isCreateBy("NULL")).isTrue()
+ assertThat(page.isCreateBy("Spp")).isFalse()
+ assertThat(page.hasRuntimeParam()).isFalse()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+ }
+
+ @Test
+ fun testRegularPage() {
+ val page = SettingsPage.create("mySpp", "SppDisplayName")
+ assertThat(page.id).isEqualTo(getUniquePageId("mySpp"))
+ assertThat(page.sppName).isEqualTo("mySpp")
+ assertThat(page.displayName).isEqualTo("SppDisplayName")
+ assertThat(page.buildRoute()).isEqualTo("mySpp")
+ assertThat(page.isCreateBy("NULL")).isFalse()
+ assertThat(page.isCreateBy("mySpp")).isTrue()
+ assertThat(page.hasRuntimeParam()).isFalse()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+ "-e spaActivityDestination mySpp"
+ )
+ }
+
+ @Test
+ fun testParamPage() {
+ val arguments = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ )
+ val page = spaEnvironment.createPage("SppWithParam", arguments)
+ assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ ), arguments))
+ assertThat(page.sppName).isEqualTo("SppWithParam")
+ assertThat(page.displayName).isEqualTo("SppWithParam")
+ assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
+ assertThat(page.isCreateBy("SppWithParam")).isTrue()
+ assertThat(page.hasRuntimeParam()).isFalse()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+ "-e spaActivityDestination SppWithParam/myStr/10"
+ )
+ }
+
+ @Test
+ fun testRtParamPage() {
+ val arguments = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ "rt_param" to "rtStr",
+ )
+ val page = spaEnvironment.createPage("SppWithRtParam", arguments)
+ assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ ), arguments))
+ assertThat(page.sppName).isEqualTo("SppWithRtParam")
+ assertThat(page.displayName).isEqualTo("SppWithRtParam")
+ assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
+ assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
+ assertThat(page.hasRuntimeParam()).isTrue()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+ }
+
+ @Test
+ fun testPageEvent() {
+ spaLogger.reset()
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val page = spaEnvironment.createPage("SppHome")
+ page.enterPage()
+ page.leavePage()
+ page.enterPage()
+ assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+ .isEqualTo(2)
+ assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+ .isEqualTo(1)
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
new file mode 100644
index 0000000..b8731a3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.BrowseActivity
+
+class SpaLoggerForTest : SpaLogger {
+ data class MsgCountKey(val msg: String, val category: LogCategory)
+ data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
+
+ private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
+ private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
+
+ override fun message(tag: String, msg: String, category: LogCategory) {
+ val key = MsgCountKey("[$tag]$msg", category)
+ messageCount[key] = messageCount.getOrDefault(key, 0) + 1
+ }
+
+ override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+ val key = EventCountKey(id, event, category)
+ eventCount[key] = eventCount.getOrDefault(key, 0) + 1
+ }
+
+ fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
+ val key = MsgCountKey("[$tag]$msg", category)
+ return messageCount.getOrDefault(key, 0)
+ }
+
+ fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
+ val key = EventCountKey(id, event, category)
+ return eventCount.getOrDefault(key, 0)
+ }
+
+ fun reset() {
+ messageCount.clear()
+ eventCount.clear()
+ }
+}
+
+class MockActivity : BrowseActivity()
+
+object SppHome : SettingsPageProvider {
+ override val name = "SppHome"
+
+ override fun getTitle(arguments: Bundle?): String {
+ return "TitleHome"
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SppLayer1.buildInject().setLink(fromPage = owner).build(),
+ )
+ }
+}
+
+object SppLayer1 : SettingsPageProvider {
+ override val name = "SppLayer1"
+
+ override fun getTitle(arguments: Bundle?): String {
+ return "TitleLayer1"
+ }
+
+ fun buildInject(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(this.createSettingsPage())
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
+ SppLayer2.buildInject().setLink(fromPage = owner).build(),
+ SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
+ )
+ }
+}
+
+object SppLayer2 : SettingsPageProvider {
+ override val name = "SppLayer2"
+
+ fun buildInject(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(this.createSettingsPage())
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SettingsEntryBuilder.create(owner, "Layer2Entry1").build(),
+ SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
+ )
+ }
+}
+
+class SpaEnvironmentForTest(
+ context: Context,
+ override val browseActivityClass: Class<out Activity>? = MockActivity::class.java,
+ override val logger: SpaLogger = SpaLoggerForTest()
+) : SpaEnvironment(context) {
+
+ override val pageProviderRepository = lazy {
+ SettingsPageProviderRepository(
+ listOf(
+ SppHome, SppLayer1, SppLayer2,
+ object : SettingsPageProvider {
+ override val name = "SppWithParam"
+ override val parameter = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ )
+ },
+ object : SettingsPageProvider {
+ override val name = "SppWithRtParam"
+ override val parameter = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ )
+ },
+ ),
+ listOf(SettingsPage.create("SppHome"))
+ )
+ }
+
+ fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
+ return pageProviderRepository.value
+ .getProviderOrNull(sppName)!!.createSettingsPage(arguments)
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
new file mode 100644
index 0000000..93f9afe
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.normalize
+
+fun getUniquePageId(
+ name: String,
+ parameter: List<NamedNavArgument> = emptyList(),
+ arguments: Bundle? = null
+): String {
+ val normArguments = parameter.normalize(arguments)
+ return "$name:${normArguments?.toString()}".toHashId()
+}
+
+fun getUniquePageId(page: SettingsPage): String {
+ return getUniquePageId(page.sppName, page.parameter, page.arguments)
+}
+
+fun getUniqueEntryId(
+ name: String,
+ owner: SettingsPage,
+ fromPage: SettingsPage? = null,
+ toPage: SettingsPage? = null
+): String {
+ val ownerId = getUniquePageId(owner)
+ val fromId = if (fromPage == null) "null" else getUniquePageId(fromPage)
+ val toId = if (toPage == null) "null" else getUniquePageId(toPage)
+ return "$name:$ownerId($fromId-$toId)".toHashId()
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
new file mode 100644
index 0000000..2ff3039
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import android.content.Context
+import android.content.res.Resources
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.font.FontFamily
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class SettingsThemeTest {
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Mock
+ private lateinit var context: Context
+
+ @Mock
+ private lateinit var resources: Resources
+
+ private var nextMockResId = 1
+
+ @Before
+ fun setUp() {
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getString(anyInt())).thenReturn("")
+ }
+
+ private fun mockAndroidConfig(configName: String, configValue: String) {
+ whenever(resources.getIdentifier(configName, "string", "android"))
+ .thenReturn(nextMockResId)
+ whenever(resources.getString(nextMockResId)).thenReturn(configValue)
+ nextMockResId++
+ }
+
+ @Test
+ fun noFontFamilyConfig_useDefaultFontFamily() {
+ val fontFamily = getFontFamily()
+
+ assertThat(fontFamily.headlineLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
+ assertThat(fontFamily.bodyLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
+ }
+
+ @Test
+ fun hasFontFamilyConfig_useConfiguredFontFamily() {
+ mockAndroidConfig("config_headlineFontFamily", "HeadlineNormal")
+ mockAndroidConfig("config_headlineFontFamilyMedium", "HeadlineMedium")
+ mockAndroidConfig("config_bodyFontFamily", "BodyNormal")
+ mockAndroidConfig("config_bodyFontFamilyMedium", "BodyMedium")
+
+ val fontFamily = getFontFamily()
+
+ val headlineFontFamily = fontFamily.headlineLarge.fontFamily.toString()
+ assertThat(headlineFontFamily).contains("HeadlineNormal")
+ assertThat(headlineFontFamily).contains("HeadlineMedium")
+ val bodyFontFamily = fontFamily.bodyLarge.fontFamily.toString()
+ assertThat(bodyFontFamily).contains("BodyNormal")
+ assertThat(bodyFontFamily).contains("BodyMedium")
+ }
+
+ private fun getFontFamily(): Typography {
+ lateinit var typography: Typography
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ SettingsTheme {
+ typography = MaterialTheme.typography
+ }
+ }
+ }
+ return typography
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
index 21ff085..48ebd8d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -20,9 +20,12 @@
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavType
import androidx.navigation.navArgument
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Test
+import org.junit.runner.RunWith
+@RunWith(AndroidJUnit4::class)
class ParameterTest {
@Test
fun navRouteTest() {
@@ -88,14 +91,14 @@
"Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
)
- val setParialParam = navArguments.normalize(
+ val setPartialParam = navArguments.normalize(
bundleOf(
"string_param" to "myStr",
"rt_param" to "rtStr",
)
)
- assertThat(setParialParam).isNotNull()
- assertThat(setParialParam.toString()).isEqualTo(
+ assertThat(setPartialParam).isNotNull()
+ assertThat(setPartialParam.toString()).isEqualTo(
"Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
new file mode 100644
index 0000000..fa7a98a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.SemanticsPropertyKey
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.LineChart
+import com.android.settingslib.spa.widget.chart.LineChartData
+import com.android.settingslib.spa.widget.chart.PieChart
+import com.android.settingslib.spa.widget.chart.PieChartData
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ChartTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val Chart = SemanticsPropertyKey<String>("Chart")
+ private var SemanticsPropertyReceiver.chart by Chart
+ private fun hasChart(chart: String): SemanticsMatcher =
+ SemanticsMatcher.expectValue(Chart, chart)
+
+ @Test
+ fun line_chart_displayed() {
+ composeTestRule.setContent {
+ LineChart(
+ chartDataList = listOf(
+ LineChartData(x = 0f, y = 0f),
+ LineChartData(x = 1f, y = 0.1f),
+ LineChartData(x = 2f, y = 0.2f),
+ LineChartData(x = 3f, y = 0.7f),
+ LineChartData(x = 4f, y = 0.9f),
+ LineChartData(x = 5f, y = 1.0f),
+ LineChartData(x = 6f, y = 0.8f),
+ ),
+ modifier = Modifier.semantics { chart = "line" }
+ )
+ }
+
+ composeTestRule.onNode(hasChart("line")).assertIsDisplayed()
+ }
+
+ @Test
+ fun bar_chart_displayed() {
+ composeTestRule.setContent {
+ BarChart(
+ chartDataList = listOf(
+ BarChartData(x = 0f, y = 12f),
+ BarChartData(x = 1f, y = 5f),
+ BarChartData(x = 2f, y = 21f),
+ BarChartData(x = 3f, y = 5f),
+ BarChartData(x = 4f, y = 10f),
+ BarChartData(x = 5f, y = 9f),
+ BarChartData(x = 6f, y = 1f),
+ ),
+ yAxisMaxValue = 30f,
+ modifier = Modifier.semantics { chart = "bar" }
+ )
+ }
+
+ composeTestRule.onNode(hasChart("bar")).assertIsDisplayed()
+ }
+
+ @Test
+ fun pie_chart_displayed() {
+ composeTestRule.setContent {
+ PieChart(
+ chartDataList = listOf(
+ PieChartData(label = "Settings", value = 20f),
+ PieChartData(label = "Chrome", value = 5f),
+ PieChartData(label = "Gmail", value = 3f),
+ ),
+ centerText = "Today",
+ modifier = Modifier.semantics { chart = "pie" }
+ )
+ }
+
+ composeTestRule.onNode(hasChart("pie")).assertIsDisplayed()
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 3cd8378..9d6b311 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -41,6 +41,13 @@
import kotlinx.coroutines.Dispatchers
private const val TAG = "AppList"
+private const val CONTENT_TYPE_HEADER = "header"
+
+internal data class AppListState(
+ val showSystem: State<Boolean>,
+ val option: State<Int>,
+ val searchQuery: State<String>,
+)
/**
* The template to render an App List.
@@ -49,23 +56,26 @@
*/
@Composable
internal fun <T : AppRecord> AppList(
- appListConfig: AppListConfig,
+ config: AppListConfig,
listModel: AppListModel<T>,
- showSystem: State<Boolean>,
- option: State<Int>,
- searchQuery: State<String>,
+ state: AppListState,
+ header: @Composable () -> Unit,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
bottomPadding: Dp,
+ appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
+ loadAppListData(config, listModel, state)
+ },
) {
- LogCompositions(TAG, appListConfig.userId.toString())
- val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
- AppListWidget(appListData, listModel, appItem, bottomPadding)
+ LogCompositions(TAG, config.userId.toString())
+ val appListData = appListDataSupplier()
+ AppListWidget(appListData, listModel, header, appItem, bottomPadding)
}
@Composable
private fun <T : AppRecord> AppListWidget(
appListData: State<AppListData<T>?>,
listModel: AppListModel<T>,
+ header: @Composable () -> Unit,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
bottomPadding: Dp,
) {
@@ -81,6 +91,10 @@
state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
contentPadding = PaddingValues(bottom = bottomPadding),
) {
+ item(contentType = CONTENT_TYPE_HEADER) {
+ header()
+ }
+
items(count = list.size, key = { option to list[it].record.app.packageName }) {
val appEntry = list[it]
val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
@@ -94,19 +108,17 @@
}
@Composable
-private fun <T : AppRecord> loadAppEntries(
- appListConfig: AppListConfig,
+private fun <T : AppRecord> loadAppListData(
+ config: AppListConfig,
listModel: AppListModel<T>,
- showSystem: State<Boolean>,
- option: State<Int>,
- searchQuery: State<String>,
+ state: AppListState,
): State<AppListData<T>?> {
- val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
- viewModel.appListConfig.setIfAbsent(appListConfig)
+ val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+ viewModel.appListConfig.setIfAbsent(config)
viewModel.listModel.setIfAbsent(listModel)
- viewModel.showSystem.Sync(showSystem)
- viewModel.option.Sync(option)
- viewModel.searchQuery.Sync(searchQuery)
+ viewModel.showSystem.Sync(state.showSystem)
+ viewModel.option.Sync(state.option)
+ viewModel.searchQuery.Sync(state.searchQuery)
return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2953367..388a7d8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -37,6 +37,8 @@
/**
* The full screen template for an App List page.
+ *
+ * @param header the description header appears before all the applications.
*/
@Composable
fun <T : AppRecord> AppListPage(
@@ -44,6 +46,7 @@
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
primaryUserOnly: Boolean = false,
+ header: @Composable () -> Unit = {},
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
val showSystem = rememberSaveable { mutableStateOf(false) }
@@ -59,14 +62,17 @@
val selectedOption = rememberSaveable { mutableStateOf(0) }
Spinner(options, selectedOption.value) { selectedOption.value = it }
AppList(
- appListConfig = AppListConfig(
+ config = AppListConfig(
userId = userInfo.id,
showInstantApps = showInstantApps,
),
listModel = listModel,
- showSystem = showSystem,
- option = selectedOption,
- searchQuery = searchQuery,
+ state = AppListState(
+ showSystem = showSystem,
+ option = selectedOption,
+ searchQuery = searchQuery,
+ ),
+ header = header,
appItem = appItem,
bottomPadding = bottomPadding,
)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
new file mode 100644
index 0000000..80c4eac
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
+import com.android.settingslib.spaprivileged.model.app.AppListData
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun whenNoApps() {
+ setContent(appEntries = emptyList())
+
+ composeTestRule.onNodeWithText(context.getString(R.string.no_applications))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun couldShowAppItem() {
+ setContent(appEntries = listOf(APP_ENTRY))
+
+ composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+ }
+
+ @Test
+ fun couldShowHeader() {
+ setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+
+ composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+ }
+
+ private fun setContent(
+ header: @Composable () -> Unit = {},
+ appEntries: List<AppEntry<TestAppRecord>>,
+ ) {
+ composeTestRule.setContent {
+ AppList(
+ config = AppListConfig(userId = USER_ID, showInstantApps = false),
+ listModel = TestAppListModel(),
+ state = AppListState(
+ showSystem = false.toState(),
+ option = 0.toState(),
+ searchQuery = "".toState(),
+ ),
+ header = header,
+ appItem = { AppListItem(it) {} },
+ bottomPadding = 0.dp,
+ appListDataSupplier = {
+ stateOf(AppListData(appEntries, option = 0))
+ }
+ )
+ }
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val HEADER = "Header"
+ val APP_ENTRY = AppEntry(
+ record = TestAppRecord(ApplicationInfo()),
+ label = "AAA",
+ labelCollationKey = CollationKey("", byteArrayOf()),
+ )
+ }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.asyncMapItem { TestAppRecord(it) }
+
+ @Composable
+ override fun getSummary(option: Int, record: TestAppRecord) = null
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<TestAppRecord>>,
+ ) = recordListFlow
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 6bc1160..dd56bde 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -188,7 +188,6 @@
/**
* Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
* Hearing Aid Service is connected and the HiSyncId's are now available.
- * @param LocalBluetoothProfileManager profileManager
*/
public synchronized void updateHearingAidsDevices() {
mHearingAidDeviceManager.updateHearingAidsDevices();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
new file mode 100644
index 0000000..f06aab3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HapClientProfile handles the Bluetooth HAP service client role.
+ */
+public class HapClientProfile implements LocalBluetoothProfile {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ HearingAidType.TYPE_INVALID,
+ HearingAidType.TYPE_BINAURAL,
+ HearingAidType.TYPE_MONAURAL,
+ HearingAidType.TYPE_BANDED,
+ HearingAidType.TYPE_RFU
+ })
+
+ /** Hearing aid type definition for HAP Client. */
+ public @interface HearingAidType {
+ int TYPE_INVALID = -1;
+ int TYPE_BINAURAL = BluetoothHapClient.TYPE_BINAURAL;
+ int TYPE_MONAURAL = BluetoothHapClient.TYPE_MONAURAL;
+ int TYPE_BANDED = BluetoothHapClient.TYPE_BANDED;
+ int TYPE_RFU = BluetoothHapClient.TYPE_RFU;
+ }
+
+ static final String NAME = "HapClient";
+ private static final String TAG = "HapClientProfile";
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 1;
+
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+ private BluetoothHapClient mService;
+ private boolean mIsProfileReady;
+
+ // These callbacks run on the main thread.
+ private final class HapClientServiceListener implements BluetoothProfile.ServiceListener {
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ mService = (BluetoothHapClient) proxy;
+ // We just bound to the service, so refresh the UI for any connected HapClient devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // Adds a new device into mDeviceManager if it does not exist
+ if (device == null) {
+ Log.w(TAG, "HapClient profile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(
+ HapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mIsProfileReady = true;
+ mProfileManager.callServiceConnectedListeners();
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ mIsProfileReady = false;
+ mProfileManager.callServiceDisconnectedListeners();
+ }
+ }
+
+ HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+ if (bluetoothManager != null) {
+ mBluetoothAdapter = bluetoothManager.getAdapter();
+ mBluetoothAdapter.getProfileProxy(context, new HapClientServiceListener(),
+ BluetoothProfile.HAP_CLIENT);
+ } else {
+ mBluetoothAdapter = null;
+ }
+ }
+
+ /**
+ * Get hearing aid devices matching connection states{
+ * {@code BluetoothProfile.STATE_CONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTING},
+ * {@code BluetoothProfile.STATE_DISCONNECTING}}
+ *
+ * @return Matching device list
+ */
+ public List<BluetoothDevice> getConnectedDevices() {
+ return getDevicesByStates(new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ /**
+ * Get hearing aid devices matching connection states{
+ * {@code BluetoothProfile.STATE_DISCONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTING},
+ * {@code BluetoothProfile.STATE_DISCONNECTING}}
+ *
+ * @return Matching device list
+ */
+ public List<BluetoothDevice> getConnectableDevices() {
+ return getDevicesByStates(new int[] {
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ private List<BluetoothDevice> getDevicesByStates(int[] states) {
+ if (mService == null) {
+ return new ArrayList<>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(states);
+ }
+
+ /**
+ * Gets the hearing aid type of the device.
+ *
+ * @param device is the device for which we want to get the hearing aid type
+ * @return hearing aid type
+ */
+ @HearingAidType
+ public int getHearingAidType(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return HearingAidType.TYPE_INVALID;
+ }
+ return mService.getHearingAidType(device);
+ }
+
+ /**
+ * Gets if this device supports synchronized presets or not
+ *
+ * @param device is the device for which we want to know if supports synchronized presets
+ * @return {@code true} if the device supports synchronized presets
+ */
+ public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportSynchronizedPresets(device);
+ }
+
+ /**
+ * Gets if this device supports independent presets or not
+ *
+ * @param device is the device for which we want to know if supports independent presets
+ * @return {@code true} if the device supports independent presets
+ */
+ public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportIndependentPresets(device);
+ }
+
+ /**
+ * Gets if this device supports dynamic presets or not
+ *
+ * @param device is the device for which we want to know if supports dynamic presets
+ * @return {@code true} if the device supports dynamic presets
+ */
+ public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportDynamicPresets(device);
+ }
+
+ /**
+ * Gets if this device supports writable presets or not
+ *
+ * @param device is the device for which we want to know if supports writable presets
+ * @return {@code true} if the device supports writable presets
+ */
+ public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportWritablePresets(device);
+ }
+
+ @Override
+ public boolean accessProfileEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ @Override
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
+ }
+
+ @Override
+ public boolean isEnabled(BluetoothDevice device) {
+ if (mService == null || device == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
+ }
+
+ @Override
+ public int getConnectionPolicy(BluetoothDevice device) {
+ if (mService == null || device == null) {
+ return CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
+ }
+
+ @Override
+ public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+ boolean isEnabled = false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ if (enabled) {
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ }
+ } else {
+ isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ }
+
+ return isEnabled;
+ }
+
+ @Override
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ @Override
+ public int getProfileId() {
+ return BluetoothProfile.HAP_CLIENT;
+ }
+
+ @Override
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ @Override
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_hearing_aid;
+ }
+
+ @Override
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_hearing_aid_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_hearing_aid_profile_summary_connected;
+
+ default:
+ return BluetoothUtils.getConnectionStateSummary(state);
+ }
+ }
+
+ @Override
+ public int getDrawableResource(BluetoothClass btClass) {
+ return com.android.internal.R.drawable.ic_bt_hearing_aid;
+ }
+
+ /**
+ * Gets the name of this class
+ *
+ * @return the name of this class
+ */
+ public String toString() {
+ return NAME;
+ }
+
+ protected void finalize() {
+ Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mService);
+ mService = null;
+ } catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up HAP Client proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
index 51cf59c..ac9cdac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
@@ -34,16 +34,16 @@
private static final float CORNER_LINE_LENGTH = 264f; // 264dp
private static final float CORNER_RADIUS = 16f; // 16dp
- final private int mCornerColor;
- final private int mFocusedCornerColor;
- final private int mBackgroundColor;
+ private final int mCornerColor;
+ private final int mFocusedCornerColor;
+ private final int mBackgroundColor;
- final private Paint mStrokePaint;
- final private Paint mTransparentPaint;
- final private Paint mBackgroundPaint;
+ private final Paint mStrokePaint;
+ private final Paint mTransparentPaint;
+ private final Paint mBackgroundPaint;
- final private float mRadius;
- final private float mInnerRidus;
+ private final float mRadius;
+ private final float mInnerRadius;
private Bitmap mMaskBitmap;
private Canvas mMaskCanvas;
@@ -72,7 +72,7 @@
mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS,
getResources().getDisplayMetrics());
// Inner radius needs to minus stroke width for keeping the width of border consistent.
- mInnerRidus = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ mInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
CORNER_RADIUS - CORNER_STROKE_WIDTH, getResources().getDisplayMetrics());
mCornerColor = context.getResources().getColor(R.color.qr_corner_line_color);
@@ -95,7 +95,10 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if(mMaskBitmap == null) {
+ if (!isLaidOut()) {
+ return;
+ }
+ if (mMaskBitmap == null) {
mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mMaskCanvas = new Canvas(mMaskBitmap);
}
@@ -105,16 +108,18 @@
@Override
protected void onDraw(Canvas canvas) {
- // Set frame line color.
- mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
- // Draw background color.
- mMaskCanvas.drawColor(mBackgroundColor);
- // Draw outer corner.
- mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
- // Draw inner transparent corner.
- mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRidus, mInnerRidus, mTransparentPaint);
+ if (mMaskCanvas != null && mMaskBitmap != null) {
+ // Set frame line color.
+ mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
+ // Draw background color.
+ mMaskCanvas.drawColor(mBackgroundColor);
+ // Draw outer corner.
+ mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
+ // Draw inner transparent corner.
+ mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRadius, mInnerRadius, mTransparentPaint);
- canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+ canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+ }
super.onDraw(canvas);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
new file mode 100644
index 0000000..03a792a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class HapClientProfileTest {
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private CachedBluetoothDeviceManager mDeviceManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private BluetoothDevice mBluetoothDevice;
+ @Mock
+ private BluetoothHapClient mService;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private BluetoothProfile.ServiceListener mServiceListener;
+ private HapClientProfile mProfile;
+
+ @Before
+ public void setUp() {
+ mProfile = new HapClientProfile(mContext, mDeviceManager, mProfileManager);
+ final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+ final ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(bluetoothManager.getAdapter());
+ mServiceListener = shadowBluetoothAdapter.getServiceListener();
+ }
+
+ @Test
+ public void onServiceConnected_isProfileReady() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+ assertThat(mProfile.isProfileReady()).isTrue();
+ verify(mProfileManager).callServiceConnectedListeners();
+ }
+
+ @Test
+ public void onServiceDisconnected_isProfileNotReady() {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HAP_CLIENT);
+
+ assertThat(mProfile.isProfileReady()).isFalse();
+ verify(mProfileManager).callServiceDisconnectedListeners();
+ }
+
+ @Test
+ public void getConnectionStatus_returnCorrectConnectionState() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionState(mBluetoothDevice))
+ .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+ assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyForbidden_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+ .isEqualTo(CONNECTION_POLICY_ALLOWED);
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void getConnectedDevices_returnCorrectList() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ int[] connectedStates = new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING};
+ List<BluetoothDevice> connectedList = Arrays.asList(
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice);
+ when(mService.getDevicesMatchingConnectionStates(connectedStates))
+ .thenReturn(connectedList);
+
+ assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+ }
+
+ @Test
+ public void getConnectableDevices_returnCorrectList() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ int[] connectableStates = new int[] {
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING};
+ List<BluetoothDevice> connectableList = Arrays.asList(
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice);
+ when(mService.getDevicesMatchingConnectionStates(connectableStates))
+ .thenReturn(connectableList);
+
+ assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size());
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index def7ddc..98af15a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cde4bc4..80af69c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4267ba2..07e37d3 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -557,6 +557,16 @@
android:showForAllUsers="true">
</activity>
+ <!-- started from SensoryPrivacyService -->
+ <activity android:name=".sensorprivacy.television.TvSensorPrivacyChangedActivity"
+ android:exported="true"
+ android:launchMode="singleTop"
+ android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
+ android:theme="@style/BottomSheet"
+ android:finishOnCloseSystemDialogs="true"
+ android:showForAllUsers="true">
+ </activity>
+
<!-- started from UsbDeviceSettingsManager -->
<activity android:name=".usb.UsbAccessoryUriActivity"
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index e30d441..8d44315 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -35,4 +35,6 @@
<!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
fade_out_complete_frame -->
<dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
+
+ <integer name="qs_carrier_max_em">7</integer>
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b325c56..d2deb4f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -783,6 +783,62 @@
Microphone and camera available
</string>
+ <!--- Title of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_turned_on_dialog_title">
+ Microphone turned on
+ </string>
+
+ <!--- Title of dialog triggered if the microphone sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_turned_off_dialog_title">
+ Microphone turned off
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_unblocked_dialog_content">
+ Microphone is enabled for all apps and services.
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+ and there are no active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content">
+ Microphone access is disabled for all apps and services.
+ You can enable microphone access in Settings > Privacy > Microphone.
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+ and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content">
+ Microphone access is disabled for all apps and services.
+ You can change this in Settings > Privacy > Microphone.
+ </string>
+
+ <!--- Title of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_turned_on_dialog_title">
+ Camera turned on
+ </string>
+
+ <!--- Title of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_turned_off_dialog_title">
+ Camera turned off
+ </string>
+
+ <!--- Content of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_unblocked_dialog_content">
+ Camera is enabled for all apps and services.
+ </string>
+
+ <!--- Content of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_blocked_dialog_content">
+ Camera access is disabled for all apps and services.
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+ and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_htt_blocked_dialog_content">To use the microphone button, enable microphone access in Settings.</string>
+
+ <!-- Sensor privacy dialog: Button to open system settings [CHAR LIMIT=50] -->
+ <string name="sensor_privacy_dialog_open_settings">Open settings.</string>
+
<!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
<string name="media_seamless_other_device">Other device</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index e743ec8..bfbe88c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,6 +20,7 @@
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import com.android.systemui.shared.recents.ISystemUiProxy;
oneway interface IOverviewProxy {
@@ -44,12 +45,6 @@
void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8;
/**
- * Sent when there was an action on one of the onboarding tips view.
- * TODO: Move this implementation to SystemUI completely
- */
- void onTip(int actionType, int viewType) = 10;
-
- /**
* Sent when device assistant changes its default assistant whether it is available or not.
*/
void onAssistantAvailable(boolean available) = 13;
@@ -60,13 +55,6 @@
void onAssistantVisibilityChanged(float visibility) = 14;
/**
- * Sent when back is triggered.
- * TODO: Move this implementation to SystemUI completely
- */
- void onBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) = 15;
-
- /**
* Sent when some system ui state changes.
*/
void onSystemUiStateChanged(int stateFlags) = 16;
@@ -115,4 +103,9 @@
* Sent when split keyboard shortcut is triggered to enter stage split.
*/
void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+
+ /**
+ * Sent when the surface for navigation bar is created or changed
+ */
+ void onNavigationBarSurface(in SurfaceControl surface) = 26;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2b2b05ce..1c532fe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -24,7 +24,6 @@
import android.view.MotionEvent;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.RemoteTransitionCompat;
/**
* Temporary callbacks into SystemUI.
@@ -106,9 +105,6 @@
/** Sets home rotation enabled. */
void setHomeRotationEnabled(boolean enabled) = 45;
- /** Notifies that a swipe-up gesture has started */
- oneway void notifySwipeUpGestureStarted() = 46;
-
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
deleted file mode 100644
index 37e706a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TransitionOldType;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
-import android.annotation.SuppressLint;
-import android.app.IApplicationThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-
-import com.android.wm.shell.util.CounterRotator;
-
-/**
- * @see RemoteAnimationAdapter
- */
-public class RemoteAnimationAdapterCompat {
-
- private final RemoteAnimationAdapter mWrapped;
- private final RemoteTransitionCompat mRemoteTransition;
-
- public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration,
- long statusBarTransitionDelay, IApplicationThread appThread) {
- mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration,
- statusBarTransitionDelay);
- mRemoteTransition = buildRemoteTransition(runner, appThread);
- }
-
- public RemoteAnimationAdapter getWrapped() {
- return mWrapped;
- }
-
- /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
- public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner,
- IApplicationThread appThread) {
- return new RemoteTransitionCompat(
- new RemoteTransition(wrapRemoteTransition(runner), appThread));
- }
-
- public RemoteTransitionCompat getRemoteTransition() {
- return mRemoteTransition;
- }
-
- /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
- public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
- final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
- return new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- final Runnable animationFinishedCallback = new Runnable() {
- @Override
- public void run() {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
- + " finished callback", e);
- }
- }
- };
- remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
- nonApps, animationFinishedCallback);
- }
-
- @Override
- public void onAnimationCancelled(boolean isKeyguardOccluded) {
- remoteAnimationAdapter.onAnimationCancelled();
- }
- };
- }
-
- private static IRemoteTransition.Stub wrapRemoteTransition(
- final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
- return new IRemoteTransition.Stub() {
- final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
-
- @Override
- public void startAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishCallback) {
- final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
- final RemoteAnimationTarget[] apps =
- RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
- final RemoteAnimationTarget[] wallpapers =
- RemoteAnimationTargetCompat.wrapNonApps(
- info, true /* wallpapers */, t, leashMap);
- final RemoteAnimationTarget[] nonApps =
- RemoteAnimationTargetCompat.wrapNonApps(
- info, false /* wallpapers */, t, leashMap);
-
- // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
- boolean isReturnToHome = false;
- TransitionInfo.Change launcherTask = null;
- TransitionInfo.Change wallpaper = null;
- int launcherLayer = 0;
- int rotateDelta = 0;
- float displayW = 0;
- float displayH = 0;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- // skip changes that we didn't wrap
- if (!leashMap.containsKey(change.getLeash())) continue;
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
- isReturnToHome = change.getMode() == TRANSIT_OPEN
- || change.getMode() == TRANSIT_TO_FRONT;
- launcherTask = change;
- launcherLayer = info.getChanges().size() - i;
- } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- wallpaper = change;
- }
- if (change.getParent() == null && change.getEndRotation() >= 0
- && change.getEndRotation() != change.getStartRotation()) {
- rotateDelta = change.getEndRotation() - change.getStartRotation();
- displayW = change.getEndAbsBounds().width();
- displayH = change.getEndAbsBounds().height();
- }
- }
-
- // Prepare for rotation if there is one
- final CounterRotator counterLauncher = new CounterRotator();
- final CounterRotator counterWallpaper = new CounterRotator();
- if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
- counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
- rotateDelta, displayW, displayH);
- if (counterLauncher.getSurface() != null) {
- t.setLayer(counterLauncher.getSurface(), launcherLayer);
- }
- }
-
- if (isReturnToHome) {
- if (counterLauncher.getSurface() != null) {
- t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
- }
- // Need to "boost" the closing things since that's what launcher expects.
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = leashMap.get(change.getLeash());
- // skip changes that we didn't wrap
- if (leash == null) continue;
- final int mode = info.getChanges().get(i).getMode();
- // Only deal with independent layers
- if (!TransitionInfo.isIndependent(change, info)) continue;
- if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- t.setLayer(leash, info.getChanges().size() * 3 - i);
- counterLauncher.addChild(t, leash);
- }
- }
- // Make wallpaper visible immediately since launcher apparently won't do this.
- for (int i = wallpapers.length - 1; i >= 0; --i) {
- t.show(wallpapers[i].leash);
- t.setAlpha(wallpapers[i].leash, 1.f);
- }
- } else {
- if (launcherTask != null) {
- counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
- }
- if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
- counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
- rotateDelta, displayW, displayH);
- if (counterWallpaper.getSurface() != null) {
- t.setLayer(counterWallpaper.getSurface(), -1);
- counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
- }
- }
- }
- t.apply();
-
- final Runnable animationFinishedCallback = new Runnable() {
- @Override
- @SuppressLint("NewApi")
- public void run() {
- final SurfaceControl.Transaction finishTransaction =
- new SurfaceControl.Transaction();
- counterLauncher.cleanUp(finishTransaction);
- counterWallpaper.cleanUp(finishTransaction);
- // Release surface references now. This is apparently to free GPU memory
- // while doing quick operations (eg. during CTS).
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- info.getChanges().get(i).getLeash().release();
- }
- // Don't release here since launcher might still be using them. Instead
- // let launcher release them (eg. via RemoteAnimationTargets)
- leashMap.clear();
- try {
- finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
- } catch (RemoteException e) {
- Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
- + " finished callback", e);
- }
- }
- };
- synchronized (mFinishRunnables) {
- mFinishRunnables.put(token, animationFinishedCallback);
- }
- // TODO(bc-unlcok): Pass correct transit type.
- remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
- apps, wallpapers, nonApps, () -> {
- synchronized (mFinishRunnables) {
- if (mFinishRunnables.remove(token) == null) return;
- }
- animationFinishedCallback.run();
- });
- }
-
- @Override
- public void mergeAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) {
- // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
- // to legacy cancel.
- final Runnable finishRunnable;
- synchronized (mFinishRunnables) {
- finishRunnable = mFinishRunnables.remove(mergeTarget);
- }
- if (finishRunnable == null) return;
- remoteAnimationAdapter.onAnimationCancelled();
- finishRunnable.run();
- }
- };
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
deleted file mode 100644
index ab55037..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.view.RemoteAnimationDefinition;
-
-/**
- * @see RemoteAnimationDefinition
- */
-public class RemoteAnimationDefinitionCompat {
-
- private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition();
-
- public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) {
- mWrapped.addRemoteAnimation(transition, adapter.getWrapped());
- }
-
- public void addRemoteAnimation(int transition, int activityTypeFilter,
- RemoteAnimationAdapterCompat adapter) {
- mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped());
- }
-
- public RemoteAnimationDefinition getWrapped() {
- return mWrapped;
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5809c81..93c8073 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,12 +16,197 @@
package com.android.systemui.shared.system;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-public interface RemoteAnimationRunnerCompat {
- void onAnimationStart(@WindowManager.TransitionOldType int transit,
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManager.TransitionOldType;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.util.CounterRotator;
+
+public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
+
+ public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
- void onAnimationCancelled();
+
+ @Override
+ public final void onAnimationStart(@TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+
+ onAnimationStart(transit, apps, wallpapers,
+ nonApps, () -> {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ + " finished callback", e);
+ }
+ });
+ }
+
+ public IRemoteTransition toRemoteTransition() {
+ return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
+ @Override
+ public void startAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
+ final RemoteAnimationTarget[] apps =
+ RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
+ final RemoteAnimationTarget[] wallpapers =
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, true /* wallpapers */, t, leashMap);
+ final RemoteAnimationTarget[] nonApps =
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, false /* wallpapers */, t, leashMap);
+
+ // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
+ boolean isReturnToHome = false;
+ TransitionInfo.Change launcherTask = null;
+ TransitionInfo.Change wallpaper = null;
+ int launcherLayer = 0;
+ int rotateDelta = 0;
+ float displayW = 0;
+ float displayH = 0;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ // skip changes that we didn't wrap
+ if (!leashMap.containsKey(change.getLeash())) continue;
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+ isReturnToHome = change.getMode() == TRANSIT_OPEN
+ || change.getMode() == TRANSIT_TO_FRONT;
+ launcherTask = change;
+ launcherLayer = info.getChanges().size() - i;
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ wallpaper = change;
+ }
+ if (change.getParent() == null && change.getEndRotation() >= 0
+ && change.getEndRotation() != change.getStartRotation()) {
+ rotateDelta = change.getEndRotation() - change.getStartRotation();
+ displayW = change.getEndAbsBounds().width();
+ displayH = change.getEndAbsBounds().height();
+ }
+ }
+
+ // Prepare for rotation if there is one
+ final CounterRotator counterLauncher = new CounterRotator();
+ final CounterRotator counterWallpaper = new CounterRotator();
+ if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
+ counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), launcherLayer);
+ }
+ }
+
+ if (isReturnToHome) {
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
+ }
+ // Need to "boost" the closing things since that's what launcher expects.
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = leashMap.get(change.getLeash());
+ // skip changes that we didn't wrap
+ if (leash == null) continue;
+ final int mode = info.getChanges().get(i).getMode();
+ // Only deal with independent layers
+ if (!TransitionInfo.isIndependent(change, info)) continue;
+ if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+ t.setLayer(leash, info.getChanges().size() * 3 - i);
+ counterLauncher.addChild(t, leash);
+ }
+ }
+ // Make wallpaper visible immediately since launcher apparently won't do this.
+ for (int i = wallpapers.length - 1; i >= 0; --i) {
+ t.show(wallpapers[i].leash);
+ t.setAlpha(wallpapers[i].leash, 1.f);
+ }
+ } else {
+ if (launcherTask != null) {
+ counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
+ }
+ if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
+ counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterWallpaper.getSurface() != null) {
+ t.setLayer(counterWallpaper.getSurface(), -1);
+ counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
+ }
+ }
+ }
+ t.apply();
+
+ final Runnable animationFinishedCallback = () -> {
+ final SurfaceControl.Transaction finishTransaction =
+ new SurfaceControl.Transaction();
+ counterLauncher.cleanUp(finishTransaction);
+ counterWallpaper.cleanUp(finishTransaction);
+ // Release surface references now. This is apparently to free GPU memory
+ // while doing quick operations (eg. during CTS).
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ info.getChanges().get(i).getLeash().release();
+ }
+ // Don't release here since launcher might still be using them. Instead
+ // let launcher release them (eg. via RemoteAnimationTargets)
+ leashMap.clear();
+ try {
+ finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+ } catch (RemoteException e) {
+ Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ + " finished callback", e);
+ }
+ };
+ synchronized (mFinishRunnables) {
+ mFinishRunnables.put(token, animationFinishedCallback);
+ }
+ // TODO(bc-unlcok): Pass correct transit type.
+ onAnimationStart(TRANSIT_OLD_NONE,
+ apps, wallpapers, nonApps, () -> {
+ synchronized (mFinishRunnables) {
+ if (mFinishRunnables.remove(token) == null) return;
+ }
+ animationFinishedCallback.run();
+ });
+ }
+
+ @Override
+ public void mergeAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+ // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+ // to legacy cancel.
+ final Runnable finishRunnable;
+ synchronized (mFinishRunnables) {
+ finishRunnable = mFinishRunnables.remove(mergeTarget);
+ }
+ if (finishRunnable == null) return;
+ onAnimationCancelled(false /* isKeyguardOccluded */);
+ finishRunnable.run();
+ }
+ };
+ }
}
\ No newline at end of file
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 d6655a7..d4d3d25 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
@@ -18,29 +18,22 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
-import android.content.ComponentName;
import android.graphics.Rect;
import android.os.IBinder;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -53,72 +46,23 @@
import android.window.PictureInPictureSurfaceTransaction;
import android.window.RemoteTransition;
import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DataClass;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.ArrayList;
-import java.util.concurrent.Executor;
/**
- * Wrapper to expose RemoteTransition (shell transitions) to Launcher.
- *
- * @see IRemoteTransition
- * @see TransitionFilter
+ * Helper class to build {@link RemoteTransition} objects
*/
-@DataClass
-public class RemoteTransitionCompat implements Parcelable {
+public class RemoteTransitionCompat {
private static final String TAG = "RemoteTransitionCompat";
- @NonNull final RemoteTransition mTransition;
- @Nullable TransitionFilter mFilter = null;
-
- RemoteTransitionCompat(RemoteTransition transition) {
- mTransition = transition;
- }
-
- public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
- @NonNull Executor executor, @Nullable IApplicationThread appThread) {
- IRemoteTransition remote = new IRemoteTransition.Stub() {
- @Override
- public void startAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishedCallback) {
- final Runnable finishAdapter = () -> {
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to call transition finished callback", e);
- }
- };
- executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter));
- }
-
- @Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishedCallback) {
- final Runnable finishAdapter = () -> {
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to call transition finished callback", e);
- }
- };
- executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget,
- finishAdapter));
- }
- };
- mTransition = new RemoteTransition(remote, appThread);
- }
-
/** Constructor specifically for recents animation */
- public RemoteTransitionCompat(RecentsAnimationListener recents,
+ public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
RecentsAnimationControllerCompat controller, IApplicationThread appThread) {
IRemoteTransition remote = new IRemoteTransition.Stub() {
final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
@@ -193,25 +137,7 @@
mRecentsSession.commitTasksAppearedIfNeeded(recents);
}
};
- mTransition = new RemoteTransition(remote, appThread);
- }
-
- /** Adds a filter check that restricts this remote transition to home open transitions. */
- public void addHomeOpenCheck(ComponentName homeActivity) {
- if (mFilter == null) {
- mFilter = new TransitionFilter();
- }
- // No need to handle the transition that also dismisses keyguard.
- mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
- mFilter.mRequirements =
- new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
- new TransitionFilter.Requirement()};
- mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
- mFilter.mRequirements[0].mTopActivity = homeActivity;
- mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
- mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
- mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ return new RemoteTransition(remote, appThread);
}
/**
@@ -505,161 +431,4 @@
@Override public void animateNavigationBarToApp(long duration) {
}
}
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- /* package-private */ RemoteTransitionCompat(
- @NonNull RemoteTransition transition,
- @Nullable TransitionFilter filter) {
- this.mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- this.mFilter = filter;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public @NonNull RemoteTransition getTransition() {
- return mTransition;
- }
-
- @DataClass.Generated.Member
- public @Nullable TransitionFilter getFilter() {
- return mFilter;
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- byte flg = 0;
- if (mFilter != null) flg |= 0x2;
- dest.writeByte(flg);
- dest.writeTypedObject(mTransition, flags);
- if (mFilter != null) dest.writeTypedObject(mFilter, flags);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- protected RemoteTransitionCompat(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
- TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
-
- this.mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- this.mFilter = filter;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR
- = new Parcelable.Creator<RemoteTransitionCompat>() {
- @Override
- public RemoteTransitionCompat[] newArray(int size) {
- return new RemoteTransitionCompat[size];
- }
-
- @Override
- public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) {
- return new RemoteTransitionCompat(in);
- }
- };
-
- /**
- * A builder for {@link RemoteTransitionCompat}
- */
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
- public static class Builder {
-
- private @NonNull RemoteTransition mTransition;
- private @Nullable TransitionFilter mFilter;
-
- private long mBuilderFieldsSet = 0L;
-
- public Builder(
- @NonNull RemoteTransition transition) {
- mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- }
-
- @DataClass.Generated.Member
- public @NonNull Builder setTransition(@NonNull RemoteTransition value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mTransition = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull Builder setFilter(@NonNull TransitionFilter value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2;
- mFilter = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull RemoteTransitionCompat build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x4; // Mark builder used
-
- if ((mBuilderFieldsSet & 0x2) == 0) {
- mFilter = null;
- }
- RemoteTransitionCompat o = new RemoteTransitionCompat(
- mTransition,
- mFilter);
- return o;
- }
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x4) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
- }
-
- @DataClass.Generated(
- time = 1629321609807L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
- inputSignatures = "private static final java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate android.window.WindowContainerToken mPausingTask\nprivate android.window.WindowContainerToken mPipTask\nprivate android.window.TransitionInfo mInfo\nprivate android.view.SurfaceControl mOpeningLeash\nprivate android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate android.os.IBinder mTransition\n void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
deleted file mode 100644
index accc456..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.system;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-/** Interface for something that runs a remote transition animation. */
-public interface RemoteTransitionRunner {
- /**
- * Starts a transition animation. Once complete, the implementation should call
- * `finishCallback`.
- */
- void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
- Runnable finishCallback);
-
- /**
- * Attempts to merge a transition into the currently-running animation. If merge is not
- * possible/supported, this should do nothing. Otherwise, the implementation should call
- * `finishCallback` immediately to indicate that it merged the transition.
- *
- * @param transition The transition that wants to be merged into the running animation.
- * @param mergeTarget The transition to merge into (that this runner is currently animating).
- */
- default void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index d48d7ff..c9b8712 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -228,7 +228,7 @@
listenForDozing(this)
if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
listenForDozeAmountTransition(this)
- listenForGoneToAodTransition(this)
+ listenForAnyStateToAodTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -286,10 +286,10 @@
* dozing.
*/
@VisibleForTesting
- internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+ internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.goneToAodTransition.filter {
- it.transitionState == TransitionState.STARTED
+ keyguardTransitionInteractor.anyStateToAodTransition.filter {
+ it.transitionState == TransitionState.FINISHED
}.collect {
dozeAmount = 1f
clock?.animations?.doze(dozeAmount)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8..a0206f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@
val keyguardOccluded: Boolean,
val occludingAppRequestingFp: Boolean,
val primaryUser: Boolean,
+ val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c756a17..2bb3a5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -106,8 +106,9 @@
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
- @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
+ @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
public @interface Mode {}
+ static final int MODE_UNINITIALIZED = -1;
static final int MODE_DEFAULT = 0;
static final int MODE_ONE_HANDED = 1;
static final int MODE_USER_SWITCHER = 2;
@@ -154,7 +155,11 @@
private boolean mDisappearAnimRunning;
private SwipeListener mSwipeListener;
private ViewMode mViewMode = new DefaultViewMode();
- private @Mode int mCurrentMode = MODE_DEFAULT;
+ /*
+ * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
+ * yet been called on it. This will happen when the ViewController is initialized.
+ */
+ private @Mode int mCurrentMode = MODE_UNINITIALIZED;
private int mWidth = -1;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -347,6 +352,8 @@
private String modeToString(@Mode int mode) {
switch (mode) {
+ case MODE_UNINITIALIZED:
+ return "Uninitialized";
case MODE_DEFAULT:
return "Default";
case MODE_ONE_HANDED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 79a01b9..fbb114c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -318,6 +318,7 @@
@Override
public void onInit() {
mSecurityViewFlipperController.init();
+ configureMode();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 39dc609..9d2dcf0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -150,6 +150,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Lists;
@@ -321,17 +322,20 @@
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
+ private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -380,6 +384,7 @@
protected Handler getHandler() {
return mHandler;
}
+
private final Handler mHandler;
private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -704,6 +709,7 @@
/**
* Request to listen for face authentication when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request face auth listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFace()}
@@ -716,6 +722,7 @@
/**
* Request to listen for fingerprint when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request fingerprint listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)}
@@ -1930,6 +1937,7 @@
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
+ SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -1972,6 +1980,7 @@
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
+ mSecureSettings = secureSettings;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2214,9 +2223,35 @@
Settings.System.TIME_12_24)));
}
};
+
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+ updateSfpsRequireScreenOnToAuthPref();
+ mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSfpsRequireScreenOnToAuthPref();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ mSecureSettings.getUriFor(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+ false,
+ mSfpsRequireScreenOnToAuthPrefObserver,
+ getCurrentUser());
+ }
+
+ protected void updateSfpsRequireScreenOnToAuthPref() {
+ final int defaultSfpsRequireScreenOnToAuthValue =
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+ mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ defaultSfpsRequireScreenOnToAuthValue,
+ getCurrentUser()) != 0;
}
private void initializeSimState() {
@@ -2261,6 +2296,22 @@
}
/**
+ * @return true if there's at least one sfps enrollment for the current user.
+ */
+ public boolean isSfpsEnrolled() {
+ return mAuthController.isSfpsEnrolled(getCurrentUser());
+ }
+
+ /**
+ * @return true if sfps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isSfpsSupported() {
+ return mAuthController.getSfpsProps() != null
+ && !mAuthController.getSfpsProps().isEmpty();
+ }
+
+ /**
* @return true if there's at least one face enrolled
*/
public boolean isFaceEnrolled() {
@@ -2583,13 +2634,21 @@
!(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
- && userDoesNotHaveTrust);
+ && !isEncryptedOrLockdownForUser
+ && userDoesNotHaveTrust);
- final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+ boolean shouldListenSideFpsState = true;
+ if (isSfpsSupported() && isSfpsEnrolled()) {
+ shouldListenSideFpsState =
+ mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+ }
+
+ boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+ && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenSideFpsState;
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
@@ -2611,6 +2670,7 @@
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
+ shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
@@ -3712,6 +3772,11 @@
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
+ if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mSfpsRequireScreenOnToAuthPrefObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3784,6 +3849,13 @@
pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+ } else if (isSfpsSupported()) {
+ pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
+ pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
+ if (isSfpsEnrolled()) {
+ pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
+ + mSfpsRequireScreenOnToAuthPrefEnabled);
+ }
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 313ff4157..0cf3cfe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -52,7 +52,7 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
@@ -147,7 +147,7 @@
@NonNull private final WindowManager mWindowManager;
@NonNull private final DisplayManager mDisplayManager;
@Nullable private UdfpsController mUdfpsController;
- @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
+ @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
@Nullable private SidefpsController mSidefpsController;
@Nullable private IBiometricContextListener mBiometricContextListener;
@Nullable private UdfpsLogger mUdfpsLogger;
@@ -160,6 +160,7 @@
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
@NonNull private final SparseBooleanArray mFaceEnrolledForUser;
+ @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -365,6 +366,15 @@
}
}
}
+ if (mSidefpsProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+ } else {
+ for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+ if (prop.sensorId == sensorId) {
+ mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged(modality);
}
@@ -722,6 +732,7 @@
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
@@ -872,21 +883,22 @@
}
/**
- * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
+ * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}.
*
- * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by
- * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}.
+ * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback}
+ * and registers it with this class by calling
+ * {@link CommandQueue#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)}.
*/
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
- mUdfpsHbmListener = listener;
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
+ mUdfpsRefreshRateRequestCallback = callback;
}
/**
- * @return IUdfpsHbmListener that can be set by DisplayModeDirector.
+ * @return IUdfpsRefreshRateRequestCallback that can be set by DisplayModeDirector.
*/
- @Nullable public IUdfpsHbmListener getUdfpsHbmListener() {
- return mUdfpsHbmListener;
+ @Nullable public IUdfpsRefreshRateRequestCallback getUdfpsRefreshRateCallback() {
+ return mUdfpsRefreshRateRequestCallback;
}
@Override
@@ -964,6 +976,11 @@
return mUdfpsProps;
}
+ @Nullable
+ public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+ return mSidefpsProps;
+ }
+
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
@@ -1090,6 +1107,17 @@
return mUdfpsEnrolledForUser.get(userId);
}
+ /**
+ * Whether the passed userId has enrolled SFPS.
+ */
+ public boolean isSfpsEnrolled(int userId) {
+ if (mSidefpsController == null) {
+ return false;
+ }
+
+ return mSfpsEnrolledForUser.get(userId);
+ }
+
/** If BiometricPrompt is currently being shown to the user. */
public boolean isShowing() {
return mCurrentDialog != null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 48c60a0..a091ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
@@ -63,6 +64,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -133,6 +135,7 @@
@NonNull private final LatencyTracker mLatencyTracker;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ @NonNull private final BouncerInteractor mBouncerInteractor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -219,7 +222,8 @@
mUnlockedScreenOffAnimationController,
mUdfpsDisplayMode, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator)));
+ fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
+ mBouncerInteractor)));
}
@Override
@@ -296,6 +300,30 @@
mOverlay.getOverlayView().setDebugMessage(message);
});
}
+
+ public Rect getSensorBounds() {
+ return mOverlayParams.getSensorBounds();
+ }
+
+ /**
+ * Passes a mocked MotionEvent to OnTouch.
+ *
+ * @param event MotionEvent to simulate in onTouch
+ */
+ public void debugOnTouch(long requestId, MotionEvent event) {
+ UdfpsController.this.onTouch(requestId, event, false);
+ }
+
+ /**
+ * Debug to run onUiReady
+ */
+ public void debugOnUiReady(long requestId, int sensorId) {
+ if (UdfpsController.this.mAlternateTouchProvider != null) {
+ UdfpsController.this.mAlternateTouchProvider.onUiReady();
+ } else {
+ UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId);
+ }
+ }
}
/**
@@ -620,7 +648,8 @@
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
- @BiometricsBackground Executor biometricsExecutor) {
+ @BiometricsBackground Executor biometricsExecutor,
+ @NonNull BouncerInteractor bouncerInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -651,6 +680,7 @@
mActivityLaunchAnimator = activityLaunchAnimator;
mAlternateTouchProvider = alternateTouchProvider.orElse(null);
mBiometricExecutor = biometricsExecutor;
+ mBouncerInteractor = bouncerInteractor;
mOrientationListener = new BiometricDisplayListener(
context,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7d01096..d70861a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -48,6 +48,8 @@
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -70,29 +72,31 @@
*/
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
- private val context: Context,
- fingerprintManager: FingerprintManager,
- private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
- private val accessibilityManager: AccessibilityManager,
- private val statusBarStateController: StatusBarStateController,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager,
- private val transitionController: LockscreenShadeTransitionController,
- private val configurationController: ConfigurationController,
- private val systemClock: SystemClock,
- private val keyguardStateController: KeyguardStateController,
- private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
- private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
- val requestId: Long,
- @ShowReason val requestReason: Int,
- private val controllerCallback: IUdfpsOverlayControllerCallback,
- private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
+ private val context: Context,
+ fingerprintManager: FingerprintManager,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val accessibilityManager: AccessibilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dialogManager: SystemUIDialogManager,
+ private val dumpManager: DumpManager,
+ private val transitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ val requestId: Long,
+ @ShowReason val requestReason: Int,
+ private val controllerCallback: IUdfpsOverlayControllerCallback,
+ private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -246,7 +250,9 @@
unlockedScreenOffAnimationController,
dialogManager,
controller,
- activityLaunchAnimator
+ activityLaunchAnimator,
+ featureFlags,
+ bouncerInteractor
)
}
REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
index b80b8a0..670a8e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
@@ -46,7 +46,7 @@
logger.e(TAG, "enable | already requested")
return
}
- if (authController.udfpsHbmListener == null) {
+ if (authController.udfpsRefreshRateCallback == null) {
logger.e(TAG, "enable | mDisplayManagerCallback is null")
return
}
@@ -60,7 +60,7 @@
try {
// This method is a misnomer. It has nothing to do with HBM, its purpose is to set
// the appropriate display refresh rate.
- authController.udfpsHbmListener!!.onHbmEnabled(request.displayId)
+ authController.udfpsRefreshRateCallback!!.onRequestEnabled(request.displayId)
logger.v(TAG, "enable | requested optimal refresh rate for UDFPS")
} catch (e: RemoteException) {
logger.e(TAG, "enable", e)
@@ -84,7 +84,7 @@
try {
// Allow DisplayManager to unset the UDFPS refresh rate.
- authController.udfpsHbmListener!!.onHbmDisabled(request.displayId)
+ authController.udfpsRefreshRateCallback!!.onRequestDisabled(request.displayId)
logger.v(TAG, "disable | removed the UDFPS refresh rate request")
} catch (e: RemoteException) {
logger.e(TAG, "disable", e)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
deleted file mode 100644
index 4d7f89d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.util.MathUtils;
-import android.view.MotionEvent;
-
-import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-
-import java.io.PrintWriter;
-
-/**
- * Class that coordinates non-HBM animations during keyguard authentication.
- */
-public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
- public static final String TAG = "UdfpsKeyguardViewCtrl";
- @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
- @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
- @NonNull private final ConfigurationController mConfigurationController;
- @NonNull private final SystemClock mSystemClock;
- @NonNull private final KeyguardStateController mKeyguardStateController;
- @NonNull private final UdfpsController mUdfpsController;
- @NonNull private final UnlockedScreenOffAnimationController
- mUnlockedScreenOffAnimationController;
- @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
- private final ValueAnimator mUnlockedScreenOffDozeAnimator = ValueAnimator.ofFloat(0f, 1f);
-
- private boolean mShowingUdfpsBouncer;
- private boolean mUdfpsRequested;
- private float mQsExpansion;
- private boolean mFaceDetectRunning;
- private int mStatusBarState;
- private float mTransitionToFullShadeProgress;
- private float mLastDozeAmount;
- private long mLastUdfpsBouncerShowTime = -1;
- private float mPanelExpansionFraction;
- private boolean mLaunchTransitionFadingAway;
- private boolean mIsLaunchingActivity;
- private float mActivityLaunchProgress;
-
- /**
- * hidden amount of pin/pattern/password bouncer
- * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
- * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f)
- */
- private float mInputBouncerHiddenAmount;
- private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible
-
- protected UdfpsKeyguardViewController(
- @NonNull UdfpsKeyguardView view,
- @NonNull StatusBarStateController statusBarStateController,
- @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
- @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DumpManager dumpManager,
- @NonNull LockscreenShadeTransitionController transitionController,
- @NonNull ConfigurationController configurationController,
- @NonNull SystemClock systemClock,
- @NonNull KeyguardStateController keyguardStateController,
- @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- @NonNull SystemUIDialogManager systemUIDialogManager,
- @NonNull UdfpsController udfpsController,
- @NonNull ActivityLaunchAnimator activityLaunchAnimator) {
- super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
- dumpManager);
- mKeyguardViewManager = statusBarKeyguardViewManager;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLockScreenShadeTransitionController = transitionController;
- mConfigurationController = configurationController;
- mSystemClock = systemClock;
- mKeyguardStateController = keyguardStateController;
- mUdfpsController = udfpsController;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- mActivityLaunchAnimator = activityLaunchAnimator;
-
- mUnlockedScreenOffDozeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mUnlockedScreenOffDozeAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mUnlockedScreenOffDozeAnimator.addUpdateListener(
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mView.onDozeAmountChanged(
- animation.getAnimatedFraction(),
- (float) animation.getAnimatedValue(),
- UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
- }
- });
- }
-
- @Override
- @NonNull protected String getTag() {
- return "UdfpsKeyguardViewController";
- }
-
- @Override
- public void onInit() {
- super.onInit();
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- }
-
- @Override
- protected void onViewAttached() {
- super.onViewAttached();
- final float dozeAmount = getStatusBarStateController().getDozeAmount();
- mLastDozeAmount = dozeAmount;
- mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
- getStatusBarStateController().addCallback(mStateListener);
-
- mUdfpsRequested = false;
-
- mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
- mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
- mStatusBarState = getStatusBarStateController().getState();
- mQsExpansion = mKeyguardViewManager.getQsExpansion();
- updateGenericBouncerVisibility();
- mConfigurationController.addCallback(mConfigurationListener);
- getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
- updateScaleFactor();
- mView.updatePadding();
- updateAlpha();
- updatePauseAuth();
-
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
- mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- protected void onViewDetached() {
- super.onViewDetached();
- mFaceDetectRunning = false;
-
- mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
- getStatusBarStateController().removeCallback(mStateListener);
- mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- mConfigurationController.removeCallback(mConfigurationListener);
- getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
- if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
- }
- mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- super.dump(pw, args);
- pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer);
- pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
- pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState));
- pw.println("mTransitionToFullShadeProgress=" + mTransitionToFullShadeProgress);
- pw.println("mQsExpansion=" + mQsExpansion);
- pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing);
- pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
- pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction);
- pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
- pw.println("mUdfpsRequested=" + mUdfpsRequested);
- pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
- pw.println("mLastDozeAmount=" + mLastDozeAmount);
-
- mView.dump(pw);
- }
-
- /**
- * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
- * @return whether the udfpsBouncer has been newly shown or hidden
- */
- private boolean showUdfpsBouncer(boolean show) {
- if (mShowingUdfpsBouncer == show) {
- return false;
- }
-
- boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
- mShowingUdfpsBouncer = show;
- if (mShowingUdfpsBouncer) {
- mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
- }
- if (mShowingUdfpsBouncer) {
- if (udfpsAffordanceWasNotShowing) {
- mView.animateInUdfpsBouncer(null);
- }
-
- if (mKeyguardStateController.isOccluded()) {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
- }
-
- mView.announceForAccessibility(mView.getContext().getString(
- R.string.accessibility_fingerprint_bouncer));
- } else {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- }
-
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- return true;
- }
-
- /**
- * Returns true if the fingerprint manager is running but we want to temporarily pause
- * authentication. On the keyguard, we may want to show udfps when the shade
- * is expanded, so this can be overridden with the showBouncer method.
- */
- public boolean shouldPauseAuth() {
- if (mShowingUdfpsBouncer) {
- return false;
- }
-
- if (mUdfpsRequested && !getNotificationShadeVisible()
- && (!mIsGenericBouncerShowing
- || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)
- && mKeyguardStateController.isShowing()) {
- return false;
- }
-
- if (mLaunchTransitionFadingAway) {
- return true;
- }
-
- // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
- // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
- // delayed. However, we still animate in the UDFPS affordance with the
- // mUnlockedScreenOffDozeAnimator.
- if (mStatusBarState != KEYGUARD && mLastDozeAmount == 0f) {
- return true;
- }
-
- if (mInputBouncerHiddenAmount < .5f) {
- return true;
- }
-
- if (mView.getUnpausedAlpha() < (255 * .1)) {
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean listenForTouchesOutsideView() {
- return true;
- }
-
- @Override
- public void onTouchOutsideView() {
- maybeShowInputBouncer();
- }
-
- /**
- * If we were previously showing the udfps bouncer, hide it and instead show the regular
- * (pin/pattern/password) bouncer.
- *
- * Does nothing if we weren't previously showing the UDFPS bouncer.
- */
- private void maybeShowInputBouncer() {
- if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
- mKeyguardViewManager.showBouncer(true);
- }
- }
-
- /**
- * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
- * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
- * bouncer.
- */
- private boolean hasUdfpsBouncerShownWithMinTime() {
- return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
- }
-
- /**
- * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
- * transitioning yet, while 1.0f means we've fully dragged down.
- *
- * For example, start swiping down to expand the notification shade from the empty space in
- * the middle of the lock screen.
- */
- public void setTransitionToFullShadeProgress(float progress) {
- mTransitionToFullShadeProgress = progress;
- updateAlpha();
- }
-
- /**
- * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's
- * alpha is based on the doze amount.
- */
- @Override
- public void updateAlpha() {
- // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
- // then the keyguard is occluded by some application - so instead use the input bouncer
- // hidden amount to determine the fade.
- float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mPanelExpansionFraction;
-
- int alpha = mShowingUdfpsBouncer ? 255
- : (int) MathUtils.constrain(
- MathUtils.map(.5f, .9f, 0f, 255f, expansion),
- 0f, 255f);
-
- if (!mShowingUdfpsBouncer) {
- // swipe from top of the lockscreen to expand full QS:
- alpha *= (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(mQsExpansion));
-
- // swipe from the middle (empty space) of lockscreen to expand the notification shade:
- alpha *= (1.0f - mTransitionToFullShadeProgress);
-
- // Fade out the icon if we are animating an activity launch over the lockscreen and the
- // activity didn't request the UDFPS.
- if (mIsLaunchingActivity && !mUdfpsRequested) {
- alpha *= (1.0f - mActivityLaunchProgress);
- }
-
- // Fade out alpha when a dialog is shown
- // Fade in alpha when a dialog is hidden
- alpha *= mView.getDialogSuggestedAlpha();
- }
- mView.setUnpausedAlpha(alpha);
- }
-
- /**
- * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the
- * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not.
- */
- private void updateGenericBouncerVisibility() {
- mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer
- final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth();
- if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) {
- mInputBouncerHiddenAmount = 1f;
- } else if (mIsGenericBouncerShowing) {
- // input bouncer is fully showing
- mInputBouncerHiddenAmount = 0f;
- }
- }
-
- /**
- * Update the scale factor based on the device's resolution.
- */
- private void updateScaleFactor() {
- if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) {
- mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor());
- }
- }
-
- private final StatusBarStateController.StateListener mStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- if (mLastDozeAmount < linear) {
- showUdfpsBouncer(false);
- }
- mUnlockedScreenOffDozeAnimator.cancel();
- final boolean animatingFromUnlockedScreenOff =
- mUnlockedScreenOffAnimationController.isAnimationPlaying();
- if (animatingFromUnlockedScreenOff && linear != 0f) {
- // we manually animate the fade in of the UDFPS icon since the unlocked
- // screen off animation prevents the doze amounts to be incrementally eased in
- mUnlockedScreenOffDozeAnimator.start();
- } else {
- mView.onDozeAmountChanged(linear, eased,
- UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
- }
-
- mLastDozeAmount = linear;
- updatePauseAuth();
- }
-
- @Override
- public void onStateChanged(int statusBarState) {
- mStatusBarState = statusBarState;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
- new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
- @Override
- public boolean showAlternateAuthBouncer() {
- return showUdfpsBouncer(true);
- }
-
- @Override
- public boolean hideAlternateAuthBouncer() {
- return showUdfpsBouncer(false);
- }
-
- @Override
- public boolean isShowingAlternateAuthBouncer() {
- return mShowingUdfpsBouncer;
- }
-
- @Override
- public void requestUdfps(boolean request, int color) {
- mUdfpsRequested = request;
- mView.requestUdfps(request, color);
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean isAnimating() {
- return false;
- }
-
- /**
- * Set the amount qs is expanded. Forxample, swipe down from the top of the
- * lock screen to start the full QS expansion.
- */
- @Override
- public void setQsExpansion(float qsExpansion) {
- mQsExpansion = qsExpansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean onTouch(MotionEvent event) {
- if (mTransitionToFullShadeProgress != 0) {
- return false;
- }
- return mUdfpsController.onTouch(event);
- }
-
- @Override
- public void setBouncerExpansionChanged(float expansion) {
- mInputBouncerHiddenAmount = expansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- /**
- * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer
- * visibility changes.
- */
- @Override
- public void onBouncerVisibilityChanged() {
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.println(getTag());
- }
- };
-
- private final ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onUiModeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onThemeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- updateScaleFactor();
- mView.updatePadding();
- mView.updateColor();
- }
- };
-
- private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
- @Override
- public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
- float fraction = event.getFraction();
- mPanelExpansionFraction =
- mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
- .aboutToShowBouncerProgress(fraction) : fraction;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onLaunchTransitionFadingAwayChanged() {
- mLaunchTransitionFadingAway =
- mKeyguardStateController.isLaunchTransitionFadingAway();
- updatePauseAuth();
- }
- };
-
- private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
- new ActivityLaunchAnimator.Listener() {
- @Override
- public void onLaunchAnimationStart() {
- mIsLaunchingActivity = true;
- mActivityLaunchProgress = 0f;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationEnd() {
- mIsLaunchingActivity = false;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationProgress(float linearProgress) {
- mActivityLaunchProgress = linearProgress;
- updateAlpha();
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..5bae2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.animation.ValueAnimator
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+open class UdfpsKeyguardViewController
+constructor(
+ private val view: UdfpsKeyguardView,
+ statusBarStateController: StatusBarStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val keyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ dumpManager: DumpManager,
+ private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ systemUIDialogManager: SystemUIDialogManager,
+ private val udfpsController: UdfpsController,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor
+) :
+ UdfpsAnimationViewController<UdfpsKeyguardView>(
+ view,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager
+ ) {
+ private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
+ private var showingUdfpsBouncer = false
+ private var udfpsRequested = false
+ private var qsExpansion = 0f
+ private var faceDetectRunning = false
+ private var statusBarState = 0
+ private var transitionToFullShadeProgress = 0f
+ private var lastDozeAmount = 0f
+ private var lastUdfpsBouncerShowTime: Long = -1
+ private var panelExpansionFraction = 0f
+ private var launchTransitionFadingAway = false
+ private var isLaunchingActivity = false
+ private var activityLaunchProgress = 0f
+ private val unlockedScreenOffDozeAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
+ interpolator = Interpolators.ALPHA_IN
+ addUpdateListener { animation ->
+ view.onDozeAmountChanged(
+ animation.animatedFraction,
+ animation.animatedValue as Float,
+ UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF
+ )
+ }
+ }
+ /**
+ * Hidden amount of input (pin/pattern/password) bouncer. This is used
+ * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only
+ * used for the non-modernBouncer.
+ */
+ private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN
+ private var inputBouncerExpansion = 0f // only used for modernBouncer
+
+ private val stateListener: StatusBarStateController.StateListener =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ if (lastDozeAmount < linear) {
+ showUdfpsBouncer(false)
+ }
+ unlockedScreenOffDozeAnimator.cancel()
+ val animatingFromUnlockedScreenOff =
+ unlockedScreenOffAnimationController.isAnimationPlaying()
+ if (animatingFromUnlockedScreenOff && linear != 0f) {
+ // we manually animate the fade in of the UDFPS icon since the unlocked
+ // screen off animation prevents the doze amounts to be incrementally eased in
+ unlockedScreenOffDozeAnimator.start()
+ } else {
+ view.onDozeAmountChanged(
+ linear,
+ eased,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+ )
+ }
+ lastDozeAmount = linear
+ updatePauseAuth()
+ }
+
+ override fun onStateChanged(statusBarState: Int) {
+ this@UdfpsKeyguardViewController.statusBarState = statusBarState
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val bouncerExpansionCallback: BouncerExpansionCallback =
+ object : BouncerExpansionCallback {
+ override fun onExpansionChanged(expansion: Float) {
+ inputBouncerHiddenAmount = expansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun onVisibilityChanged(isVisible: Boolean) {
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val configurationListener: ConfigurationController.ConfigurationListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ view.updateColor()
+ }
+
+ override fun onThemeChanged() {
+ view.updateColor()
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ updateScaleFactor()
+ view.updatePadding()
+ view.updateColor()
+ }
+ }
+
+ private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
+ panelExpansionFraction =
+ if (keyguardViewManager.isBouncerInTransit) {
+ aboutToShowBouncerProgress(fraction)
+ } else {
+ fraction
+ }
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+ object : KeyguardStateController.Callback {
+ override fun onLaunchTransitionFadingAwayChanged() {
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ updatePauseAuth()
+ }
+ }
+
+ private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
+ object : ActivityLaunchAnimator.Listener {
+ override fun onLaunchAnimationStart() {
+ isLaunchingActivity = true
+ activityLaunchProgress = 0f
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationEnd() {
+ isLaunchingActivity = false
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationProgress(linearProgress: Float) {
+ activityLaunchProgress = linearProgress
+ updateAlpha()
+ }
+ }
+
+ private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
+ object : KeyguardViewManagerCallback {
+ override fun onQSExpansionChanged(qsExpansion: Float) {
+ this@UdfpsKeyguardViewController.qsExpansion = qsExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ /**
+ * Forward touches to the UdfpsController. This allows the touch to start from outside
+ * the sensor area and then slide their finger into the sensor area.
+ */
+ override fun onTouch(event: MotionEvent) {
+ // Don't forward touches if the shade has already started expanding.
+ if (transitionToFullShadeProgress != 0f) {
+ return
+ }
+ udfpsController.onTouch(event)
+ }
+ }
+
+ private val alternateAuthInterceptor: AlternateAuthInterceptor =
+ object : AlternateAuthInterceptor {
+ override fun showAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(true)
+ }
+
+ override fun hideAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(false)
+ }
+
+ override fun isShowingAlternateAuthBouncer(): Boolean {
+ return showingUdfpsBouncer
+ }
+
+ override fun requestUdfps(request: Boolean, color: Int) {
+ udfpsRequested = request
+ view.requestUdfps(request, color)
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun dump(pw: PrintWriter) {
+ pw.println(tag)
+ }
+ }
+
+ override val tag: String
+ get() = TAG
+
+ override fun onInit() {
+ super.onInit()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ }
+
+ init {
+ if (isModernBouncerEnabled) {
+ view.repeatWhenAttached {
+ // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+ // can make the view not visible; and we still want to listen for events
+ // that may make the view visible again.
+ repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ return scope.launch {
+ bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+ inputBouncerExpansion = bouncerExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+ }
+
+ public override fun onViewAttached() {
+ super.onViewAttached()
+ val dozeAmount = statusBarStateController.dozeAmount
+ lastDozeAmount = dozeAmount
+ stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
+ statusBarStateController.addCallback(stateListener)
+ udfpsRequested = false
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ keyguardStateController.addCallback(keyguardStateControllerCallback)
+ statusBarState = statusBarStateController.state
+ qsExpansion = keyguardViewManager.qsExpansion
+ keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ val bouncer = keyguardViewManager.bouncer
+ bouncer?.expansion?.let {
+ bouncerExpansionCallback.onExpansionChanged(it)
+ bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ updateBouncerHiddenAmount()
+ }
+ configurationController.addCallback(configurationListener)
+ shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
+ updateScaleFactor()
+ view.updatePadding()
+ updateAlpha()
+ updatePauseAuth()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = this
+ activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ }
+
+ override fun onViewDetached() {
+ super.onViewDetached()
+ faceDetectRunning = false
+ keyguardStateController.removeCallback(keyguardStateControllerCallback)
+ statusBarStateController.removeCallback(stateListener)
+ keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ configurationController.removeCallback(configurationListener)
+ shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
+ if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) {
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = null
+ }
+ activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+ keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ super.dump(pw, args)
+ pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
+ pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
+ pw.println("faceDetectRunning=$faceDetectRunning")
+ pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
+ pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
+ pw.println("qsExpansion=$qsExpansion")
+ pw.println("panelExpansionFraction=$panelExpansionFraction")
+ pw.println("unpausedAlpha=" + view.unpausedAlpha)
+ pw.println("udfpsRequestedByApp=$udfpsRequested")
+ pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
+ pw.println("lastDozeAmount=$lastDozeAmount")
+ if (isModernBouncerEnabled) {
+ pw.println("inputBouncerExpansion=$inputBouncerExpansion")
+ } else {
+ pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
+ }
+ view.dump(pw)
+ }
+
+ /**
+ * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
+ * @return whether the udfpsBouncer has been newly shown or hidden
+ */
+ private fun showUdfpsBouncer(show: Boolean): Boolean {
+ if (showingUdfpsBouncer == show) {
+ return false
+ }
+ val udfpsAffordanceWasNotShowing = shouldPauseAuth()
+ showingUdfpsBouncer = show
+ if (showingUdfpsBouncer) {
+ lastUdfpsBouncerShowTime = systemClock.uptimeMillis()
+ }
+ if (showingUdfpsBouncer) {
+ if (udfpsAffordanceWasNotShowing) {
+ view.animateInUdfpsBouncer(null)
+ }
+ if (keyguardStateController.isOccluded) {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
+ }
+ view.announceForAccessibility(
+ view.context.getString(R.string.accessibility_fingerprint_bouncer)
+ )
+ } else {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ }
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ return true
+ }
+
+ /**
+ * Returns true if the fingerprint manager is running but we want to temporarily pause
+ * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
+ * this can be overridden with the showBouncer method.
+ */
+ override fun shouldPauseAuth(): Boolean {
+ if (showingUdfpsBouncer) {
+ return false
+ }
+ if (
+ udfpsRequested &&
+ !notificationShadeVisible &&
+ !isInputBouncerFullyVisible() &&
+ keyguardStateController.isShowing
+ ) {
+ return false
+ }
+ if (launchTransitionFadingAway) {
+ return true
+ }
+
+ // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
+ // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
+ // delayed. However, we still animate in the UDFPS affordance with the
+ // mUnlockedScreenOffDozeAnimator.
+ if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
+ return true
+ }
+ if (isBouncerExpansionGreaterThan(.5f)) {
+ return true
+ }
+ return view.unpausedAlpha < 255 * .1
+ }
+
+ fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion >= bouncerExpansionThreshold
+ } else {
+ inputBouncerHiddenAmount < bouncerExpansionThreshold
+ }
+ }
+
+ fun isInputBouncerFullyVisible(): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion == 1f
+ } else {
+ keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+ }
+ }
+
+ override fun listenForTouchesOutsideView(): Boolean {
+ return true
+ }
+
+ override fun onTouchOutsideView() {
+ maybeShowInputBouncer()
+ }
+
+ /**
+ * If we were previously showing the udfps bouncer, hide it and instead show the regular
+ * (pin/pattern/password) bouncer.
+ *
+ * Does nothing if we weren't previously showing the UDFPS bouncer.
+ */
+ private fun maybeShowInputBouncer() {
+ if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
+ keyguardViewManager.showBouncer(true)
+ }
+ }
+
+ /**
+ * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the
+ * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer.
+ */
+ private fun hasUdfpsBouncerShownWithMinTime(): Boolean {
+ return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200
+ }
+
+ /**
+ * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
+ * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
+ * to expand the notification shade from the empty space in the middle of the lock screen.
+ */
+ fun setTransitionToFullShadeProgress(progress: Float) {
+ transitionToFullShadeProgress = progress
+ updateAlpha()
+ }
+
+ /**
+ * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
+ * based on the doze amount.
+ */
+ override fun updateAlpha() {
+ // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
+ // then the keyguard is occluded by some application - so instead use the input bouncer
+ // hidden amount to determine the fade.
+ val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
+ var alpha: Int =
+ if (showingUdfpsBouncer) 255
+ else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
+ if (!showingUdfpsBouncer) {
+ // swipe from top of the lockscreen to expand full QS:
+ alpha =
+ (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
+ .toInt()
+
+ // swipe from the middle (empty space) of lockscreen to expand the notification shade:
+ alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
+
+ // Fade out the icon if we are animating an activity launch over the lockscreen and the
+ // activity didn't request the UDFPS.
+ if (isLaunchingActivity && !udfpsRequested) {
+ alpha = (alpha * (1.0f - activityLaunchProgress)).toInt()
+ }
+
+ // Fade out alpha when a dialog is shown
+ // Fade in alpha when a dialog is hidden
+ alpha = (alpha * view.dialogSuggestedAlpha).toInt()
+ }
+ view.unpausedAlpha = alpha
+ }
+
+ private fun getInputBouncerHiddenAmt(): Float {
+ return if (isModernBouncerEnabled) {
+ 1f - inputBouncerExpansion
+ } else {
+ inputBouncerHiddenAmount
+ }
+ }
+
+ /** Update the scale factor based on the device's resolution. */
+ private fun updateScaleFactor() {
+ udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
+ }
+
+ private fun updateBouncerHiddenAmount() {
+ if (isModernBouncerEnabled) {
+ return
+ }
+ val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
+ if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+ inputBouncerHiddenAmount = 1f
+ } else if (keyguardViewManager.isBouncerShowing) {
+ // input bouncer is fully showing
+ inputBouncerHiddenAmount = 0f
+ }
+ }
+
+ companion object {
+ const val TAG = "UdfpsKeyguardViewController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index da50f1c..f48cfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics
import android.content.Context
+import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
@@ -27,6 +28,11 @@
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.util.Log
import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -36,6 +42,8 @@
private const val TAG = "UdfpsShell"
private const val REQUEST_ID = 2L
private const val SENSOR_ID = 0
+private const val MINOR = 10F
+private const val MAJOR = 10F
/**
* Used to show and hide the UDFPS overlay with statusbar commands.
@@ -67,6 +75,12 @@
hideUdfpsOverlay()
} else if (args.size == 2 && args[0] == "show") {
showOverlay(getEnrollmentReason(args[1]))
+ } else if (args.size == 1 && args[0] == "onUiReady") {
+ onUiReady()
+ } else if (args.size == 1 && args[0] == "simFingerDown") {
+ simFingerDown()
+ } else if (args.size == 1 && args[0] == "simFingerUp") {
+ simFingerUp()
} else {
invalidCommand(pw)
}
@@ -80,6 +94,11 @@
"auth-keyguard, auth-other, auth-settings]")
pw.println(" -> reason otherwise defaults to unknown")
pw.println(" - hide")
+ pw.println(" - onUiReady")
+ pw.println(" - simFingerDown")
+ pw.println(" -> Simulates onFingerDown on sensor")
+ pw.println(" - simFingerUp")
+ pw.println(" -> Simulates onFingerUp on sensor")
}
private fun invalidCommand(pw: PrintWriter) {
@@ -125,4 +144,54 @@
private fun hideOverlay() {
udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
}
+
+
+ @VisibleForTesting
+ fun onUiReady() {
+ udfpsOverlayController?.debugOnUiReady(REQUEST_ID, SENSOR_ID)
+ }
+
+ @VisibleForTesting
+ fun simFingerDown() {
+ val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+ val downEvent: MotionEvent? = obtainMotionEvent(ACTION_DOWN, sensorBounds.exactCenterX(),
+ sensorBounds.exactCenterY(), MINOR, MAJOR)
+ udfpsOverlayController?.debugOnTouch(REQUEST_ID, downEvent)
+
+ val moveEvent: MotionEvent? = obtainMotionEvent(ACTION_MOVE, sensorBounds.exactCenterX(),
+ sensorBounds.exactCenterY(), MINOR, MAJOR)
+ udfpsOverlayController?.debugOnTouch(REQUEST_ID, moveEvent)
+
+ downEvent?.recycle()
+ moveEvent?.recycle()
+ }
+
+ @VisibleForTesting
+ fun simFingerUp() {
+ val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+ val upEvent: MotionEvent? = obtainMotionEvent(ACTION_UP, sensorBounds.exactCenterX(),
+ sensorBounds.exactCenterY(), MINOR, MAJOR)
+ udfpsOverlayController?.debugOnTouch(REQUEST_ID, upEvent)
+ upEvent?.recycle()
+ }
+
+ private fun obtainMotionEvent(
+ action: Int,
+ x: Float,
+ y: Float,
+ minor: Float,
+ major: Float
+ ): MotionEvent? {
+ val pp = MotionEvent.PointerProperties()
+ pp.id = 1
+ val pc = MotionEvent.PointerCoords()
+ pc.x = x
+ pc.y = y
+ pc.touchMinor = minor
+ pc.touchMajor = major
+ return MotionEvent.obtain(0, 0, action, 1, arrayOf(pp), arrayOf(pc),
+ 0, 0, 1f, 1f, 0, 0, 0, 0)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index fb01691..4eb444e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -25,6 +25,7 @@
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.screenshot.LongScreenshotActivity;
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
+import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -142,4 +143,11 @@
@ClassKey(HdmiCecSetMenuLanguageActivity.class)
public abstract Activity bindHdmiCecSetMenuLanguageActivity(
HdmiCecSetMenuLanguageActivity activity);
+
+ /** Inject into TvSensorPrivacyChangedActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvSensorPrivacyChangedActivity.class)
+ public abstract Activity bindTvSensorPrivacyChangedActivity(
+ TvSensorPrivacyChangedActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 139a8b7..25418c3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.INotificationManager;
@@ -137,6 +138,12 @@
@Provides
@Singleton
+ static AppOpsManager provideAppOpsManager(Context context) {
+ return context.getSystemService(AppOpsManager.class);
+ }
+
+ @Provides
+ @Singleton
static AudioManager provideAudioManager(Context context) {
return context.getSystemService(AudioManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b5d4ecd..2a94e52 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -239,11 +239,13 @@
@JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
// 1100 - windowing
+ @JvmField
@Keep
val WM_ENABLE_SHELL_TRANSITIONS =
SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
/** b/170163464: animate bubbles expanded view collapse with home gesture */
+ @JvmField
@Keep
val BUBBLES_HOME_GESTURE =
SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
@@ -265,40 +267,50 @@
@Keep
val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+ @JvmField
@Keep
val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+ @JvmField
@Keep
val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+ @JvmField
@Keep
val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
+ @JvmField
@Keep
val SHOW_FLOATING_TASKS_AS_BUBBLES =
SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
+ @JvmField
@Keep
val ENABLE_FLING_TO_DISMISS_BUBBLE =
SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+ @JvmField
@Keep
val ENABLE_FLING_TO_DISMISS_PIP =
SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+ @JvmField
@Keep
val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
// 1200 - predictive back
+ @JvmField
@Keep
val WM_ENABLE_PREDICTIVE_BACK =
SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+ @JvmField
@Keep
val WM_ENABLE_PREDICTIVE_BACK_ANIM =
SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+ @JvmField
@Keep
val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
@@ -325,7 +337,7 @@
@JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
// 1700 - clipboard
- @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
// 1800 - shade container
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 99ae85d..80c6130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -18,6 +18,7 @@
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -45,4 +46,9 @@
fun dispatchBackKeyEventPreIme(): Boolean
fun showNextSecurityScreenOrFinish(): Boolean
fun resume()
+ fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?,
+ )
+ fun willDismissWithActions(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 543389e..0046256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -21,10 +21,9 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -41,9 +40,15 @@
/** Determines if we want to instantaneously show the bouncer instead of translating. */
private val _isScrimmed = MutableStateFlow(false)
val isScrimmed = _isScrimmed.asStateFlow()
- /** Set amount of how much of the bouncer is showing on the screen */
- private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
- val expansionAmount = _expansionAmount.asStateFlow()
+ /**
+ * Set how much of the panel is showing on the screen.
+ * ```
+ * 0f = panel fully hidden = bouncer fully showing
+ * 1f = panel fully showing = bouncer fully hidden
+ * ```
+ */
+ private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
+ val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _isVisible = MutableStateFlow(false)
val isVisible = _isVisible.asStateFlow()
private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
@@ -54,8 +59,6 @@
val hide = _hide.asStateFlow()
private val _startingToHide = MutableStateFlow(false)
val startingToHide = _startingToHide.asStateFlow()
- private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
- val onDismissAction = _onDismissAction.asStateFlow()
private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
val startingDisappearAnimation = _disappearAnimation.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
@@ -96,8 +99,8 @@
_isScrimmed.value = isScrimmed
}
- fun setExpansion(expansion: Float) {
- _expansionAmount.value = expansion
+ fun setPanelExpansion(panelExpansion: Float) {
+ _panelExpansionAmount.value = panelExpansion
}
fun setVisible(isVisible: Boolean) {
@@ -120,10 +123,6 @@
_startingToHide.value = startingToHide
}
- fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
- _onDismissAction.value = bouncerCallbackActionsModel
- }
-
fun setStartDisappearAnimation(runnable: Runnable?) {
_disappearAnimation.value = runnable
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 6baaf5f..c867c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -23,9 +23,12 @@
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -65,6 +68,9 @@
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Observable for whether the bouncer is showing. */
+ val isBouncerShowing: Flow<Boolean>
+
/**
* Observable for whether we are in doze state.
*
@@ -95,6 +101,9 @@
/** Observable for device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel>
+ /** Observable for biometric unlock modes */
+ val biometricUnlockState: Flow<BiometricUnlockModel>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -125,6 +134,7 @@
private val keyguardStateController: KeyguardStateController,
dozeHost: DozeHost,
wakefulnessLifecycle: WakefulnessLifecycle,
+ biometricUnlockController: BiometricUnlockController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -159,6 +169,29 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onBouncerShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "updated isBouncerShowing"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "initial isBouncerShowing"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
override val isDozing: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
@@ -248,6 +281,24 @@
awaitClose { wakefulnessLifecycle.removeObserver(callback) }
}
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+ val callback =
+ object : BiometricUnlockController.BiometricModeListener {
+ override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+ trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+ }
+ }
+
+ biometricUnlockController.addBiometricModeListener(callback)
+ trySendWithFailureLogging(
+ biometricModeIntToObject(biometricUnlockController.getMode()),
+ TAG,
+ "initial biometric mode"
+ )
+
+ awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -279,6 +330,20 @@
}
}
+ private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+ return when (value) {
+ 0 -> BiometricUnlockModel.NONE
+ 1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
+ 2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ 3 -> BiometricUnlockModel.SHOW_BOUNCER
+ 4 -> BiometricUnlockModel.ONLY_WAKE
+ 5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
+ 6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ 7 -> BiometricUnlockModel.DISMISS_BOUNCER
+ else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..7e01db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodToGoneTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("AOD->GONE") {
+
+ private val wakeAndUnlockModes =
+ setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (biometricUnlockState, keyguardState) = pair
+ if (
+ keyguardState == KeyguardState.AOD &&
+ wakeAndUnlockModes.contains(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.AOD,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 2af9318..dbb0352 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -30,7 +30,6 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -40,6 +39,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -77,7 +77,7 @@
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
errorMessage = repository.bouncerErrorMessage,
- expansionAmount = repository.expansionAmount.value
+ expansionAmount = repository.panelExpansionAmount.value
)
)
repository.setShowingSoon(false)
@@ -90,14 +90,22 @@
val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
val isVisible: Flow<Boolean> = repository.isVisible
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
- val expansionAmount: Flow<Float> = repository.expansionAmount
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
repository.startingDisappearAnimation.filterNotNull()
- val onDismissAction: Flow<BouncerCallbackActionsModel> =
- repository.onDismissAction.filterNotNull()
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition
+ val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+ /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+ val bouncerExpansion: Flow<Float> = //
+ combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
+ ->
+ if (isVisible) {
+ 1f - expansionAmount
+ } else {
+ 0f
+ }
+ }
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
@@ -128,7 +136,7 @@
Trace.beginSection("KeyguardBouncer#show")
repository.setScrimmed(isScrimmed)
if (isScrimmed) {
- setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
}
if (resumeBouncer) {
@@ -149,7 +157,6 @@
}
keyguardStateController.notifyBouncerShowing(true)
callbackInteractor.dispatchStartingToShow()
-
Trace.endSection()
}
@@ -168,7 +175,6 @@
keyguardStateController.notifyBouncerShowing(false /* showing */)
cancelShowRunnable()
repository.setShowingSoon(false)
- repository.setOnDismissAction(null)
repository.setVisible(false)
repository.setHide(true)
repository.setShow(null)
@@ -176,14 +182,17 @@
}
/**
- * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
- * where 0f => showing and 1f => hiding
+ * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+ * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+ * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+ * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+ * of 0f represents the bouncer fully showing.
*/
- fun setExpansion(expansion: Float) {
- val oldExpansion = repository.expansionAmount.value
+ fun setPanelExpansion(expansion: Float) {
+ val oldExpansion = repository.panelExpansionAmount.value
val expansionChanged = oldExpansion != expansion
if (repository.startingDisappearAnimation.value == null) {
- repository.setExpansion(expansion)
+ repository.setPanelExpansion(expansion)
}
if (
@@ -227,7 +236,7 @@
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
- repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
/** Update the resources of the views. */
@@ -282,7 +291,7 @@
/** Returns whether bouncer is fully showing. */
fun isFullyShowing(): Boolean {
return (repository.showingSoon.value || repository.isVisible.value) &&
- repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+ repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
repository.startingDisappearAnimation.value == null
}
@@ -294,8 +303,8 @@
/** If bouncer expansion is between 0f and 1f non-inclusive. */
fun isInTransit(): Boolean {
return repository.showingSoon.value ||
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
}
/** Return whether bouncer is animating away. */
@@ -305,7 +314,7 @@
/** Return whether bouncer will dismiss with actions */
fun willDismissWithAction(): Boolean {
- return repository.onDismissAction.value?.onDismissAction != null
+ return bouncerView.delegate?.willDismissWithActions() == true
}
/** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 03c6a78..614ff8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -39,10 +41,19 @@
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
- /** Whether the keyguard is showing to not. */
+ /** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the bouncer is showing or not. */
+ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+ /** Observable for the [StatusBarState] */
+ val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /**
+ * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
+ * side, under display) is used to unlock the device.
+ */
+ val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 83d9432..57fb4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,11 +41,13 @@
}
scope.launch {
- interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) }
+ interactor.finishedKeyguardTransitionStep.collect {
+ logger.i("Finished transition", it)
+ }
}
scope.launch {
- interactor.startedKeyguardState.collect { logger.i("Started transition to", it) }
+ interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index d5ea77b..a7c6d44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -41,6 +41,7 @@
is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index dffd097..749183e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,7 +21,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,8 +43,9 @@
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
- /** GONE->AOD information. */
- val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+ /** (any)->AOD transition information */
+ val anyStateToAodTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == KeyguardState.AOD }
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -57,15 +57,15 @@
lockscreenToAodTransition,
)
+ /* The last [TransitionStep] with a [TransitionState] of FINISHED */
+ val finishedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.FINISHED }
- .map { step -> step.to }
+ finishedKeyguardTransitionStep.map { step -> step.to }
- /* The last started [KeyguardState] transition */
- val startedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.STARTED }
- .map { step -> step.to }
+ /* The last [TransitionStep] with a [TransitionState] of STARTED */
+ val startedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 761f3fd..fd4814d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -16,14 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
@@ -38,7 +40,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
- private val keyguardRepository: KeyguardRepository,
+ private val keyguardInteractor: KeyguardInteractor,
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -47,12 +49,47 @@
private var transitionId: UUID? = null
override fun start() {
+ listenForDraggingUpToBouncer()
+ listenForBouncerHiding()
+ }
+
+ private fun listenForBouncerHiding() {
+ scope.launch {
+ keyguardInteractor.isBouncerShowing
+ .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isBouncerShowing, wakefulnessState) = pair
+ if (!isBouncerShowing) {
+ val to =
+ if (
+ wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
+ wakefulnessState == WakefulnessModel.ASLEEP
+ ) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.BOUNCER,
+ to = to,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+ private fun listenForDraggingUpToBouncer() {
scope.launch {
shadeRepository.shadeModel
.sample(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
- keyguardRepository.statusBarState,
+ keyguardInteractor.statusBarState,
) { keyguardState, statusBarState ->
Pair(keyguardState, statusBarState)
},
@@ -100,4 +137,15 @@
}
}
}
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 728bafa..37f33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -42,6 +42,8 @@
@Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+
@Binds
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
new file mode 100644
index 0000000..db709b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Model device wakefulness states. */
+enum class BiometricUnlockModel {
+ /** Mode in which we don't need to wake up the device when we authenticate. */
+ NONE,
+ /**
+ * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a
+ * fingerprint while the screen is off and the device was sleeping.
+ */
+ WAKE_AND_UNLOCK,
+ /**
+ * Mode in which we wake the device up, and fade out the Keyguard contents because they were
+ * already visible while pulsing in doze mode.
+ */
+ WAKE_AND_UNLOCK_PULSING,
+ /**
+ * Mode in which we wake up the device, but play the normal dismiss animation. Active when we
+ * acquire a fingerprint pulsing in doze mode.
+ */
+ SHOW_BOUNCER,
+ /**
+ * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
+ */
+ ONLY_WAKE,
+ /**
+ * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
+ * device while being requested when keyguard is occluded or showing.
+ */
+ UNLOCK_COLLAPSING,
+ /** When bouncer is visible and will be dismissed. */
+ DISMISS_BOUNCER,
+ /** Mode in which fingerprint wakes and unlocks the device from a dream. */
+ WAKE_AND_UNLOCK_FROM_DREAM,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0ca3582..732a6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -21,10 +21,11 @@
val to: KeyguardState = KeyguardState.NONE,
val value: Float = 0f, // constrained [0.0, 1.0]
val transitionState: TransitionState = TransitionState.FINISHED,
+ val ownerName: String = "",
) {
constructor(
info: TransitionInfo,
value: Float,
transitionState: TransitionState,
- ) : this(info.from, info.to, value, transitionState)
+ ) : this(info.from, info.to, value, transitionState, info.ownerName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index df26014..a22958b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.collect
@@ -75,6 +76,17 @@
hostViewController.showPrimarySecurityScreen()
hostViewController.onResume()
}
+
+ override fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?
+ ) {
+ hostViewController.setOnDismissAction(onDismissAction, cancelAction)
+ }
+
+ override fun willDismissWithActions(): Boolean {
+ return hostViewController.hasDismissActions()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,15 +134,6 @@
}
launch {
- viewModel.setDismissAction.collect {
- hostViewController.setOnDismissAction(
- it.onDismissAction,
- it.cancelAction
- )
- }
- }
-
- launch {
viewModel.startDisappearAnimation.collect {
hostViewController.startDisappearAnimation(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9ad5211..9a92843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,7 +20,6 @@
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -38,7 +37,7 @@
private val interactor: BouncerInteractor,
) {
/** Observe on bouncer expansion amount. */
- val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+ val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
@@ -63,9 +62,6 @@
/** Observe whether bouncer is starting to hide. */
val startingToHide: Flow<Unit> = interactor.startingToHide
- /** Observe whether we want to set the dismiss action to the bouncer. */
- val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
-
/** Observe whether we want to start the disappear animation. */
val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50cf63d..b9f5859 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -87,14 +87,18 @@
import android.view.HapticFeedbackConstants;
import android.view.InsetsFrameProvider;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -366,15 +370,6 @@
}
@Override
- public void onQuickStepStarted() {
- // Use navbar dragging as a signal to hide the rotate button
- mView.getRotationButtonController().setRotateSuggestionButtonState(false);
-
- // Hide the notifications panel when quick step starts
- mShadeController.collapsePanel(true /* animate */);
- }
-
- @Override
public void onPrioritizedRotation(@Surface.Rotation int rotation) {
mStartingQuickSwitchRotation = rotation;
if (rotation == -1) {
@@ -487,6 +482,24 @@
}
};
+ private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
+ new SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceReplaced(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+ };
+
@Inject
NavigationBar(
NavigationBarView navigationBarView,
@@ -696,7 +709,8 @@
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ mView.setComponents(
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController());
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -717,6 +731,8 @@
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
mOnComputeInternalInsetsListener);
+ mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+ notifyNavigationBarSurface();
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -795,6 +811,10 @@
mHandler.removeCallbacks(mEnableLayoutTransitions);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+ }
mFrame = null;
mOrientationHandle = null;
}
@@ -947,6 +967,12 @@
}
}
+ private void notifyNavigationBarSurface() {
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null;
+ mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
+ }
+
private int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
@@ -1059,7 +1085,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
@@ -1272,8 +1298,8 @@
}
private void onVerticalChanged(boolean isVertical) {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- statusBar -> statusBar.setQsScrimEnabled(!isVertical));
+ mCentralSurfacesOptionalLazy.get().ifPresent(statusBar ->
+ statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical));
}
private boolean onNavigationTouch(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 73fc21e..eb87ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -49,8 +49,8 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -355,7 +355,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
- InsetsVisibilities requestedVisibilities, String packageName,
+ @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
boolean nbModeChanged = false;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 622f5a2..83c2a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -412,10 +412,6 @@
logSomePresses(action, flags);
if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
- if (action == MotionEvent.ACTION_UP) {
- mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
- -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
- }
}
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
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 10ff48b..7964d16 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -289,8 +289,6 @@
mBackAnimation.setTriggerBack(true);
}
- mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
logGesture(mInRejectedExclusion
? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
@@ -302,8 +300,6 @@
mBackAnimation.setTriggerBack(false);
}
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
- mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
}
@Override
@@ -804,9 +800,6 @@
if (mExcludeRegion.contains(x, y)) {
if (withinRange) {
- // Log as exclusion only if it is in acceptable range in the first place.
- mOverviewProxyService.notifyBackAction(
- false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
// We don't have the end point for logging purposes.
mEndPoint.x = -1;
mEndPoint.y = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 703b95a..b5ceeae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -19,6 +19,7 @@
import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
@@ -33,6 +34,7 @@
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.util.LargeScreenUtils;
import java.util.Objects;
@@ -72,6 +74,7 @@
mMobileSignal = findViewById(R.id.mobile_signal);
mCarrierText = findViewById(R.id.qs_carrier_text);
mSpacer = findViewById(R.id.spacer);
+ updateResources();
}
/**
@@ -142,4 +145,20 @@
public void updateTextAppearance(@StyleRes int resId) {
FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId);
}
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateResources();
+ }
+
+ private void updateResources() {
+ boolean useLargeScreenHeader =
+ LargeScreenUtils.shouldUseLargeScreenShadeHeader(getResources());
+ mCarrierText.setMaxEms(
+ useLargeScreenHeader
+ ? Integer.MAX_VALUE
+ : getResources().getInteger(R.integer.qs_carrier_max_em)
+ );
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e9a6c25..1f92b12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,7 +140,7 @@
iv.setTag(R.id.qs_icon_tag, icon);
iv.setTag(R.id.qs_slash_tag, state.slash);
iv.setPadding(0, padding, 0, padding);
- if (d instanceof Animatable2) {
+ if (shouldAnimate && d instanceof Animatable2) {
Animatable2 a = (Animatable2) d;
a.start();
if (state.isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66be00d..46c4f41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,6 +64,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
@@ -149,6 +150,7 @@
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
+ private SurfaceControl mNavigationBarSurface;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
@@ -190,7 +192,8 @@
// TODO move this logic to message queue
mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
if (event.getActionMasked() == ACTION_DOWN) {
- centralSurfaces.getPanelController().startExpandLatencyTracking();
+ centralSurfaces.getNotificationPanelViewController()
+ .startExpandLatencyTracking();
}
mHandler.post(() -> {
int action = event.getActionMasked();
@@ -217,17 +220,15 @@
}
@Override
- public void onBackPressed() throws RemoteException {
+ public void onBackPressed() {
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-
- notifyBackAction(true, -1, -1, true, false);
});
}
@Override
- public void onImeSwitcherPressed() throws RemoteException {
+ public void onImeSwitcherPressed() {
// TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
// Launcher/Taskbar isn't display aware.
mContext.getSystemService(InputMethodManager.class)
@@ -316,12 +317,6 @@
}
@Override
- public void notifySwipeUpGestureStarted() {
- verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
- notifySwipeUpGestureStartedInternal());
- }
-
- @Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
notifyPrioritizedRotationInternal(rotation));
@@ -443,6 +438,7 @@
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
+ dispatchNavigationBarSurface();
// Force-update the systemui state flags
updateSystemUiStateFlags();
@@ -597,11 +593,18 @@
.commitUpdate(mContext.getDisplayId());
}
- public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) {
+ /**
+ * Called when the navigation bar surface is created or changed
+ */
+ public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
+ mNavigationBarSurface = navbarSurface;
+ dispatchNavigationBarSurface();
+ }
+
+ private void dispatchNavigationBarSurface() {
try {
if (mOverviewProxy != null) {
- mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+ mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify back action", e);
@@ -614,7 +617,7 @@
final NavigationBarView navBarView =
mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
final NotificationPanelViewController panelController =
- mCentralSurfacesOptionalLazy.get().get().getPanelController();
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController();
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
+ " navBarView=" + navBarView + " panelController=" + panelController);
@@ -800,24 +803,12 @@
}
}
- public void notifyQuickStepStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickStepStarted();
- }
- }
-
private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
}
}
- public void notifyQuickScrubStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickScrubStarted();
- }
- }
-
private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onAssistantProgress(progress);
@@ -836,12 +827,6 @@
}
}
- private void notifySwipeUpGestureStartedInternal() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
- }
- }
-
public void notifyAssistantVisibilityChanged(float visibility) {
try {
if (mOverviewProxy != null) {
@@ -1005,23 +990,20 @@
pw.print(" mWindowCornerRadius="); pw.println(mWindowCornerRadius);
pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
+ pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
mSysUiState.dump(pw, args);
}
public interface OverviewProxyListener {
default void onConnectionChanged(boolean isConnected) {}
- default void onQuickStepStarted() {}
- default void onSwipeUpGestureStarted() {}
default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
default void onOverviewShown(boolean fromHome) {}
- default void onQuickScrubStarted() {}
/** Notify the recents app (overview) is started by 3-button navigation. */
default void onToggleRecentApps() {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
default void onTaskbarAutohideSuspend(boolean suspend) {}
- default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
default void startAssistant(Bundle bundle) {}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
new file mode 100644
index 0000000..731b177
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.sensorprivacy.television;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+
+import android.annotation.DimenRes;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.hardware.SensorPrivacyManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.tv.TvBottomSheetActivity;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import javax.inject.Inject;
+
+/**
+ * Bottom sheet that is shown when the camera/mic sensors privacy state changed
+ * by the global software toggle or physical privacy switch.
+ */
+public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity {
+
+ private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName();
+
+ private static final int ALL_SENSORS = Integer.MAX_VALUE;
+
+ private int mSensor = -1;
+ private int mToggleType = -1;
+
+ private final GlobalSettings mGlobalSettings;
+ private final IndividualSensorPrivacyController mSensorPrivacyController;
+ private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
+ private TextView mTitle;
+ private TextView mContent;
+ private ImageView mIcon;
+ private ImageView mSecondIcon;
+ private Button mPositiveButton;
+ private Button mCancelButton;
+
+ @Inject
+ public TvSensorPrivacyChangedActivity(
+ IndividualSensorPrivacyController individualSensorPrivacyController,
+ GlobalSettings globalSettings) {
+ mSensorPrivacyController = individualSensorPrivacyController;
+ mGlobalSettings = globalSettings;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
+ false);
+ if (allSensors) {
+ mSensor = ALL_SENSORS;
+ } else {
+ mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
+ }
+
+ mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1);
+
+ if (mSensor == -1 || mToggleType == -1) {
+ Log.v(TAG, "Invalid extras");
+ finish();
+ return;
+ }
+
+ // Do not show for software toggles
+ if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
+ finish();
+ return;
+ }
+
+ mSensorPrivacyCallback = (sensor, blocked) -> {
+ updateUI();
+ };
+
+ initUI();
+ }
+
+ private void initUI() {
+ mTitle = findViewById(R.id.bottom_sheet_title);
+ mContent = findViewById(R.id.bottom_sheet_body);
+ mIcon = findViewById(R.id.bottom_sheet_icon);
+ // mic icon if both icons are shown
+ mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
+ mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
+ mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+ mCancelButton.setText(android.R.string.cancel);
+ mCancelButton.setOnClickListener(v -> finish());
+
+ updateUI();
+ }
+
+ private void updateUI() {
+ final Resources resources = getResources();
+ setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
+ setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
+
+ switch (mSensor) {
+ case CAMERA:
+ updateUiForCameraUpdate(
+ mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA));
+ break;
+ case MICROPHONE:
+ default:
+ updateUiForMicUpdate(
+ mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE));
+ break;
+ }
+
+ // Start animation if drawable is animated
+ Drawable iconDrawable = mIcon.getDrawable();
+ if (iconDrawable instanceof Animatable) {
+ ((Animatable) iconDrawable).start();
+ }
+
+ mPositiveButton.setVisibility(View.GONE);
+ mCancelButton.setText(android.R.string.ok);
+ }
+
+ private void updateUiForMicUpdate(boolean blocked) {
+ if (blocked) {
+ mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title);
+ if (isExplicitUserInteractionAudioBypassAllowed()) {
+ mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content);
+ } else {
+ mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content);
+ }
+ mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
+ mSecondIcon.setVisibility(View.GONE);
+ } else {
+ mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title);
+ mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content);
+ mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed);
+ mSecondIcon.setVisibility(View.GONE);
+ }
+ }
+
+ private void updateUiForCameraUpdate(boolean blocked) {
+ if (blocked) {
+ mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title);
+ mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content);
+ mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
+ mSecondIcon.setVisibility(View.GONE);
+ } else {
+ mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title);
+ mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content);
+ mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed);
+ mSecondIcon.setVisibility(View.GONE);
+ }
+ }
+
+ private void setIconTint(boolean enableTint) {
+ final Resources resources = getResources();
+
+ if (enableTint) {
+ final ColorStateList iconTint = resources.getColorStateList(
+ R.color.bottom_sheet_icon_color, getTheme());
+ mIcon.setImageTintList(iconTint);
+ mSecondIcon.setImageTintList(iconTint);
+ } else {
+ mIcon.setImageTintList(null);
+ mSecondIcon.setImageTintList(null);
+ }
+
+ mIcon.invalidate();
+ mSecondIcon.invalidate();
+ }
+
+ private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
+ final Resources resources = getResources();
+ final int iconWidth = resources.getDimensionPixelSize(widthRes);
+ final int iconHeight = resources.getDimensionPixelSize(heightRes);
+
+ mIcon.getLayoutParams().width = iconWidth;
+ mIcon.getLayoutParams().height = iconHeight;
+ mIcon.invalidate();
+
+ mSecondIcon.getLayoutParams().width = iconWidth;
+ mSecondIcon.getLayoutParams().height = iconHeight;
+ mSecondIcon.invalidate();
+ }
+
+ private boolean isExplicitUserInteractionAudioBypassAllowed() {
+ return mGlobalSettings.getInt(
+ Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateUI();
+ mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
+ }
+
+ @Override
+ public void onPause() {
+ mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
+ super.onPause();
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index d543eb2..1b9657f 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -16,17 +16,23 @@
package com.android.systemui.sensorprivacy.television;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.hardware.SensorPrivacyManager.Sources.OTHER;
import android.annotation.DimenRes;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.hardware.SensorPrivacyManager;
import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -39,6 +45,8 @@
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.tv.TvBottomSheetActivity;
+import java.util.List;
+
import javax.inject.Inject;
/**
@@ -57,6 +65,8 @@
private int mSensor = -1;
+ private final AppOpsManager mAppOpsManager;
+ private final RoleManager mRoleManager;
private final IndividualSensorPrivacyController mSensorPrivacyController;
private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
private TextView mTitle;
@@ -68,8 +78,11 @@
@Inject
public TvUnblockSensorActivity(
- IndividualSensorPrivacyController individualSensorPrivacyController) {
+ IndividualSensorPrivacyController individualSensorPrivacyController,
+ AppOpsManager appOpsManager, RoleManager roleManager) {
mSensorPrivacyController = individualSensorPrivacyController;
+ mAppOpsManager = appOpsManager;
+ mRoleManager = roleManager;
}
@Override
@@ -120,6 +133,10 @@
toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
break;
}
+ showToastAndFinish(toastMsgResId);
+ }
+
+ private void showToastAndFinish(int toastMsgResId) {
Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
finish();
}
@@ -149,7 +166,9 @@
}
private void updateUI() {
- if (isBlockedByHardwareToggle()) {
+ if (isHTTAccessDisabled()) {
+ updateUiForHTT();
+ } else if (isBlockedByHardwareToggle()) {
updateUiForHardwareToggle();
} else {
updateUiForSoftwareToggle();
@@ -208,20 +227,20 @@
switch (mSensor) {
case MICROPHONE:
- mTitle.setText(R.string.sensor_privacy_start_use_mic_dialog_title);
+ mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
mSecondIcon.setVisibility(View.GONE);
break;
case CAMERA:
- mTitle.setText(R.string.sensor_privacy_start_use_camera_dialog_title);
+ mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
mSecondIcon.setVisibility(View.GONE);
break;
case ALL_SENSORS:
default:
- mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title);
+ mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
mSecondIcon.setImageResource(
@@ -241,6 +260,29 @@
});
}
+ private void updateUiForHTT() {
+ setIconTint(true);
+ setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
+
+ mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
+ mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content);
+ mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
+ mSecondIcon.setVisibility(View.GONE);
+
+ mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings);
+ mPositiveButton.setOnClickListener(v -> {
+ Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY);
+ ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(),
+ MATCH_SYSTEM_ONLY);
+ if (activityInfo == null) {
+ showToastAndFinish(com.android.internal.R.string.noApplications);
+ } else {
+ startActivity(openPrivacySettings);
+ finish();
+ }
+ });
+ }
+
private void setIconTint(boolean enableTint) {
final Resources resources = getResources();
@@ -272,6 +314,18 @@
mSecondIcon.invalidate();
}
+ private boolean isHTTAccessDisabled() {
+ String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ List<String> assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT);
+ if (!assistantPkgs.contains(pkg)) {
+ return false;
+ }
+
+ return (mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(),
+ pkg) != AppOpsManager.MODE_ALLOWED);
+ }
+
@Override
public void onResume() {
super.onResume();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 2450197..3a624ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -531,7 +531,7 @@
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
- private KeyguardIndicationController mKeyguardIndicationController;
+ private final KeyguardIndicationController mKeyguardIndicationController;
private int mHeadsUpInset;
private boolean mHeadsUpPinnedMode;
private boolean mAllowExpandForSmallExpansion;
@@ -743,6 +743,7 @@
SysUiState sysUiState,
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@@ -779,6 +780,7 @@
mResources = mView.getResources();
mKeyguardStateController = keyguardStateController;
+ mKeyguardIndicationController = keyguardIndicationController;
mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mNotificationShadeWindowController = notificationShadeWindowController;
FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get();
@@ -1020,7 +1022,7 @@
mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
- mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+ setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
initBottomArea();
@@ -1264,7 +1266,7 @@
int index = mView.indexOfChild(mKeyguardBottomArea);
mView.removeView(mKeyguardBottomArea);
KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
- mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView();
+ setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
mKeyguardBottomArea.initFrom(oldBottomArea);
mView.addView(mKeyguardBottomArea, index);
initBottomArea();
@@ -1343,8 +1345,8 @@
return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
}
- public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
- mKeyguardIndicationController = indicationController;
+ private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
+ mKeyguardBottomArea = keyguardBottomArea;
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
}
@@ -3880,6 +3882,7 @@
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
NotificationPanelViewController.this);
@@ -4361,10 +4364,6 @@
mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
- public ShadeHeadsUpChangedListener getOnHeadsUpChangedListener() {
- return mOnHeadsUpChangedListener;
- }
-
public void setHeaderDebugInfo(String text) {
if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
}
@@ -4644,7 +4643,7 @@
mUpdateFlingVelocity = vel;
}
} else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
onEmptySpaceClick();
onTrackingStopped(true);
@@ -5682,6 +5681,7 @@
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
+ mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
if (SPEW_LOGCAT) {
Log.v(TAG,
"NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
@@ -5694,6 +5694,8 @@
// Do not let touches go to shade or QS if the bouncer is visible,
// but still let user swipe down to expand the panel, dismissing the bouncer.
if (mCentralSurfaces.isBouncerShowing()) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "bouncer is showing");
return true;
}
if (mCommandQueue.panelsEnabled()
@@ -5701,15 +5703,21 @@
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "HeadsUpTouchHelper");
return true;
}
if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
&& mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "PulseExpansionHandler");
return true;
}
if (!isFullyCollapsed() && onQsIntercept(event)) {
debugLog("onQsIntercept true");
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "QsIntercept");
return true;
}
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -5740,6 +5748,9 @@
if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
cancelHeightAnimator();
mTouchSlopExceeded = true;
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
+ + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:"
+ + " false");
return true;
}
mInitialExpandY = y;
@@ -5784,6 +5795,8 @@
&& hAbs > Math.abs(x - mInitialExpandX)) {
cancelHeightAnimator();
startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ mShadeLog.v("NotificationPanelViewController MotionEvent"
+ + " intercepted: startExpandMotion");
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 65bd58d..1e63b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -284,7 +284,7 @@
return true;
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// capture all touches if the alt auth bouncer is showing
return true;
}
@@ -322,7 +322,7 @@
handled = !mService.isPulsing();
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// eat the touch
handled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index 084b7dc..bf622c9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -52,6 +52,7 @@
private val centralSurfaces: CentralSurfaces,
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val statusBarStateController: StatusBarStateController,
+ private val shadeLogger: ShadeLogger,
tunerService: TunerService,
dumpManager: DumpManager
) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -77,18 +78,23 @@
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
- if (statusBarStateController.isDozing &&
- singleTapEnabled &&
- !dockManager.isDocked &&
- !falsingManager.isProximityNear &&
- !falsingManager.isFalseTap(LOW_PENALTY)
- ) {
- centralSurfaces.wakeUpIfDozing(
+ val isNotDocked = !dockManager.isDocked
+ shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked)
+ if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) {
+ val proximityIsNotNear = !falsingManager.isProximityNear
+ val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY)
+ shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
+ if (proximityIsNotNear && isNotAFalseTap) {
+ shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
+ centralSurfaces.wakeUpIfDozing(
SystemClock.uptimeMillis(),
notificationShadeWindowView,
- "PULSING_SINGLE_TAP")
+ "PULSING_SINGLE_TAP"
+ )
+ }
return true
}
+ shadeLogger.d("onSingleTapUp event ignored")
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index f389dd9..eaf7fae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -224,6 +224,6 @@
}
private NotificationPanelViewController getNotificationPanelViewController() {
- return getCentralSurfaces().getPanelController();
+ return getCentralSurfaces().getNotificationPanelViewController();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 7f1bba3..7615301 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -16,6 +16,10 @@
buffer.log(TAG, LogLevel.VERBOSE, msg)
}
+ fun d(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, LogLevel.DEBUG, msg)
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
@@ -123,4 +127,25 @@
"animatingQs=$long1"
})
}
+
+ fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = isDozing
+ bool2 = singleTapEnabled
+ bool3 = isNotDocked
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
+ })
+ }
+
+ fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = proximityIsNotNear
+ bool2 = isNotFalseTap
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4ae0f6a..f786ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -39,7 +39,7 @@
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
@@ -53,7 +53,7 @@
import android.util.Pair;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -154,7 +154,7 @@
//TODO(b/169175022) Update name and when feature name is locked.
private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT;
private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
- private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
+ private static final int MSG_SET_UDFPS_REFRESH_RATE_CALLBACK = 60 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT;
private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT;
@@ -333,9 +333,9 @@
}
/**
- * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+ * @see IStatusBar#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)
*/
- default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ default void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
}
/**
@@ -356,11 +356,11 @@
default void onRecentsAnimationStateChanged(boolean running) { }
/**
- * @see IStatusBar#onSystemBarAttributesChanged.
+ * @see IStatusBar#onSystemBarAttributesChanged
*/
default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName, LetterboxDetails[] letterboxDetails) { }
/**
@@ -1017,9 +1017,9 @@
}
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
+ mHandler.obtainMessage(MSG_SET_UDFPS_REFRESH_RATE_CALLBACK, callback).sendToTarget();
}
}
@@ -1090,7 +1090,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
@@ -1099,7 +1099,7 @@
args.argi3 = navbarColorManagedByIme ? 1 : 0;
args.arg1 = appearanceRegions;
args.argi4 = behavior;
- args.arg2 = requestedVisibilities;
+ args.argi5 = requestedVisibleTypes;
args.arg3 = packageName;
args.arg4 = letterboxDetails;
mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
@@ -1546,9 +1546,10 @@
(IBiometricContextListener) msg.obj);
}
break;
- case MSG_SET_UDFPS_HBM_LISTENER:
+ case MSG_SET_UDFPS_REFRESH_RATE_CALLBACK:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
+ mCallbacks.get(i).setUdfpsRefreshRateCallback(
+ (IUdfpsRefreshRateRequestCallback) msg.obj);
}
break;
case MSG_SHOW_CHARGING_ANIMATION:
@@ -1581,8 +1582,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
(AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
- (InsetsVisibilities) args.arg2, (String) args.arg3,
- (LetterboxDetails[]) args.arg4);
+ args.argi5, (String) args.arg3, (LetterboxDetails[]) args.arg4);
}
args.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 87ef92a..408293c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -496,9 +496,6 @@
return;
}
- final float smallCornerRadius =
- getResources().getDimension(R.dimen.notification_corner_radius_small)
- / getResources().getDimension(R.dimen.notification_corner_radius);
final float viewEnd = viewStart + anv.getActualHeight();
final float cornerAnimationDistance = mCornerAnimationDistance
* mAmbientState.getExpansionFraction();
@@ -509,7 +506,7 @@
final float changeFraction = MathUtils.saturate(
(viewEnd - cornerAnimationTop) / cornerAnimationDistance);
anv.requestBottomRoundness(
- anv.isLastInSection() ? 1f : changeFraction,
+ /* value = */ anv.isLastInSection() ? 1f : changeFraction,
/* animate = */ false,
SourceType.OnScroll);
@@ -517,7 +514,7 @@
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
anv.requestBottomRoundness(
- anv.isLastInSection() ? 1f : smallCornerRadius,
+ /* value = */ anv.isLastInSection() ? 1f : 0f,
/* animate = */ false,
SourceType.OnScroll);
}
@@ -527,16 +524,16 @@
final float changeFraction = MathUtils.saturate(
(viewStart - cornerAnimationTop) / cornerAnimationDistance);
anv.requestTopRoundness(
- anv.isFirstInSection() ? 1f : changeFraction,
- false,
+ /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
+ /* animate = */ false,
SourceType.OnScroll);
} else if (viewStart < cornerAnimationTop) {
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
anv.requestTopRoundness(
- anv.isFirstInSection() ? 1f : smallCornerRadius,
- false,
+ /* value = */ anv.isFirstInSection() ? 1f : 0f,
+ /* animate = */ false,
SourceType.OnScroll);
}
}
@@ -976,6 +973,16 @@
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
+ /**
+ * This method resets the OnScroll roundness of a view to 0f
+ *
+ * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
+ */
+ public static void resetOnScrollRoundness(ExpandableView expandableView) {
+ expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
+ expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+ }
+
public class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index c04bc82..fdec745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
@@ -34,9 +32,10 @@
import android.util.Log;
import android.view.Choreographer;
import android.view.InsetsFlags;
-import android.view.InsetsVisibilities;
import android.view.View;
import android.view.ViewDebug;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.animation.Interpolator;
@@ -497,9 +496,9 @@
@Override
public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
- InsetsVisibilities requestedVisibilities, String packageName) {
- boolean isFullscreen = !requestedVisibilities.getVisibility(ITYPE_STATUS_BAR)
- || !requestedVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
+ @InsetsType int requestedVisibleTypes, String packageName) {
+ boolean isFullscreen = (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0
+ || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
if (mIsFullscreen != isFullscreen) {
mIsFullscreen = isFullscreen;
synchronized (mListeners) {
@@ -514,12 +513,12 @@
if (DEBUG_IMMERSIVE_APPS) {
boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
- String requestedVisibilityString = requestedVisibilities.toString();
- if (requestedVisibilityString.isEmpty()) {
- requestedVisibilityString = "none";
+ String requestedVisibleTypesString = WindowInsets.Type.toString(requestedVisibleTypes);
+ if (requestedVisibleTypesString.isEmpty()) {
+ requestedVisibleTypesString = "none";
}
Log.d(TAG, packageName + " dim=" + dim + " behavior=" + behaviorName
- + " requested visibilities: " + requestedVisibilityString);
+ + " requested visible types: " + requestedVisibleTypesString);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 2cc7738..1189107 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,8 +19,8 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -154,7 +154,7 @@
* Set the system bar attributes
*/
void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
- InsetsVisibilities requestedVisibilities, String packageName);
+ @InsetsType int requestedVisibleTypes, String packageName);
/**
* Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 26f0ad9..0554fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -44,6 +44,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.NotificationGroupingUtil;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -308,6 +309,11 @@
row.setContentTransformationAmount(0, false /* isLastChild */);
row.setNotificationFaded(mContainingNotificationIsFaded);
+
+ // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
+ // Here we should reset the `OnScroll` roundness only on top-level rows.
+ NotificationShelf.resetOnScrollRoundness(row);
+
// It doesn't make sense to keep old animations around, lets cancel them!
ExpandableViewState viewState = row.getViewState();
if (viewState != null) {
@@ -1377,8 +1383,12 @@
if (child.getVisibility() == View.GONE) {
continue;
}
+ child.requestTopRoundness(
+ /* value = */ 0f,
+ /* animate = */ isShown(),
+ SourceType.DefaultValue);
child.requestBottomRoundness(
- last ? getBottomRoundness() : 0f,
+ /* value = */ last ? getBottomRoundness() : 0f,
/* animate = */ isShown(),
SourceType.DefaultValue);
last = false;
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 c4ef28e..2c3330e 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
@@ -114,6 +114,8 @@
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.LargeScreenUtils;
+import com.google.errorprone.annotations.CompileTimeConstant;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -3693,6 +3695,8 @@
@ShadeViewRefactor(RefactorComponent.INPUT)
void handleEmptySpaceClick(MotionEvent ev) {
+ logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
+ mStatusBarState, mTouchIsClick);
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
final float touchSlop = getTouchSlop(ev);
@@ -3704,12 +3708,34 @@
case MotionEvent.ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
+ debugLog("handleEmptySpaceClick: touch event propagated further");
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
break;
+ default:
+ debugLog("handleEmptySpaceClick: MotionEvent ignored");
}
}
+ private void debugLog(@CompileTimeConstant String s) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.d(s);
+ }
+
+ private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
+ int statusBarState, boolean touchIsClick) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.logEmptySpaceClick(
+ isTouchBelowLastNotification,
+ statusBarState,
+ touchIsClick,
+ MotionEvent.actionToString(ev.getActionMasked()));
+ }
+
@ShadeViewRefactor(RefactorComponent.INPUT)
void initDownStates(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 4c52db7..64dd6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -2,6 +2,7 @@
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
@@ -10,6 +11,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
+import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@@ -56,6 +58,25 @@
"key: $str1 expected: $bool1 actual: $bool2"
})
}
+
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+
+ fun logEmptySpaceClick(
+ isBelowLastNotification: Boolean,
+ statusBarState: Int,
+ touchIsClick: Boolean,
+ motionEventDesc: String
+ ) {
+ buffer.log(TAG, DEBUG, {
+ int1 = statusBarState
+ bool1 = touchIsClick
+ bool2 = isBelowLastNotification
+ str1 = motionEventDesc
+ }, {
+ "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " +
+ "isTouchBelowNotification: $bool2 motionEvent: $str1"
+ })
+ }
}
private const val TAG = "NotificationStackScroll"
\ No newline at end of file
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 9900e41..2f7d344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -64,8 +64,10 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -163,7 +165,7 @@
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
- private BiometricModeListener mBiometricModeListener;
+ private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
@@ -305,9 +307,14 @@
mKeyguardViewController = keyguardViewController;
}
- /** Sets a {@link BiometricModeListener}. */
- public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
- mBiometricModeListener = biometricModeListener;
+ /** Adds a {@link BiometricModeListener}. */
+ public void addBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.add(listener);
+ }
+
+ /** Removes a {@link BiometricModeListener}. */
+ public void removeBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.remove(listener);
}
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -481,15 +488,12 @@
break;
}
onModeChanged(mMode);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.notifyBiometricAuthModeChanged();
- }
Trace.endSection();
}
private void onModeChanged(@WakeAndUnlockMode int mode) {
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onModeChanged(mode);
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onModeChanged(mode);
}
}
@@ -696,9 +700,8 @@
mMode = MODE_NONE;
mBiometricType = null;
mNotificationShadeWindowController.setForceDozeBrightness(false);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onResetMode();
- mBiometricModeListener.notifyBiometricAuthModeChanged();
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onResetMode();
}
mNumConsecutiveFpFailures = 0;
mLastFpFailureUptimeMillis = 0;
@@ -807,10 +810,8 @@
/** An interface to interact with the {@link BiometricUnlockController}. */
public interface BiometricModeListener {
/** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
- void onResetMode();
+ default void onResetMode() {}
/** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
- void onModeChanged(@WakeAndUnlockMode int mode);
- /** Called after processing {@link #onModeChanged(int)}. */
- void notifyBiometricAuthModeChanged();
+ default void onModeChanged(@WakeAndUnlockMode int mode) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 2504fc1..1961e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -196,8 +196,6 @@
void collapsePanelOnMainThread();
- void collapsePanelWithDuration(int duration);
-
void togglePanel();
void start();
@@ -305,9 +303,6 @@
void checkBarModes();
- // Called by NavigationBarFragment
- void setQsScrimEnabled(boolean scrimEnabled);
-
void updateBubblesVisibility();
void setInteracting(int barWindow, boolean interacting);
@@ -379,8 +374,6 @@
void showKeyguardImpl();
- boolean isInLaunchTransition();
-
void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
Runnable endRunnable, Runnable cancelRunnable);
@@ -437,8 +430,6 @@
void showPinningEscapeToast();
- KeyguardBottomAreaView getKeyguardBottomAreaView();
-
void setBouncerShowing(boolean bouncerShowing);
void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
@@ -505,12 +496,8 @@
boolean isBouncerShowingOverDream();
- void onBouncerPreHideAnimation();
-
boolean isKeyguardSecure();
- NotificationPanelViewController getPanelController();
-
NotificationGutsManager getGutsManager();
void updateNotificationPanelTouchState();
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 d6fadca..41f0520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -38,8 +38,8 @@
import android.util.Log;
import android.util.Slog;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.KeyEvent;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -458,7 +458,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
@@ -471,7 +471,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails
);
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 eb7a742..6bb5272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -466,7 +466,7 @@
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
- private final com.android.systemui.shade.ShadeController mShadeController;
+ private final ShadeController mShadeController;
private final InitController mInitController;
private final PluginDependencyProvider mPluginDependencyProvider;
@@ -479,9 +479,9 @@
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
- // expanded notifications
- // the sliding/resizing panel within the notification window
- protected NotificationPanelViewController mNotificationPanelViewController;
+ /** Controller for the Shade. */
+ @VisibleForTesting
+ NotificationPanelViewController mNotificationPanelViewController;
// settings
private QSPanelController mQSPanelController;
@@ -928,7 +928,7 @@
}
mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
- result.mRequestedVisibilities, result.mPackageName, result.mLetterboxDetails);
+ result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
// StatusBarManagerService has a back up of IME token and it's restored here.
mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
@@ -1152,7 +1152,6 @@
initializer.initializeStatusBar(mCentralSurfacesComponent);
mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
- mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
createNavigationBar(result);
@@ -1161,9 +1160,6 @@
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
-
mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
R.id.ambient_indication_container);
@@ -1514,11 +1510,12 @@
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
- mBiometricUnlockController.setBiometricModeListener(
+ mBiometricUnlockController.addBiometricModeListener(
new BiometricUnlockController.BiometricModeListener() {
@Override
public void onResetMode() {
setWakeAndUnlocking(false);
+ notifyBiometricAuthModeChanged();
}
@Override
@@ -1529,11 +1526,7 @@
case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
setWakeAndUnlocking(true);
}
- }
-
- @Override
- public void notifyBiometricAuthModeChanged() {
- CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+ notifyBiometricAuthModeChanged();
}
private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
@@ -2001,8 +1994,7 @@
}
void makeExpandedInvisible() {
- if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
- + " mExpandedVisible=" + mExpandedVisible);
+ if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
if (!mExpandedVisible || mNotificationShadeWindowView == null) {
return;
@@ -2178,12 +2170,6 @@
mNoAnimationOnNextBarModeChange = false;
}
- // Called by NavigationBarFragment
- @Override
- public void setQsScrimEnabled(boolean scrimEnabled) {
- mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
- }
-
/** Temporarily hides Bubbles if the status bar is hidden. */
@Override
public void updateBubblesVisibility() {
@@ -2559,14 +2545,11 @@
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */, true /* delayed*/);
} else {
-
// Do it after DismissAction has been processed to conserve the needed
// ordering.
mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
}
- } else if (CentralSurfacesImpl.this.isInLaunchTransition()
- && mNotificationPanelViewController.isLaunchTransitionFinished()) {
-
+ } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
// We are not dismissing the shade, but the launch transition is already
// finished,
// so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -2988,11 +2971,6 @@
mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
}
- @Override
- public boolean isInLaunchTransition() {
- return mNotificationPanelViewController.isLaunchTransitionFinished();
- }
-
/**
* Fades the content of the keyguard away after the launch transition is done.
*
@@ -3373,12 +3351,6 @@
}
}
- /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */
- @Override
- public void collapsePanelWithDuration(int duration) {
- mNotificationPanelViewController.collapseWithDuration(duration);
- }
-
/**
* Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
* from the power button).
@@ -3468,12 +3440,6 @@
mNavigationBarController.showPinningEscapeToast(mDisplayId);
}
- //TODO(b/254875405): this should be removed.
- @Override
- public KeyguardBottomAreaView getKeyguardBottomAreaView() {
- return mNotificationPanelViewController.getKeyguardBottomAreaView();
- }
-
protected ViewRootImpl getViewRootImpl() {
NotificationShadeWindowView nswv = getNotificationShadeWindowView();
if (nswv != null) return nswv.getViewRootImpl();
@@ -4176,23 +4142,11 @@
return mBouncerShowingOverDream;
}
- /**
- * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
- */
- @Override
- public void onBouncerPreHideAnimation() {
- mNotificationPanelViewController.startBouncerPreHideAnimation();
-
- }
-
@Override
public boolean isKeyguardSecure() {
return mStatusBarKeyguardViewManager.isSecure();
}
- @Override
- public NotificationPanelViewController getPanelController() {
- return mNotificationPanelViewController;
- }
+
// End Extra BaseStatusBarMethods.
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 37f04bb..a28fca1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -64,6 +64,11 @@
private static final String TAG = "KeyguardBouncer";
static final long BOUNCER_FACE_DELAY = 1200;
public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
+ /**
+ * Values for the bouncer expansion represented as the panel expansion.
+ * Panel expansion 1f = panel fully showing = bouncer fully hidden
+ * Panel expansion 0f = panel fully hiding = bouncer fully showing
+ */
public static final float EXPANSION_HIDDEN = 1f;
public static final float EXPANSION_VISIBLE = 0f;
@@ -143,6 +148,14 @@
}
/**
+ * Get the KeyguardBouncer expansion
+ * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
+ */
+ public float getExpansion() {
+ return mExpansion;
+ }
+
+ /**
* Enable/disable only the back button
*/
public void setBackButtonEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 6e98c49..eba7fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -22,8 +22,8 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -144,7 +144,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName, LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
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 5480f5d..93b6437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -86,8 +86,10 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -166,13 +168,9 @@
@Override
public void onExpansionChanged(float expansion) {
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
- }
if (mBouncerAnimating) {
mCentralSurfaces.setBouncerHiddenFraction(expansion);
}
- updateStates();
}
@Override
@@ -184,9 +182,6 @@
if (!isVisible) {
mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
}
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.onBouncerVisibilityChanged();
- }
/* Register predictive back callback when keyguard becomes visible, and unregister
when it's hidden. */
@@ -252,6 +247,7 @@
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+ final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernBouncerEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -465,7 +461,7 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
@@ -475,17 +471,17 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
} else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
- && !mCentralSurfaces.isInLaunchTransition()
+ && !mNotificationPanelViewController.isLaunchTransitionFinished()
&& !isUnlockCollapsing()) {
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
} else {
- mBouncerInteractor.setExpansion(fraction);
+ mBouncerInteractor.setPanelExpansion(fraction);
}
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
@@ -504,7 +500,7 @@
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -849,7 +845,7 @@
if (isShowing && isOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
- if (mCentralSurfaces.isInLaunchTransition()) {
+ if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
final Runnable endRunnable = new Runnable() {
@Override
public void run() {
@@ -903,7 +899,7 @@
} else {
mBouncerInteractor.startDisappearAnimation(finishRunnable);
}
- mCentralSurfaces.onBouncerPreHideAnimation();
+ mNotificationPanelViewController.startBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
@@ -935,7 +931,7 @@
long uptimeMillis = SystemClock.uptimeMillis();
long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
- if (mCentralSurfaces.isInLaunchTransition()
+ if (mNotificationPanelViewController.isLaunchTransitionFinished()
|| mKeyguardStateController.isFlingingToDismissKeyguard()) {
final boolean wasFlingingToDismissKeyguard =
mKeyguardStateController.isFlingingToDismissKeyguard();
@@ -1312,7 +1308,7 @@
@Override
public boolean shouldDisableWindowAnimationsForUnlock() {
- return mCentralSurfaces.isInLaunchTransition();
+ return mNotificationPanelViewController.isLaunchTransitionFinished();
}
@Override
@@ -1355,7 +1351,7 @@
mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
+ if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
resetAlternateAuth(false);
executeAfterKeyguardGoneAction();
}
@@ -1441,6 +1437,10 @@
pw.println(" mPendingWakeupAction: " + mPendingWakeupAction);
pw.println(" isBouncerShowing(): " + isBouncerShowing());
pw.println(" bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+ pw.println(" Registered KeyguardViewManagerCallbacks:");
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ pw.println(" " + callback);
+ }
if (mBouncer != null) {
mBouncer.dump(pw);
@@ -1465,6 +1465,20 @@
}
/**
+ * Add a callback to listen for changes
+ */
+ public void addCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ /**
+ * Removes callback to stop receiving updates
+ */
+ public void removeCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ /**
* Whether qs is currently expanded.
*/
public float getQsExpansion() {
@@ -1476,8 +1490,8 @@
*/
public void setQsExpansion(float qsExpansion) {
mQsExpansion = qsExpansion;
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setQsExpansion(qsExpansion);
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ callback.onQSExpansionChanged(mQsExpansion);
}
}
@@ -1491,21 +1505,13 @@
&& mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
}
- public boolean isShowingAlternateAuthOrAnimating() {
- return mAlternateAuthInterceptor != null
- && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
- || mAlternateAuthInterceptor.isAnimating());
- }
-
/**
- * Forward touches to any alternate authentication affordances.
+ * Forward touches to callbacks.
*/
- public boolean onTouch(MotionEvent event) {
- if (mAlternateAuthInterceptor == null) {
- return false;
+ public void onTouch(MotionEvent event) {
+ for (KeyguardViewManagerCallback callback: mCallbacks) {
+ callback.onTouch(event);
}
-
- return mAlternateAuthInterceptor.onTouch(event);
}
/** Update keyguard position based on a tapped X coordinate. */
@@ -1639,38 +1645,6 @@
boolean isShowingAlternateAuthBouncer();
/**
- * print information for the alternate auth interceptor registered
- */
- void dump(PrintWriter pw);
-
- /**
- * @return true if the new auth method bouncer is currently animating in or out.
- */
- boolean isAnimating();
-
- /**
- * How much QS is fully expanded where 0f is not showing and 1f is fully expanded.
- */
- void setQsExpansion(float qsExpansion);
-
- /**
- * Forward potential touches to authentication interceptor
- * @return true if event was handled
- */
- boolean onTouch(MotionEvent event);
-
- /**
- * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully
- * hidden
- */
- void setBouncerExpansionChanged(float expansion);
-
- /**
- * called when the bouncer view visibility has changed.
- */
- void onBouncerVisibilityChanged();
-
- /**
* Use when an app occluding the keyguard would like to give the user ability to
* unlock the device using udfps.
*
@@ -1679,5 +1653,25 @@
*/
void requestUdfps(boolean requestUdfps, int color);
+ /**
+ * print information for the alternate auth interceptor registered
+ */
+ void dump(PrintWriter pw);
+ }
+
+ /**
+ * Callback for KeyguardViewManager state changes.
+ */
+ public interface KeyguardViewManagerCallback {
+ /**
+ * Set the amount qs is expanded. For example, swipe down from the top of the
+ * lock screen to start the full QS expansion.
+ */
+ default void onQSExpansionChanged(float qsExpansion) { }
+
+ /**
+ * Forward touch events to callbacks
+ */
+ default void onTouch(MotionEvent event) { }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index ee948c0..b1642d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -31,7 +31,7 @@
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
- centralSurfaces.collapsePanelWithDuration(
+ centralSurfaces.notificationPanelViewController.collapseWithDuration(
ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index 6cd8c78..08599c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.phone
-import android.view.InsetsVisibilities
+import android.view.WindowInsets.Type.InsetsType
import android.view.WindowInsetsController.Appearance
import android.view.WindowInsetsController.Behavior
import com.android.internal.statusbar.LetterboxDetails
@@ -66,21 +66,21 @@
params.appearanceRegionsArray,
params.navbarColorManagedByIme,
params.behavior,
- params.requestedVisibilities,
+ params.requestedVisibleTypes,
params.packageName,
params.letterboxesArray)
}
}
fun onSystemBarAttributesChanged(
- displayId: Int,
- @Appearance originalAppearance: Int,
- originalAppearanceRegions: Array<AppearanceRegion>,
- navbarColorManagedByIme: Boolean,
- @Behavior behavior: Int,
- requestedVisibilities: InsetsVisibilities,
- packageName: String,
- letterboxDetails: Array<LetterboxDetails>
+ displayId: Int,
+ @Appearance originalAppearance: Int,
+ originalAppearanceRegions: Array<AppearanceRegion>,
+ navbarColorManagedByIme: Boolean,
+ @Behavior behavior: Int,
+ @InsetsType requestedVisibleTypes: Int,
+ packageName: String,
+ letterboxDetails: Array<LetterboxDetails>
) {
lastSystemBarAttributesParams =
SystemBarAttributesParams(
@@ -89,7 +89,7 @@
originalAppearanceRegions.toList(),
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails.toList())
@@ -104,7 +104,7 @@
centralSurfaces.updateBubblesVisibility()
statusBarStateController.setSystemBarAttributes(
- appearance, behavior, requestedVisibilities, packageName)
+ appearance, behavior, requestedVisibleTypes, packageName)
}
private fun modifyAppearanceIfNeeded(
@@ -137,14 +137,14 @@
* [SystemBarAttributesListener.onSystemBarAttributesChanged].
*/
private data class SystemBarAttributesParams(
- val displayId: Int,
- @Appearance val appearance: Int,
- val appearanceRegions: List<AppearanceRegion>,
- val navbarColorManagedByIme: Boolean,
- @Behavior val behavior: Int,
- val requestedVisibilities: InsetsVisibilities,
- val packageName: String,
- val letterboxes: List<LetterboxDetails>,
+ val displayId: Int,
+ @Appearance val appearance: Int,
+ val appearanceRegions: List<AppearanceRegion>,
+ val navbarColorManagedByIme: Boolean,
+ @Behavior val behavior: Int,
+ @InsetsType val requestedVisibleTypes: Int,
+ val packageName: String,
+ val letterboxes: List<LetterboxDetails>,
) {
val letterboxesArray = letterboxes.toTypedArray()
val appearanceRegionsArray = appearanceRegions.toTypedArray()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
new file mode 100644
index 0000000..e618905
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.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.statusbar.pipeline.mobile.data.model
+
+import android.net.NetworkCapabilities
+
+/** Provides information about a mobile network connection */
+data class MobileConnectivityModel(
+ /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
+ val isConnected: Boolean = false,
+ /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
+ val isValidated: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 06e8f46..581842b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings.Global
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
@@ -34,6 +38,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.util.settings.GlobalSettings
import java.lang.IllegalStateException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,9 +47,12 @@
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -65,14 +73,23 @@
*/
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
- val dataEnabled: Flow<Boolean>
+ val dataEnabled: StateFlow<Boolean>
+ /**
+ * True if this connection represents the default subscription per
+ * [SubscriptionManager.getDefaultDataSubscriptionId]
+ */
+ val isDefaultDataSubscription: StateFlow<Boolean>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
+ private val context: Context,
private val subId: Int,
private val telephonyManager: TelephonyManager,
+ private val globalSettings: GlobalSettings,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
scope: CoroutineScope,
@@ -86,6 +103,8 @@
}
}
+ private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
var state = MobileSubscriptionModel()
conflatedCallbackFlow {
@@ -165,33 +184,75 @@
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .onEach { telephonyCallbackEvent.tryEmit(Unit) }
.logOutputChange(logger, "MobileSubscriptionModel")
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
+ /** Produces whenever the mobile data setting changes for this subId */
+ private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
+ /* notifyForDescendants */ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
/**
* There are a few cases where we will need to poll [TelephonyManager] so we can update some
* internal state where callbacks aren't provided. Any of those events should be merged into
* this flow, which can be used to trigger the polling.
*/
- private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
+ private val telephonyPollingEvent: Flow<Unit> =
+ merge(
+ telephonyCallbackEvent,
+ localMobileDataSettingChangedEvent,
+ globalMobileDataSettingChangedEvent,
+ )
- override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
+ override val dataEnabled: StateFlow<Boolean> =
+ telephonyPollingEvent
+ .mapLatest { dataConnectionAllowed() }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
+ override val isDefaultDataSubscription: StateFlow<Boolean> =
+ defaultDataSubId
+ .mapLatest { it == subId }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+
class Factory
@Inject
constructor(
+ private val context: Context,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
+ private val globalSettings: GlobalSettings,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) {
- fun build(subId: Int): MobileConnectionRepository {
+ fun build(
+ subId: Int,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
+ ): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
+ context,
subId,
telephonyManager.createForSubscriptionId(subId),
+ globalSettings,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
bgDispatcher,
logger,
scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 0e2428a..c3c1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -16,15 +16,27 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.annotation.SuppressLint
import android.content.Context
import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
+import android.provider.Settings.Global.MOBILE_DATA
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,7 +44,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -40,10 +54,12 @@
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -57,13 +73,22 @@
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
/** Observable for the subscriptionId of the current mobile data connection */
- val activeMobileDataSubscriptionId: Flow<Int>
+ val activeMobileDataSubscriptionId: StateFlow<Int>
/** Observable for [MobileMappings.Config] tracking the defaults */
val defaultDataSubRatConfig: StateFlow<Config>
+ /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
+ val defaultDataSubId: StateFlow<Int>
+
+ /** The current connectivity status for the default mobile network connection */
+ val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
+
/** Get or create a repository for the line of service for the given subscription ID */
fun getRepoForSubId(subId: Int): MobileConnectionRepository
+
+ /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
+ val globalMobileDataSettingChangedEvent: Flow<Unit>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -72,10 +97,12 @@
class MobileConnectionsRepositoryImpl
@Inject
constructor(
+ private val connectivityManager: ConnectivityManager,
private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
broadcastDispatcher: BroadcastDispatcher,
+ private val globalSettings: GlobalSettings,
private val context: Context,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@@ -121,17 +148,26 @@
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+
+ private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
+ override val defaultDataSubId: StateFlow<Int> =
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ ) { intent, _ ->
+ intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ }
+ .distinctUntilChanged()
+ .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn(
scope,
- started = SharingStarted.WhileSubscribed(),
- SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ SharingStarted.WhileSubscribed(),
+ SubscriptionManager.getDefaultDataSubscriptionId()
)
- private val defaultDataSubChangedEvent =
- broadcastDispatcher.broadcastFlow(
- IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- )
-
private val carrierConfigChangedEvent =
broadcastDispatcher.broadcastFlow(
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
@@ -148,9 +184,8 @@
* This flow will produce whenever the default data subscription or the carrier config changes.
*/
override val defaultDataSubRatConfig: StateFlow<Config> =
- combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
- Config.readConfig(context)
- }
+ merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+ .mapLatest { Config.readConfig(context) }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -168,6 +203,57 @@
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
}
+ /**
+ * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
+ * connection repositories also observe the URI for [MOBILE_DATA] + subId.
+ */
+ override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor(MOBILE_DATA),
+ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
+ @SuppressLint("MissingPermission")
+ override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+ conflatedCallbackFlow {
+ val callback =
+ object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onLost(network: Network) {
+ // Send a disconnected model when lost. Maybe should create a sealed
+ // type or null here?
+ trySend(MobileConnectivityModel())
+ }
+
+ override fun onCapabilitiesChanged(
+ network: Network,
+ caps: NetworkCapabilities
+ ) {
+ trySend(
+ MobileConnectivityModel(
+ isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
+ isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
+ )
+ )
+ }
+ }
+
+ connectivityManager.registerDefaultNetworkCallback(callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
+
private fun isValidSubId(subId: Int): Boolean {
subscriptionsFlow.value.forEach {
if (it.subscriptionId == subId) {
@@ -181,7 +267,11 @@
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
- return mobileConnectionRepositoryFactory.build(subId)
+ return mobileConnectionRepositoryFactory.build(
+ subId,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
+ )
}
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
index 77de849..91886bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -26,7 +26,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapLatest
@@ -40,7 +39,7 @@
*/
interface UserSetupRepository {
/** Observable tracking [DeviceProvisionedController.isUserSetup] */
- val isUserSetupFlow: Flow<Boolean>
+ val isUserSetupFlow: StateFlow<Boolean>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index f99d278c..0da84f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -18,81 +18,109 @@
import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
+ /** Only true if mobile is the default transport but is not validated, otherwise false */
+ val isDefaultConnectionFailed: StateFlow<Boolean>
+
+ /** True when telephony tells us that the data state is CONNECTED */
+ val isDataConnected: StateFlow<Boolean>
+
+ // TODO(b/256839546): clarify naming of default vs active
+ /** True if we want to consider the data connection enabled */
+ val isDefaultDataEnabled: StateFlow<Boolean>
+
/** Observable for the data enabled state of this connection */
- val isDataEnabled: Flow<Boolean>
+ val isDataEnabled: StateFlow<Boolean>
/** Observable for RAT type (network type) indicator */
- val networkTypeIconGroup: Flow<MobileIconGroup>
+ val networkTypeIconGroup: StateFlow<MobileIconGroup>
/** True if this line of service is emergency-only */
- val isEmergencyOnly: Flow<Boolean>
+ val isEmergencyOnly: StateFlow<Boolean>
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
- val level: Flow<Int>
+ val level: StateFlow<Int>
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
- val numberOfLevels: Flow<Int>
-
- /** True when we want to draw an icon that makes room for the exclamation mark */
- val cutOut: Flow<Boolean>
+ val numberOfLevels: StateFlow<Int>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconInteractorImpl(
- defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
- defaultMobileIconGroup: Flow<MobileIconGroup>,
+ @Application scope: CoroutineScope,
+ defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+ defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: StateFlow<MobileIconGroup>,
+ override val isDefaultConnectionFailed: StateFlow<Boolean>,
mobileMappingsProxy: MobileMappingsProxy,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
- override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled
+ override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+
+ override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
- override val networkTypeIconGroup: Flow<MobileIconGroup> =
+ override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
- mobileStatusInfo,
- defaultMobileIconMapping,
- defaultMobileIconGroup,
- ) { info, mapping, defaultGroup ->
- val lookupKey =
- when (val resolved = info.resolvedNetworkType) {
- is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
- is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
- }
- mapping[lookupKey] ?: defaultGroup
- }
-
- override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
-
- override val level: Flow<Int> =
- mobileStatusInfo.map { mobileModel ->
- // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
- if (mobileModel.isGsm) {
- mobileModel.primaryLevel
- } else {
- mobileModel.cdmaLevel
+ mobileStatusInfo,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ ) { info, mapping, defaultGroup ->
+ val lookupKey =
+ when (val resolved = info.resolvedNetworkType) {
+ is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
+ is OverrideNetworkType ->
+ mobileMappingsProxy.toIconKeyOverride(resolved.type)
+ }
+ mapping[lookupKey] ?: defaultGroup
}
- }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+
+ override val isEmergencyOnly: StateFlow<Boolean> =
+ mobileStatusInfo
+ .mapLatest { it.isEmergencyOnly }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val level: StateFlow<Int> =
+ mobileStatusInfo
+ .mapLatest { mobileModel ->
+ // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+ if (mobileModel.isGsm) {
+ mobileModel.primaryLevel
+ } else {
+ mobileModel.cdmaLevel
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
/**
* This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
* once it's wired up inside of [CarrierConfigTracker]
*/
- override val numberOfLevels: Flow<Int> = flowOf(4)
+ override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
- /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
- // TODO: find a better name for this?
- override val cutOut: Flow<Boolean> = flowOf(false)
+ override val isDataConnected: StateFlow<Boolean> =
+ mobileStatusInfo
+ .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 614d583..a4175c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -19,6 +19,7 @@
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
@@ -35,7 +36,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/**
@@ -51,12 +54,16 @@
interface MobileIconsInteractor {
/** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ /** True if the active mobile data subscription has data enabled */
+ val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
- val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+ val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
- val defaultMobileIconGroup: Flow<MobileIconGroup>
+ val defaultMobileIconGroup: StateFlow<MobileIconGroup>
+ /** True only if the default network is mobile, and validation also failed */
+ val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */
- val isUserSetup: Flow<Boolean>
+ val isUserSetup: StateFlow<Boolean>
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
* subId. Will throw if the ID is invalid
@@ -79,6 +86,22 @@
private val activeMobileDataSubscriptionId =
mobileConnectionsRepo.activeMobileDataSubscriptionId
+ private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
+ activeMobileDataSubscriptionId
+ .mapLatest { activeId ->
+ if (activeId == INVALID_SUBSCRIPTION_ID) {
+ null
+ } else {
+ mobileConnectionsRepo.getRepoForSubId(activeId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
+ activeMobileDataConnectionRepo
+ .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
mobileConnectionsRepo.subscriptionsFlow
@@ -132,22 +155,40 @@
*/
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
mobileConnectionsRepo.defaultDataSubRatConfig
- .map { mobileMappingsProxy.mapIconSets(it) }
+ .mapLatest { mobileMappingsProxy.mapIconSets(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileConnectionsRepo.defaultDataSubRatConfig
- .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
- override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+ /**
+ * We want to show an error state when cellular has actually failed to validate, but not if some
+ * other transport type is active, because then we expect there not to be validation.
+ */
+ override val isDefaultConnectionFailed: StateFlow<Boolean> =
+ mobileConnectionsRepo.defaultMobileNetworkConnectivity
+ .mapLatest { connectivityModel ->
+ if (!connectivityModel.isConnected) {
+ false
+ } else {
+ !connectivityModel.isValidated
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
/** Vends out new [MobileIconInteractor] for a particular subId */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl(
+ scope,
+ activeDataConnectionHasDataEnabled,
defaultMobileIconMapping,
defaultMobileIconGroup,
+ isDefaultConnectionFailed,
mobileMappingsProxy,
mobileConnectionsRepo.getRepoForSubId(subId),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 8131739..7869021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -24,10 +24,12 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -39,29 +41,38 @@
*
* TODO: figure out where carrier merged and VCN models go (probably here?)
*/
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel
constructor(
val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger,
) {
+ /** Whether or not to show the error state of [SignalDrawable] */
+ private val showExclamationMark: Flow<Boolean> =
+ iconInteractor.isDefaultDataEnabled.mapLatest { !it }
+
/** An int consumable by [SignalDrawable] for display */
- var iconId: Flow<Int> =
- combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+ val iconId: Flow<Int> =
+ combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
level,
numberOfLevels,
- cutOut ->
- SignalDrawable.getState(level, numberOfLevels, cutOut)
+ showExclamationMark ->
+ SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
}
.distinctUntilChanged()
.logOutputChange(logger, "iconId($subscriptionId)")
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- var networkTypeIcon: Flow<Icon?> =
- combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
- networkTypeIconGroup,
- isDataEnabled ->
- if (!isDataEnabled) {
+ val networkTypeIcon: Flow<Icon?> =
+ combine(
+ iconInteractor.networkTypeIconGroup,
+ iconInteractor.isDataConnected,
+ iconInteractor.isDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
+ if (!dataConnected || !dataEnabled || failedConnection) {
null
} else {
val desc =
@@ -72,5 +83,5 @@
}
}
- var tint: Flow<Int> = flowOf(Color.CYAN)
+ val tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c0..131cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@
keyguardOccluded = false,
occludingAppRequestingFp = false,
primaryUser = false,
+ shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 52f8ef8..0a2b3d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -194,6 +194,15 @@
}
@Test
+ public void onInitConfiguresViewMode() {
+ mKeyguardSecurityContainerController.onInit();
+ verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
+ }
+
+ @Test
public void showSecurityScreen_canInflateAllModes() {
SecurityMode[] modes = SecurityMode.values();
for (SecurityMode mode : modes) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 680c3b8..5718835 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -21,6 +21,7 @@
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -39,6 +40,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -54,6 +56,7 @@
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -61,18 +64,21 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -111,6 +117,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -182,6 +189,8 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private SecureSettings mSecureSettings;
+ @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -215,6 +224,7 @@
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -224,6 +234,9 @@
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+ @Mock
+ private Uri mURI;
+
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+
+ when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ ExtendedMockito.spyOn(contentResolver);
+ doNothing().when(contentResolver)
+ .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+ anyInt());
+
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
@@ -1136,6 +1158,63 @@
}
@Test
+ public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+ throws RemoteException {
+ // SFPS supported and enrolled
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+ // WHEN require screen on to auth is disabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+ // Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+
+ statusBarShadeIsLocked();
+ mTestableLooper.processAllMessages();
+
+ // THEN we should listen for sfps when screen off, because require screen on is disabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+ // WHEN require screen on to auth is enabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+ // Device now awake & keyguard is now interactive
+ deviceNotGoingToSleep();
+ deviceIsInteractive();
+ keyguardIsVisible();
+
+ // THEN we should listen for sfps when screen on, and require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
+
+ private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType) {
+ return new FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ new ArrayList<ComponentInfoInternal>(),
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
+ @Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1804,7 +1883,7 @@
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mDumpManager,
+ mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index d489656..6ec5a6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -42,6 +42,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -103,6 +105,8 @@
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var bouncerInteractor: BouncerInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -136,7 +140,8 @@
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
+ controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
+ bouncerInteractor, isDebuggable
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index ed957db..41c307a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -71,6 +71,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -192,6 +193,8 @@
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+ @Mock
+ private BouncerInteractor mBouncerInteractor;
// Capture listeners so that they can be used to send events
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
@@ -272,7 +275,8 @@
mLatencyTracker,
mActivityLaunchAnimator,
Optional.of(mAlternateTouchProvider),
- mBiometricsExecutor);
+ mBiometricsExecutor,
+ mBouncerInteractor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
index 7864f21b..1bc237d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
@@ -23,7 +23,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -48,7 +48,7 @@
@Mock
private AuthController mAuthController;
@Mock
- private IUdfpsHbmListener mDisplayCallback;
+ private IUdfpsRefreshRateRequestCallback mDisplayCallback;
@Mock
private UdfpsLogger mUdfpsLogger;
@Mock
@@ -68,7 +68,7 @@
when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID);
// Set up mocks.
- when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback);
+ when(mAuthController.getUdfpsRefreshRateCallback()).thenReturn(mDisplayCallback);
// Create a real controller with mock dependencies.
mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController,
@@ -81,7 +81,7 @@
mHbmController.enable(mOnEnabled);
// Should set the appropriate refresh rate for UDFPS and notify the caller.
- verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
verify(mOnEnabled).run();
// Disable the UDFPS mode.
@@ -89,7 +89,7 @@
// Should unset the refresh rate and notify the caller.
verify(mOnDisabled).run();
- verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
}
@Test
@@ -98,7 +98,7 @@
mHbmController.enable(mOnEnabled);
// Should set the appropriate refresh rate for UDFPS and notify the caller.
- verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
verify(mOnEnabled).run();
// Second request to enable the UDFPS mode, while it's still enabled.
@@ -115,7 +115,7 @@
mHbmController.enable(mOnEnabled);
// Should set the appropriate refresh rate for UDFPS and notify the caller.
- verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
verify(mOnEnabled).run();
// First request to disable the UDFPS mode.
@@ -123,7 +123,7 @@
// Should unset the refresh rate and notify the caller.
verify(mOnDisabled).run();
- verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
// Second request to disable the UDFPS mode, when it's already disabled.
mHbmController.disable(mOnDisabled);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
new file mode 100644
index 0000000..e5c7a42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
+ // Dependencies
+ protected @Mock UdfpsKeyguardView mView;
+ protected @Mock Context mResourceContext;
+ protected @Mock StatusBarStateController mStatusBarStateController;
+ protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+ protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ protected @Mock DumpManager mDumpManager;
+ protected @Mock DelayableExecutor mExecutor;
+ protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ protected @Mock KeyguardStateController mKeyguardStateController;
+ protected @Mock KeyguardViewMediator mKeyguardViewMediator;
+ protected @Mock ConfigurationController mConfigurationController;
+ protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ protected @Mock SystemUIDialogManager mDialogManager;
+ protected @Mock UdfpsController mUdfpsController;
+ protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ protected @Mock KeyguardBouncer mBouncer;
+ protected @Mock BouncerInteractor mBouncerInteractor;
+
+ protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ protected FakeSystemClock mSystemClock = new FakeSystemClock();
+
+ protected UdfpsKeyguardViewController mController;
+
+ // Capture listeners so that they can be used to send events
+ private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+ protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+ private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+ protected List<ShadeExpansionListener> mExpansionListeners;
+
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
+ mAltAuthInterceptorCaptor;
+ protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+
+ private @Captor ArgumentCaptor<KeyguardStateController.Callback>
+ mKeyguardStateControllerCallbackCaptor;
+ protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mView.getContext()).thenReturn(mResourceContext);
+ when(mResourceContext.getString(anyInt())).thenReturn("test string");
+ when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ mController = createUdfpsKeyguardViewController();
+ }
+
+ protected void sendStatusBarStateChanged(int statusBarState) {
+ mStatusBarStateListener.onStateChanged(statusBarState);
+ }
+
+ protected void captureStatusBarStateListeners() {
+ verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+ mStatusBarStateListener = mStateListenerCaptor.getValue();
+ }
+
+ protected void captureStatusBarExpansionListeners() {
+ verify(mShadeExpansionStateManager, times(2))
+ .addExpansionListener(mExpansionListenerCaptor.capture());
+ // first (index=0) is from super class, UdfpsAnimationViewController.
+ // second (index=1) is from UdfpsKeyguardViewController
+ mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+ }
+
+ protected void updateStatusBarExpansion(float fraction, boolean expanded) {
+ ShadeExpansionChangeEvent event =
+ new ShadeExpansionChangeEvent(
+ fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
+ for (ShadeExpansionListener listener : mExpansionListeners) {
+ listener.onPanelExpansionChanged(event);
+ }
+ }
+
+ protected void captureAltAuthInterceptor() {
+ verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
+ mAltAuthInterceptorCaptor.capture());
+ mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+ }
+
+ protected void captureKeyguardStateControllerCallback() {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateControllerCallbackCaptor.capture());
+ mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ }
+
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(false);
+ }
+
+ protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
+ boolean useModernBouncer) {
+ mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+ when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+ useModernBouncer ? null : mBouncer);
+ return new UdfpsKeyguardViewController(
+ mView,
+ mStatusBarStateController,
+ mShadeExpansionStateManager,
+ mStatusBarKeyguardViewManager,
+ mKeyguardUpdateMonitor,
+ mDumpManager,
+ mLockscreenShadeTransitionController,
+ mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
+ mDialogManager,
+ mUdfpsController,
+ mActivityLaunchAnimator,
+ mFeatureFlags,
+ mBouncerInteractor);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index c0f9c82..55b6194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -25,126 +25,53 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
-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(AndroidTestingRunner.class)
@RunWithLooper
-public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
- // Dependencies
- @Mock
- private UdfpsKeyguardView mView;
- @Mock
- private Context mResourceContext;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private ShadeExpansionStateManager mShadeExpansionStateManager;
- @Mock
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock
- private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- @Mock
- private DumpManager mDumpManager;
- @Mock
- private DelayableExecutor mExecutor;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- @Mock
- private SystemUIDialogManager mDialogManager;
- @Mock
- private UdfpsController mUdfpsController;
- @Mock
- private ActivityLaunchAnimator mActivityLaunchAnimator;
- private FakeSystemClock mSystemClock = new FakeSystemClock();
+public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
+ private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+ mBouncerExpansionCallbackCaptor;
+ private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
- private UdfpsKeyguardViewController mController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
- private List<ShadeExpansionListener> mExpansionListeners;
-
- @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
- mAltAuthInterceptorCaptor;
- private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
-
- @Captor private ArgumentCaptor<KeyguardStateController.Callback>
- mKeyguardStateControllerCallbackCaptor;
- private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mView.getContext()).thenReturn(mResourceContext);
- when(mResourceContext.getString(anyInt())).thenReturn("test string");
- when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
- when(mView.getUnpausedAlpha()).thenReturn(255);
- mController = new UdfpsKeyguardViewController(
- mView,
- mStatusBarStateController,
- mShadeExpansionStateManager,
- mStatusBarKeyguardViewManager,
- mKeyguardUpdateMonitor,
- mDumpManager,
- mLockscreenShadeTransitionController,
- mConfigurationController,
- mSystemClock,
- mKeyguardStateController,
- mUnlockedScreenOffAnimationController,
- mDialogManager,
- mUdfpsController,
- mActivityLaunchAnimator);
+ @Override
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
}
@Test
+ public void testShouldPauseAuth_bouncerShowing() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ captureBouncerExpansionCallback();
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+
+
+ @Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
captureStatusBarExpansionListeners();
@@ -202,20 +129,6 @@
}
@Test
- public void testShouldPauseAuthBouncerShowing() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- captureAltAuthInterceptor();
- when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
- mAltAuthInterceptor.onBouncerVisibilityChanged();
-
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
public void testShouldPauseAuthUnpausedAlpha0() {
mController.onViewAttached();
captureStatusBarStateListeners();
@@ -503,41 +416,8 @@
verify(mView, atLeastOnce()).setPauseAuth(false);
}
- private void sendStatusBarStateChanged(int statusBarState) {
- mStatusBarStateListener.onStateChanged(statusBarState);
- }
-
- private void captureStatusBarStateListeners() {
- verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
- mStatusBarStateListener = mStateListenerCaptor.getValue();
- }
-
- private void captureStatusBarExpansionListeners() {
- verify(mShadeExpansionStateManager, times(2))
- .addExpansionListener(mExpansionListenerCaptor.capture());
- // first (index=0) is from super class, UdfpsAnimationViewController.
- // second (index=1) is from UdfpsKeyguardViewController
- mExpansionListeners = mExpansionListenerCaptor.getAllValues();
- }
-
- private void updateStatusBarExpansion(float fraction, boolean expanded) {
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
- for (ShadeExpansionListener listener : mExpansionListeners) {
- listener.onPanelExpansionChanged(event);
- }
- }
-
- private void captureAltAuthInterceptor() {
- verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
- mAltAuthInterceptorCaptor.capture());
- mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
- }
-
- private void captureKeyguardStateControllerCallback() {
- verify(mKeyguardStateController).addCallback(
- mKeyguardStateControllerCallbackCaptor.capture());
- mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ private void captureBouncerExpansionCallback() {
+ verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
+ mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..7b19768
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
+ lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+ @Before
+ override fun setUp() {
+ allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
+ MockitoAnnotations.initMocks(this)
+ keyguardBouncerRepository =
+ KeyguardBouncerRepository(
+ mock(com.android.keyguard.ViewMediatorCallback::class.java),
+ mKeyguardUpdateMonitor
+ )
+ super.setUp()
+ }
+
+ override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
+ mBouncerInteractor =
+ BouncerInteractor(
+ keyguardBouncerRepository,
+ mock(BouncerView::class.java),
+ mock(Handler::class.java),
+ mKeyguardStateController,
+ mock(KeyguardSecurityModel::class.java),
+ mock(BouncerCallbackInteractor::class.java),
+ mock(FalsingCollector::class.java),
+ mock(DismissCallbackRegistry::class.java),
+ mock(KeyguardBypassController::class.java),
+ mKeyguardUpdateMonitor
+ )
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testShouldPauseAuthBouncerShowing() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN view attached and we're on the keyguard
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+ // WHEN the bouncer expansion is VISIBLE
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setVisible(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ yield()
+
+ // THEN UDFPS shouldPauseAuth == true
+ assertTrue(mController.shouldPauseAuth())
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
new file mode 100644
index 0000000..54f20db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.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.
+ */
+
+package com.android.systemui.biometrics
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.any
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+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.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenEver
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class UdfpsShellTest : SysuiTestCase() {
+
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ // Unit under test
+ private lateinit var udfpsShell: UdfpsShell
+
+ @Mock lateinit var commandRegistry: CommandRegistry
+ @Mock lateinit var udfpsOverlay: UdfpsOverlay
+ @Mock lateinit var udfpsOverlayController: UdfpsOverlayController
+
+ @Captor private lateinit var motionEvent: ArgumentCaptor<MotionEvent>
+
+ private val sensorBounds = Rect()
+
+ @Before
+ fun setup() {
+ whenEver(udfpsOverlayController.sensorBounds).thenReturn(sensorBounds)
+
+ udfpsShell = UdfpsShell(commandRegistry, udfpsOverlay)
+ udfpsShell.udfpsOverlayController = udfpsOverlayController
+ }
+
+ @Test
+ fun testSimFingerDown() {
+ udfpsShell.simFingerDown()
+
+ verify(udfpsOverlayController, times(2)).debugOnTouch(any(), motionEvent.capture())
+
+ assertEquals(motionEvent.allValues[0].action, MotionEvent.ACTION_DOWN) // ACTION_MOVE
+ assertEquals(motionEvent.allValues[1].action, MotionEvent.ACTION_MOVE) // ACTION_MOVE
+ }
+
+ @Test
+ fun testSimFingerUp() {
+ udfpsShell.simFingerUp()
+
+ verify(udfpsOverlayController).debugOnTouch(any(), motionEvent.capture())
+
+ assertEquals(motionEvent.value.action, MotionEvent.ACTION_UP)
+ }
+
+ @Test
+ fun testOnUiReady() {
+ udfpsShell.onUiReady()
+
+ verify(udfpsOverlayController).debugOnUiReady(any(), any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9ab9d3..53d9b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -21,8 +21,10 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -46,6 +48,7 @@
@Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -59,6 +62,7 @@
keyguardStateController,
dozeHost,
wakefulnessLifecycle,
+ biometricUnlockController,
)
}
@@ -190,7 +194,7 @@
}
@Test
- fun wakefullness() = runBlockingTest {
+ fun wakefulness() = runBlockingTest {
val values = mutableListOf<WakefulnessModel>()
val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
@@ -217,4 +221,63 @@
job.cancel()
verify(wakefulnessLifecycle).removeObserver(captor.value)
}
+
+ @Test
+ fun isBouncerShowing() = runBlockingTest {
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun biometricUnlockState() = runBlockingTest {
+ val values = mutableListOf<BiometricUnlockModel>()
+ val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+ verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+ captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be NONE, followed by onModeChanged() call
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.SHOW_BOUNCER,
+ BiometricUnlockModel.ONLY_WAKE,
+ BiometricUnlockModel.UNLOCK_COLLAPSING,
+ BiometricUnlockModel.DISMISS_BOUNCER,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ )
+ )
+
+ job.cancel()
+ verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 64913c7c..27d5d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -128,11 +128,15 @@
assertThat(steps.size).isEqualTo(3)
assertThat(steps[0])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
assertThat(steps[1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME)
+ )
assertThat(steps[2])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+ )
job.cancel()
}
@@ -174,15 +178,22 @@
}
private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
- assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ assertThat(steps[0])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
fractions.forEachIndexed { index, fraction ->
assertThat(steps[index + 1])
.isEqualTo(
- TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+ TransitionStep(
+ AOD,
+ LOCKSCREEN,
+ fraction.toFloat(),
+ TransitionState.RUNNING,
+ OWNER_NAME
+ )
)
}
assertThat(steps[steps.size - 1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
assertThat(wtfHandler.failed).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
index e6c8dd8..5743b2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -27,8 +27,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -57,6 +57,7 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@@ -86,6 +87,7 @@
)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
`when`(repository.show.value).thenReturn(null)
+ `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
}
@Test
@@ -97,7 +99,7 @@
verify(repository).setHide(false)
verify(repository).setStartingToHide(false)
verify(repository).setScrimmed(true)
- verify(repository).setExpansion(EXPANSION_VISIBLE)
+ verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
verify(repository).setShowingSoon(true)
verify(keyguardStateController).notifyBouncerShowing(true)
verify(bouncerCallbackInteractor).dispatchStartingToShow()
@@ -108,7 +110,7 @@
@Test
fun testShow_isNotScrimmed() {
- verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+ verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
}
@Test
@@ -124,7 +126,6 @@
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setShowingSoon(false)
- verify(repository).setOnDismissAction(null)
verify(repository).setVisible(false)
verify(repository).setHide(true)
verify(repository).setShow(null)
@@ -132,26 +133,26 @@
@Test
fun testExpansion() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
- bouncerInteractor.setExpansion(0.6f)
- verify(repository).setExpansion(0.6f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ bouncerInteractor.setPanelExpansion(0.6f)
+ verify(repository).setPanelExpansion(0.6f)
verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@Test
fun testExpansion_fullyShown() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(bouncerCallbackInteractor).dispatchFullyShown()
}
@Test
fun testExpansion_fullyHidden() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+ bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setVisible(false)
verify(repository).setShow(null)
verify(falsingCollector).onBouncerHidden()
@@ -161,8 +162,8 @@
@Test
fun testExpansion_startingToHide() {
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- bouncerInteractor.setExpansion(0.1f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(0.1f)
verify(repository).setStartingToHide(true)
verify(bouncerCallbackInteractor).dispatchStartingToHide()
}
@@ -178,8 +179,7 @@
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
- verify(repository)
- .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
@@ -234,7 +234,7 @@
@Test
fun testIsFullShowing() {
`when`(repository.isVisible.value).thenReturn(true)
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
assertThat(bouncerInteractor.isFullyShowing()).isTrue()
`when`(repository.isVisible.value).thenReturn(false)
@@ -255,7 +255,7 @@
assertThat(bouncerInteractor.isInTransit()).isTrue()
`when`(repository.showingSoon.value).thenReturn(false)
assertThat(bouncerInteractor.isInTransit()).isFalse()
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
assertThat(bouncerInteractor.isInTransit()).isTrue()
}
@@ -269,10 +269,9 @@
@Test
fun testWillDismissWithAction() {
- `when`(repository.onDismissAction.value?.onDismissAction)
- .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
- `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0424c28..6333b24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -54,9 +54,11 @@
@Test
fun `transition collectors receives only appropriate events`() =
runBlocking(IMMEDIATE) {
- var goneToAodSteps = mutableListOf<TransitionStep>()
+ var lockscreenToAodSteps = mutableListOf<TransitionStep>()
val job1 =
- underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this)
+ underTest.lockscreenToAodTransition
+ .onEach { lockscreenToAodSteps.add(it) }
+ .launchIn(this)
var aodToLockscreenSteps = mutableListOf<TransitionStep>()
val job2 =
@@ -70,14 +72,14 @@
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
- steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
- steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
steps.forEach { repository.sendTransitionStep(it) }
assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
- assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8))
+ assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
job1.cancel()
job2.cancel()
@@ -119,10 +121,8 @@
fun keyguardStateTests() =
runBlocking(IMMEDIATE) {
var finishedSteps = mutableListOf<KeyguardState>()
- val job1 =
+ val job =
underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
- var startedSteps = mutableListOf<KeyguardState>()
- val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -137,10 +137,60 @@
steps.forEach { repository.sendTransitionStep(it) }
assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
- assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE))
- job1.cancel()
- job2.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun finishedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var finishedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.finishedKeyguardTransitionStep
+ .onEach { finishedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+
+ job.cancel()
+ }
+
+ @Test
+ fun startedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var startedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.startedKeyguardTransitionStep
+ .onEach { startedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+
+ job.cancel()
}
companion object {
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 6adce7a..c1fa9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -61,6 +61,7 @@
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -201,6 +202,8 @@
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private Resources mResources;
+ @Mock
+ private ViewRootImpl mViewRootImpl;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -227,6 +230,7 @@
when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
.thenReturn(mContext);
when(mNavigationBarView.getResources()).thenReturn(mResources);
+ when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
index 99a17a6..9115ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
@@ -24,6 +24,7 @@
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
@@ -48,6 +49,7 @@
public void setUp() throws Exception {
mTestableLooper = TestableLooper.get(this);
LayoutInflater inflater = LayoutInflater.from(mContext);
+ mContext.ensureTestableResources();
mTestableLooper.runWithLooper(() ->
mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
@@ -119,4 +121,30 @@
mQSCarrier.updateState(c, true);
assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
}
+
+ @Test
+ public void testCarrierNameMaxWidth_smallScreen_fromResource() {
+ int maxEms = 10;
+ mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_large_screen_shade_header, false);
+ TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+ mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+ assertEquals(maxEms, carrierText.getMaxEms());
+ }
+
+ @Test
+ public void testCarrierNameMaxWidth_largeScreen_maxInt() {
+ int maxEms = 10;
+ mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_large_screen_shade_header, true);
+ TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+ mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+ assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2c76be6..b067ee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -136,6 +137,20 @@
assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
}
+ @Test
+ public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+ ImageView iv = new ImageView(mContext);
+ AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getDrawable(any())).thenReturn(d);
+ when(s.icon.getInvisibleDrawable(any())).thenReturn(d);
+
+ mIconView.updateIcon(iv, s, false);
+
+ verify(d, never()).start();
+ }
+
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
return new Drawable.ConstantState() {
@Override
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 45b4353..c98c1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -486,6 +486,7 @@
mSysUiState,
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
+ mKeyguardIndicationController,
mNotificationListContainer,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
@@ -499,8 +500,6 @@
() -> {},
mNotificationShelfController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
verify(mView, atLeast(1)).addOnAttachStateChangeListener(
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 26a0770..a4a7995 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -152,7 +152,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should intercept touch
@@ -165,7 +165,7 @@
// WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we shouldn't intercept touch
@@ -178,7 +178,7 @@
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 09add65..43c6942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -66,6 +66,8 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var shadeLogger: ShadeLogger
private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
private lateinit var underTest: PulsingGestureListener
@@ -81,6 +83,7 @@
centralSurfaces,
ambientDisplayConfiguration,
statusBarStateController,
+ shadeLogger,
tunerService,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index cf7f8dd..08933fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,9 +33,10 @@
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -135,27 +136,29 @@
public void testOnSystemBarAttributesChanged() {
doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+ BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+ TEST_LETTERBOX_DETAILS);
}
@Test
public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+ BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+ TEST_LETTERBOX_DETAILS);
}
private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
letterboxDetails);
waitForIdleSync();
verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
- eq(requestedVisibilities), eq(packageName), eq(letterboxDetails));
+ eq(requestedVisibleTypes), eq(packageName), eq(letterboxDetails));
}
@Test
@@ -492,11 +495,12 @@
}
@Test
- public void testSetUdfpsHbmListener() {
- final IUdfpsHbmListener listener = mock(IUdfpsHbmListener.class);
- mCommandQueue.setUdfpsHbmListener(listener);
+ public void testSetUdfpsRefreshRateCallback() {
+ final IUdfpsRefreshRateRequestCallback callback =
+ mock(IUdfpsRefreshRateRequestCallback.class);
+ mCommandQueue.setUdfpsRefreshRateCallback(callback);
waitForIdleSync();
- verify(mCallbacks).setUdfpsHbmListener(eq(listener));
+ verify(mCallbacks).setUdfpsRefreshRateCallback(eq(callback));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e7a435e..112e759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -22,6 +22,7 @@
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -60,6 +61,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -192,6 +194,25 @@
}
/**
+ * Creates a generic row with rounded border.
+ *
+ * @return a generic row with the set roundness.
+ * @throws Exception
+ */
+ public ExpandableNotificationRow createRowWithRoundness(
+ float topRoundness,
+ float bottomRoundness,
+ SourceType sourceType
+ ) throws Exception {
+ ExpandableNotificationRow row = createRow();
+ row.requestTopRoundness(topRoundness, false, sourceType);
+ row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+ assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
+ assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
+ return row;
+ }
+
+ /**
* Creates a generic row.
*
* @return a generic row with no special properties.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 7c41abba..438b528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,6 +25,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -151,4 +152,37 @@
Assert.assertNotNull("Children container must have a header after recreation",
mChildrenContainer.getCurrentHeaderView());
}
+
+ @Test
+ public void addNotification_shouldResetOnScrollRoundness() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnScroll);
+
+ mChildrenContainer.addNotification(row, 0);
+
+ Assert.assertEquals(0f, row.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(0f, row.getBottomRoundness(), /* delta = */ 0f);
+ }
+
+ @Test
+ public void addNotification_shouldNotResetOtherRoundness() throws Exception {
+ ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.DefaultValue);
+ ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnDismissAnimation);
+
+ mChildrenContainer.addNotification(row1, 0);
+ mChildrenContainer.addNotification(row2, 0);
+
+ Assert.assertEquals(1f, row1.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row1.getBottomRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row2.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row2.getBottomRoundness(), /* delta = */ 0f);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 7741813..bda2336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.notification.stack
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
@@ -8,8 +9,10 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.SourceType
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.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -37,6 +40,13 @@
private val shelfState = shelf.viewState as NotificationShelf.ShelfState
private val ambientState = mock(AmbientState::class.java)
private val hostLayoutController: NotificationStackScrollLayoutController = mock()
+ private val notificationTestHelper by lazy {
+ allowTestableLooperAsMainThread()
+ NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this))
+ }
@Before
fun setUp() {
@@ -299,6 +309,39 @@
)
}
+ @Test
+ fun resetOnScrollRoundness_shouldSetOnScrollTo0() {
+ val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnScroll)
+
+ NotificationShelf.resetOnScrollRoundness(row)
+
+ assertEquals(0f, row.topRoundness)
+ assertEquals(0f, row.bottomRoundness)
+ }
+
+ @Test
+ fun resetOnScrollRoundness_shouldNotResetOtherRoundness() {
+ val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.DefaultValue)
+ val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnDismissAnimation)
+
+ NotificationShelf.resetOnScrollRoundness(row1)
+ NotificationShelf.resetOnScrollRoundness(row2)
+
+ assertEquals(1f, row1.topRoundness)
+ assertEquals(1f, row1.bottomRoundness)
+ assertEquals(1f, row2.topRoundness)
+ assertEquals(1f, row2.bottomRoundness)
+ }
+
private fun setFractionToShade(fraction: Float) {
whenever(ambientState.fractionToShade).thenReturn(fraction)
}
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 4dea6be..b850cf1 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
@@ -136,7 +136,7 @@
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
- mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+ mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
}
@Test
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 e1f20d5..5ebaf69 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
@@ -29,7 +29,7 @@
import android.os.PowerManager;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -177,7 +177,7 @@
AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
boolean navbarColorManagedByIme = true;
int behavior = 456;
- InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ int requestedVisibleTypes = WindowInsets.Type.systemBars();
String packageName = "test package name";
LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
@@ -187,7 +187,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails);
@@ -197,7 +197,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails
);
@@ -209,7 +209,7 @@
AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
boolean navbarColorManagedByIme = true;
int behavior = 456;
- InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ int requestedVisibleTypes = WindowInsets.Type.systemBars();
String packageName = "test package name";
LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
@@ -219,7 +219,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails);
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 5755782..7ce3a67 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
@@ -500,7 +500,7 @@
mKeyguardVieMediatorCallback);
// TODO: we should be able to call mCentralSurfaces.start() and have all the below values
- // initialized automatically.
+ // initialized automatically and make NPVC private.
mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController;
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index fca9771..287ebba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -30,6 +30,7 @@
import android.view.Display;
import android.view.View;
import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.lifecycle.Observer;
@@ -107,7 +108,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
assertTrue(mLightsOutNotifController.areLightsOut());
@@ -121,7 +122,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
assertFalse(mLightsOutNotifController.areLightsOut());
@@ -153,7 +154,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
@@ -174,7 +175,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
@@ -195,7 +196,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
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 0c35659..7166666 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
@@ -307,7 +307,7 @@
@Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+ when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
expansionEvent(
/* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
@@ -361,7 +361,7 @@
@Test
public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+ when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
mStatusBarKeyguardViewManager.show(null);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index 9957c2a..4d79a53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -3,12 +3,9 @@
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.Display
-import android.view.InsetsVisibilities
+import android.view.WindowInsets
import android.view.WindowInsetsController
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
-import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
-import android.view.WindowInsetsController.Appearance
+import android.view.WindowInsetsController.*
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
@@ -27,8 +24,8 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -88,7 +85,7 @@
changeSysBarAttrs(TEST_APPEARANCE)
verify(statusBarStateController)
- .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any())
+ .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), anyInt(), any())
}
@Test
@@ -97,7 +94,7 @@
verify(statusBarStateController)
.setSystemBarAttributes(
- eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
}
@Test
@@ -130,7 +127,7 @@
verify(statusBarStateController)
.setSystemBarAttributes(
- eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
}
@Test
@@ -197,7 +194,7 @@
appearanceRegions,
/* navbarColorManagedByIme= */ false,
WindowInsetsController.BEHAVIOR_DEFAULT,
- InsetsVisibilities(),
+ WindowInsets.Type.defaultVisible(),
"package name",
letterboxDetails)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index de1fec8..288f54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,16 +17,18 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileConnectionRepository : MobileConnectionRepository {
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
- override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+ override val subscriptionModelFlow = _subscriptionsModelFlow
private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled
+ private val _isDefaultDataSubscription = MutableStateFlow(true)
+ override val isDefaultDataSubscription = _isDefaultDataSubscription
+
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
_subscriptionsModelFlow.value = model
}
@@ -34,4 +36,8 @@
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
+
+ fun setIsDefaultDataSubscription(isDefault: Boolean) {
+ _isDefaultDataSubscription.value = isDefault
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 813e750..533d5d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -26,18 +27,26 @@
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
- private val _activeMobileDataSubscriptionId =
- MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
+ private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+ override val defaultDataSubId = _defaultDataSubId
+
+ private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
+ override val defaultMobileNetworkConnectivity = _mobileConnectivity
+
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
}
+ private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+ override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
+
fun setSubscriptions(subs: List<SubscriptionInfo>) {
_subscriptionsFlow.value = subs
}
@@ -46,6 +55,18 @@
_defaultDataSubRatConfig.value = config
}
+ fun setDefaultDataSubId(id: Int) {
+ _defaultDataSubId.value = id
+ }
+
+ fun setMobileConnectivity(model: MobileConnectivityModel) {
+ _mobileConnectivity.value = model
+ }
+
+ suspend fun triggerGlobalMobileDataSettingChangedEvent() {
+ _globalMobileDataSettingChangedEvent.emit(Unit)
+ }
+
fun setActiveMobileDataSubscriptionId(subId: Int) {
_activeMobileDataSubscriptionId.value = subId
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
index 6c495c5..141b50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -16,13 +16,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
/** Defaults to `true` */
class FakeUserSetupRepository : UserSetupRepository {
private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+ override val isUserSetupFlow = _isUserSetup
fun setUserSetup(setup: Boolean) {
_isUserSetup.value = setup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 0939364..5ce51bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.os.UserHandle
+import android.provider.Settings
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
@@ -42,6 +44,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -67,16 +70,23 @@
@Mock private lateinit var logger: ConnectivityPipelineLogger
private val scope = CoroutineScope(IMMEDIATE)
+ private val globalSettings = FakeSettings()
+ private val connectionsRepo = FakeMobileConnectionsRepository()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ globalSettings.userId = UserHandle.USER_ALL
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
underTest =
MobileConnectionRepositoryImpl(
+ context,
SUB_1_ID,
telephonyManager,
+ globalSettings,
+ connectionsRepo.defaultDataSubId,
+ connectionsRepo.globalMobileDataSettingChangedEvent,
IMMEDIATE,
logger,
scope,
@@ -290,14 +300,20 @@
}
@Test
- fun dataEnabled_isEnabled() =
+ fun dataEnabled_initial_false() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
- var latest: Boolean? = null
- val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+ assertThat(underTest.dataEnabled.value).isFalse()
+ }
- assertThat(latest).isTrue()
+ @Test
+ fun dataEnabled_isEnabled_true() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+ val job = underTest.dataEnabled.launchIn(this)
+
+ assertThat(underTest.dataEnabled.value).isTrue()
job.cancel()
}
@@ -306,10 +322,59 @@
fun dataEnabled_isDisabled() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ val job = underTest.dataEnabled.launchIn(this)
+
+ assertThat(underTest.dataEnabled.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDefaultDataSubscription_isDefault() =
+ runBlocking(IMMEDIATE) {
+ connectionsRepo.setDefaultDataSubId(SUB_1_ID)
+
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDefaultDataSubscription_isNotDefault() =
+ runBlocking(IMMEDIATE) {
+ // Our subId is SUB_1_ID
+ connectionsRepo.setDefaultDataSubId(123)
+
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+ // We don't read the setting directly, we query telephony when changes happen
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ globalSettings.putInt(subIdSettingName, 0)
+ assertThat(latest).isFalse()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+ globalSettings.putInt(subIdSettingName, 1)
+ assertThat(latest).isTrue()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ globalSettings.putInt(subIdSettingName, 0)
assertThat(latest).isFalse()
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
index 326e0d281..a953a3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -16,26 +16,33 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
+import com.android.internal.telephony.PhoneConstants
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
@@ -43,7 +50,6 @@
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -54,32 +60,26 @@
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
+ @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
private val scope = CoroutineScope(IMMEDIATE)
+ private val globalSettings = FakeSettings()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(
- broadcastDispatcher.broadcastFlow(
- any(),
- nullable(),
- ArgumentMatchers.anyInt(),
- nullable(),
- )
- )
- .thenReturn(flowOf(Unit))
underTest =
MobileConnectionsRepositoryImpl(
+ connectivityManager,
subscriptionManager,
telephonyManager,
logger,
- broadcastDispatcher,
+ fakeBroadcastDispatcher,
+ globalSettings,
context,
IMMEDIATE,
scope,
@@ -214,6 +214,139 @@
job.cancel()
}
+ @Test
+ fun testDefaultDataSubId_updatesOnBroadcast() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+ )
+ }
+
+ assertThat(latest).isEqualTo(SUB_2_ID)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ )
+ }
+
+ assertThat(latest).isEqualTo(SUB_1_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_default() {
+ assertThat(underTest.defaultMobileNetworkConnectivity.value)
+ .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+ }
+
+ @Test
+ fun mobileConnectivity_isConnected_isValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = true, validated = true)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true))
+
+ job.cancel()
+ }
+
+ @Test
+ fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() =
+ runBlocking(IMMEDIATE) {
+ var produced = false
+ val job =
+ underTest.globalMobileDataSettingChangedEvent
+ .onEach { produced = true }
+ .launchIn(this)
+
+ assertThat(produced).isFalse()
+
+ globalSettings.putInt(Settings.Global.MOBILE_DATA, 0)
+
+ assertThat(produced).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_isConnected_isNotValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = true, validated = false)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false))
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_isNotConnected_isNotValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = false, validated = false)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+
+ job.cancel()
+ }
+
+ /** In practice, I don't think this state can ever happen (!connected, validated) */
+ @Test
+ fun mobileConnectivity_isNotConnected_isValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = false, validated = true)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isEqualTo(MobileConnectivityModel(false, true))
+
+ job.cancel()
+ }
+
+ private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
+ whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated)
+ }
+
+ private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+ verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
verify(subscriptionManager)
@@ -242,5 +375,8 @@
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+ private const val NET_ID = 123
+ private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 5611c44..3ae7d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -28,18 +28,23 @@
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly
+ private val _isFailedConnection = MutableStateFlow(false)
+ override val isDefaultConnectionFailed = _isFailedConnection
+
+ override val isDataConnected = MutableStateFlow(true)
+
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
+ private val _isDefaultDataEnabled = MutableStateFlow(true)
+ override val isDefaultDataEnabled = _isDefaultDataEnabled
+
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val level = _level
private val _numberOfLevels = MutableStateFlow(4)
override val numberOfLevels = _numberOfLevels
- private val _cutOut = MutableStateFlow(false)
- override val cutOut = _cutOut
-
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
_iconGroup.value = group
}
@@ -52,6 +57,14 @@
_isDataEnabled.value = enabled
}
+ fun setIsDefaultDataEnabled(disabled: Boolean) {
+ _isDefaultDataEnabled.value = disabled
+ }
+
+ fun setIsFailedConnection(failed: Boolean) {
+ _isFailedConnection.value = failed
+ }
+
fun setLevel(level: Int) {
_level.value = level
}
@@ -59,8 +72,4 @@
fun setNumberOfLevels(num: Int) {
_numberOfLevels.value = num
}
-
- fun setCutOut(cutOut: Boolean) {
- _cutOut.value = cutOut
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 2bd2286..061c3b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -26,8 +26,7 @@
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
- MobileIconsInteractor {
+class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -46,9 +45,14 @@
FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
)
+ override val isDefaultConnectionFailed = MutableStateFlow(false)
+
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val filteredSubscriptions = _filteredSubscriptions
+ private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
+ override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index ff44af4..7fc1c0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -34,6 +35,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -49,12 +51,17 @@
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
private val connectionRepository = FakeMobileConnectionRepository()
+ private val scope = CoroutineScope(IMMEDIATE)
+
@Before
fun setUp() {
underTest =
MobileIconInteractorImpl(
+ scope,
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled,
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
+ mobileIconsInteractor.isDefaultConnectionFailed,
mobileMappingsProxy,
connectionRepository,
)
@@ -196,6 +203,66 @@
job.cancel()
}
+ @Test
+ fun test_isDefaultDataEnabled_matchesParent() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun test_isDefaultConnectionFailed_matchedParent() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isDefaultConnectionFailed.launchIn(this)
+
+ mobileIconsInteractor.isDefaultConnectionFailed.value = false
+ assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
+
+ mobileIconsInteractor.isDefaultConnectionFailed.value = true
+ assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_connected() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
+ )
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_notConnected() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
+ )
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 877ce0e..b56dcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
@@ -32,6 +34,7 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -168,6 +171,92 @@
job.cancel()
}
+ @Test
+ fun activeDataConnection_turnedOn() =
+ runBlocking(IMMEDIATE) {
+ CONNECTION_1.setDataEnabled(true)
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeDataConnection_turnedOff() =
+ runBlocking(IMMEDIATE) {
+ CONNECTION_1.setDataEnabled(true)
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ CONNECTION_1.setDataEnabled(false)
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeDataConnection_invalidSubId() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
+ yield()
+
+ // An invalid active subId should tell us that data is off
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_connected_validated_notFailed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_notConnected_notValidated_notFailed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_connected_notValidated_failed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false))
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index ce0f33f..d4c2c3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -46,10 +46,12 @@
MockitoAnnotations.initMocks(this)
interactor.apply {
setLevel(1)
- setCutOut(false)
+ setIsDefaultDataEnabled(true)
+ setIsFailedConnection(false)
setIconGroup(THREE_G)
setIsEmergencyOnly(false)
setNumberOfLevels(4)
+ isDataConnected.value = true
}
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
}
@@ -59,8 +61,23 @@
runBlocking(IMMEDIATE) {
var latest: Int? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ val expected = defaultSignal()
- assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconId_cutout_whenDefaultDataDisabled() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIsDefaultDataEnabled(false)
+
+ var latest: Int? = null
+ val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ val expected = defaultSignal(level = 1, connected = false)
+
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -97,6 +114,44 @@
}
@Test
+ fun networkType_nullWhenFailedConnection() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsDataEnabled(true)
+ interactor.setIsFailedConnection(true)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_nullWhenDataDisconnects() =
+ runBlocking(IMMEDIATE) {
+ val initial =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+
+ interactor.setIconGroup(THREE_G)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.setIconGroup(THREE_G)
+ assertThat(latest).isEqualTo(initial)
+
+ interactor.isDataConnected.value = false
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
fun networkType_null_changeToDisabled() =
runBlocking(IMMEDIATE) {
val expected =
@@ -119,6 +174,14 @@
job.cancel()
}
+ /** Convenience constructor for these tests */
+ private fun defaultSignal(
+ level: Int = 1,
+ connected: Boolean = true,
+ ): Int {
+ return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 11178db..627bd09 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import kotlinx.coroutines.flow.Flow
@@ -52,6 +53,12 @@
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+ private val _isBouncerShowing = MutableStateFlow(false)
+ override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+ private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
diff --git a/services/art-profile b/services/art-profile
index 3e05078..b6398c0 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -41551,7 +41551,7 @@
HPLcom/android/server/statusbar/StatusBarManagerService$1;->setNavigationBarLumaSamplingEnabled(IZ)V
HSPLcom/android/server/statusbar/StatusBarManagerService$1;->setNotificationDelegate(Lcom/android/server/notification/NotificationDelegate;)V
HPLcom/android/server/statusbar/StatusBarManagerService$1;->setTopAppHidesStatusBar(Z)V+]Lcom/android/internal/statusbar/IStatusBar;Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy;
-PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
+PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
HPLcom/android/server/statusbar/StatusBarManagerService$1;->setWindowState(III)V
PLcom/android/server/statusbar/StatusBarManagerService$1;->showChargingAnimation(I)V
PLcom/android/server/statusbar/StatusBarManagerService$1;->showRecentApps(Z)V
@@ -41677,6 +41677,11 @@
PLcom/android/server/statusbar/StatusBarManagerService;->setIcon(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V
HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V
HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V+]Landroid/util/ArrayMap;Landroid/util/ArrayMap;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V+]Landroid/os/Handler;Landroid/os/Handler;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+PLcom/android/server/statusbar/StatusBarManagerService;->setNavBarMode(I)V
PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
PLcom/android/server/statusbar/StatusBarManagerService;->showAuthenticationDialog(Landroid/hardware/biometrics/PromptInfo;Landroid/hardware/biometrics/IBiometricSysuiReceiver;[IZZIJLjava/lang/String;JI)V
PLcom/android/server/statusbar/StatusBarManagerService;->suppressAmbientDisplay(Z)V
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index cee78e0..34787a3 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -728,7 +728,7 @@
if (oldList != null && adding != null) {
adding.removeAll(oldList);
}
-
+ addingCount = CollectionUtils.size(adding);
EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, addingCount);
for (int i = 0; i < addingCount; i++) {
String packageName = adding.valueAt(i);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fa52ac9..8055afc 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,9 +26,15 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
@@ -39,13 +45,19 @@
import android.os.SystemProperties;
import android.util.PackageUtils;
import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ApkSigningBlockUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.util.FrameworkStatsLog;
+import libcore.util.HexEncoding;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -151,6 +163,117 @@
return 0;
}
+ private void printPackageMeasurements(PackageInfo packageInfo,
+ final PrintWriter pw) {
+ pw.print(mBinaryHashes.get(packageInfo.packageName) + ",");
+ // TODO: To be moved to somewhere more suitable
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+ ApkSignatureVerifier.verifySignaturesInternal(input,
+ packageInfo.applicationInfo.sourceDir,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, false);
+ if (parseResult.isError()) {
+ pw.println("ERROR: Failed to compute package content digest for "
+ + packageInfo.applicationInfo.sourceDir + "due to: "
+ + parseResult.getErrorMessage());
+ } else {
+ ApkSignatureVerifier.SigningDetailsWithDigests signingDetails =
+ parseResult.getResult();
+ Map<Integer, byte[]> contentDigests = signingDetails.contentDigests;
+ for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+ Integer algorithmId = entry.getKey();
+ byte[] cDigest = entry.getValue();
+ //pw.print("Content digest algorithm: ");
+ switch (algorithmId) {
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+ pw.print("CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+ pw.print("CHUNKED_SHA512:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ pw.print("VERITY_CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+ pw.print("SHA256:");
+ break;
+ default:
+ pw.print("UNKNOWN:");
+ }
+ pw.print(HexEncoding.encodeToString(cDigest, false));
+ }
+ }
+ }
+
+ private void printPackageInstallationInfo(PackageInfo packageInfo,
+ final PrintWriter pw) {
+ pw.println("--- Package Installation Info ---");
+ pw.println("Current install location: "
+ + packageInfo.applicationInfo.sourceDir);
+ if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
+ String origPackageFilepath = getOriginalPreinstalledLocation(
+ packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
+ pw.println("|--> Package pre-installed location: " + origPackageFilepath);
+ String digest = mBinaryHashes.get(origPackageFilepath);
+ if (digest == null) {
+ pw.println("ERROR finding SHA256-digest from cache...");
+ } else {
+ pw.println("|--> Pre-installed package SHA256-digest: " + digest);
+ }
+ }
+ pw.println("First install time (ms): " + packageInfo.firstInstallTime);
+ pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+ boolean isPreloaded = (packageInfo.firstInstallTime
+ == packageInfo.lastUpdateTime);
+ pw.println("Is preloaded: " + isPreloaded);
+
+ InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+ packageInfo.packageName);
+ if (installSourceInfo == null) {
+ pw.println("ERROR: Unable to obtain installSourceInfo of "
+ + packageInfo.packageName);
+ } else {
+ pw.println("Installation initiated by: "
+ + installSourceInfo.getInitiatingPackageName());
+ pw.println("Installation done by: "
+ + installSourceInfo.getInstallingPackageName());
+ pw.println("Installation originating from: "
+ + installSourceInfo.getOriginatingPackageName());
+ }
+
+ if (packageInfo.isApex) {
+ pw.println("Is an active APEX: " + packageInfo.isActiveApex);
+ }
+ }
+
+ private void printPackageSignerDetails(SigningInfo signerInfo,
+ final PrintWriter pw) {
+ if (signerInfo == null) {
+ pw.println("ERROR: Package's signingInfo is null.");
+ return;
+ }
+ pw.println("--- Package Signer Info ---");
+ pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
+ Signature[] packageSigners = signerInfo.getApkContentsSigners();
+ for (Signature packageSigner : packageSigners) {
+ byte[] packageSignerDigestBytes =
+ PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+ String packageSignerDigestHextring =
+ HexEncoding.encodeToString(packageSignerDigestBytes, false);
+ pw.println("Signer cert's SHA256-digest: " + packageSignerDigestHextring);
+ try {
+ PublicKey publicKey = packageSigner.getPublicKey();
+ pw.println("Signing key algorithm: " + publicKey.getAlgorithm());
+ } catch (CertificateException e) {
+ Slog.e(TAG,
+ "Failed to obtain public key of signer for cert with hash: "
+ + packageSignerDigestHextring);
+ e.printStackTrace();
+ }
+ }
+
+ }
+
private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
pw.println("--- Module Details ---");
pw.println("Module name: " + moduleInfo.getName());
@@ -190,28 +313,35 @@
return -1;
}
- pw.println("APEX Info:");
+ if (!verbose) {
+ pw.println("APEX Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
for (PackageInfo packageInfo : getInstalledApexs()) {
+ if (verbose) {
+ pw.println("APEX Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
String packageName = packageInfo.packageName;
- pw.println(packageName + ";"
- + packageInfo.getLongVersionCode() + ":"
- + mBinaryHashes.get(packageName).toLowerCase());
+ pw.print(packageName + ","
+ + packageInfo.getLongVersionCode() + ",");
+ printPackageMeasurements(packageInfo, pw);
+ pw.print("\n");
if (verbose) {
- pw.println("Install location: "
- + packageInfo.applicationInfo.sourceDir);
- pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime);
-
ModuleInfo moduleInfo;
try {
moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
+ pw.println("Is a module: true");
+ printModuleDetails(moduleInfo, pw);
} catch (PackageManager.NameNotFoundException e) {
- pw.println("Is A Module: False");
- pw.println("");
- continue;
+ pw.println("Is a module: false");
}
- pw.println("Is A Module: True");
- printModuleDetails(moduleInfo, pw);
+
+ printPackageInstallationInfo(packageInfo, pw);
+ printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
}
@@ -250,25 +380,37 @@
return -1;
}
- pw.println("Module Info:");
+ if (!verbose) {
+ pw.println("Module Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
+ if (verbose) {
+ pw.println("Module Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
- PackageManager.MATCH_APEX);
- pw.println(packageInfo.packageName + ";"
- + packageInfo.getLongVersionCode() + ":"
- + mBinaryHashes.get(packageName).toLowerCase());
+ PackageManager.MATCH_APEX
+ | PackageManager.GET_SIGNING_CERTIFICATES);
+ //pw.print("package:");
+ pw.print(packageInfo.packageName + ",");
+ pw.print(packageInfo.getLongVersionCode() + ",");
+ printPackageMeasurements(packageInfo, pw);
+ pw.print("\n");
if (verbose) {
- pw.println("Install location: "
- + packageInfo.applicationInfo.sourceDir);
printModuleDetails(module, pw);
+ printPackageInstallationInfo(packageInfo, pw);
+ printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
} catch (PackageManager.NameNotFoundException e) {
pw.println(packageName
- + ";ERROR:Unable to find PackageInfo for this module.");
+ + ",ERROR:Unable to find PackageInfo for this module.");
if (verbose) {
printModuleDetails(module, pw);
pw.println("");
@@ -468,7 +610,8 @@
return results;
}
List<PackageInfo> allPackages = pm.getInstalledPackages(
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX
+ | PackageManager.GET_SIGNING_CERTIFICATES));
if (allPackages == null) {
Slog.e(TAG, "Error obtaining installed packages (including APEX)");
return results;
@@ -478,6 +621,21 @@
return results;
}
+ @Nullable
+ private InstallSourceInfo getInstallSourceInfo(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+ return null;
+ }
+ try {
+ return pm.getInstallSourceInfo(packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
/**
* Updates the internal data structure with the most current APEX measurements.
@@ -539,6 +697,14 @@
return true;
}
+ private String getOriginalPreinstalledLocation(String packageName,
+ String currentInstalledLocation) {
+ if (currentInstalledLocation.contains("/decompressed/")) {
+ return "/system/apex/" + packageName + ".capex";
+ }
+ return "/system/apex" + packageName + "apex";
+ }
+
private void doFreshBinaryMeasurements() {
PackageManager pm = mContext.getPackageManager();
Slog.d(TAG, "Obtained package manager");
@@ -567,6 +733,27 @@
mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
}
+ for (PackageInfo packageInfo : getInstalledApexs()) {
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+ if (!appInfo.sourceDir.startsWith("/data/")) {
+ continue;
+ }
+ String origInstallPath = getOriginalPreinstalledLocation(packageInfo.packageName,
+ appInfo.sourceDir);
+
+ // compute SHA256 for the orig /system APEXs
+ String sha256digest = PackageUtils.computeSha256DigestForLargeFile(origInstallPath,
+ largeFileBuffer);
+ if (sha256digest == null) {
+ Slog.e(TAG, String.format("Failed to compute SHA256 digest for file at %s",
+ origInstallPath));
+ mBinaryHashes.put(origInstallPath, BINARY_HASH_ERROR);
+ } else {
+ mBinaryHashes.put(origInstallPath, sha256digest);
+ }
+ // there'd be no entry into mBinaryLastUpdateTimes
+ }
+
// Next, get all installed modules from PackageManager - skip over those APEXs we've
// processed above
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java
index a9bdf06..ee6d808 100644
--- a/services/core/java/com/android/server/ConsumerIrService.java
+++ b/services/core/java/com/android/server/ConsumerIrService.java
@@ -92,6 +92,8 @@
@Override
@EnforcePermission(TRANSMIT_IR)
public void transmit(String packageName, int carrierFrequency, int[] pattern) {
+ super.transmit_enforcePermission();
+
long totalXmitTime = 0;
for (int slice : pattern) {
@@ -128,6 +130,8 @@
@Override
@EnforcePermission(TRANSMIT_IR)
public int[] getCarrierFrequencies() {
+ super.getCarrierFrequencies_enforcePermission();
+
throwIfNoIrEmitter();
synchronized(mHalLock) {
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index ce0e69c..27215b2 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -77,6 +77,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean startInstallation(String dsuSlot) throws RemoteException {
+ super.startInstallation_enforcePermission();
+
IGsiService service = getGsiService();
mGsiService = service;
// priority from high to low: sysprop -> sdcard -> /data
@@ -124,6 +126,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public int createPartition(String name, long size, boolean readOnly) throws RemoteException {
+ super.createPartition_enforcePermission();
+
IGsiService service = getGsiService();
int status = service.createPartition(name, size, readOnly);
if (status != IGsiService.INSTALL_OK) {
@@ -135,6 +139,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean closePartition() throws RemoteException {
+ super.closePartition_enforcePermission();
+
IGsiService service = getGsiService();
if (service.closePartition() != 0) {
Slog.i(TAG, "Partition installation completes with error");
@@ -146,6 +152,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean finishInstallation() throws RemoteException {
+ super.finishInstallation_enforcePermission();
+
IGsiService service = getGsiService();
if (service.closeInstall() != 0) {
Slog.i(TAG, "Failed to finish installation");
@@ -157,12 +165,16 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public GsiProgress getInstallationProgress() throws RemoteException {
+ super.getInstallationProgress_enforcePermission();
+
return getGsiService().getInstallProgress();
}
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean abort() throws RemoteException {
+ super.abort_enforcePermission();
+
return getGsiService().cancelGsiInstall();
}
@@ -183,12 +195,16 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean isEnabled() throws RemoteException {
+ super.isEnabled_enforcePermission();
+
return getGsiService().isGsiEnabled();
}
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean remove() throws RemoteException {
+ super.remove_enforcePermission();
+
try {
GsiServiceCallback callback = new GsiServiceCallback();
synchronized (callback) {
@@ -205,6 +221,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException {
+ super.setEnable_enforcePermission();
+
IGsiService gsiService = getGsiService();
if (enable) {
try {
@@ -229,6 +247,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
+ super.setAshmem_enforcePermission();
+
try {
return getGsiService().setGsiAshmem(ashmem, size);
} catch (RemoteException e) {
@@ -239,6 +259,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean submitFromAshmem(long size) {
+ super.submitFromAshmem_enforcePermission();
+
try {
return getGsiService().commitGsiChunkFromAshmem(size);
} catch (RemoteException e) {
@@ -249,6 +271,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public boolean getAvbPublicKey(AvbPublicKey dst) {
+ super.getAvbPublicKey_enforcePermission();
+
try {
return getGsiService().getAvbPublicKey(dst) == 0;
} catch (RemoteException e) {
@@ -259,6 +283,8 @@
@Override
@EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
public long suggestScratchSize() throws RemoteException {
+ super.suggestScratchSize_enforcePermission();
+
return getGsiService().suggestScratchSize();
}
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index d29e25c..5d54b6c 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -867,6 +867,8 @@
public void shutdown() {
// TODO: remove from aidl if nobody calls externally
+ super.shutdown_enforcePermission();
+
Slog.i(TAG, "Shutting down");
}
@@ -1207,6 +1209,8 @@
@Override
public boolean setDataSaverModeEnabled(boolean enable) {
+ super.setDataSaverModeEnabled_enforcePermission();
+
if (DBG) Log.d(TAG, "setDataSaverMode: " + enable);
synchronized (mQuotaLock) {
if (mDataSaverMode == enable) {
@@ -1744,6 +1748,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY)
@Override
public boolean isNetworkRestricted(int uid) {
+ super.isNetworkRestricted_enforcePermission();
+
return isNetworkRestrictedInternal(uid);
}
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c1c9fbb..b56d1fc 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -231,7 +231,12 @@
String namespaceToReset = namespaceIt.next();
Properties properties = new Properties.Builder(namespaceToReset).build();
try {
- DeviceConfig.setProperties(properties);
+ if (!DeviceConfig.setProperties(properties)) {
+ logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+ + namespaceToReset
+ + ". Running `device_config get_sync_disabled_for_tests` will confirm"
+ + " if config-bulk-update is enabled.");
+ }
} catch (DeviceConfig.BadConfigException exception) {
logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
+ " is already banned, skip reset.");
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index e915fa1..ff903a0 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -37,6 +37,8 @@
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public String[] getSerialPorts() {
+ super.getSerialPorts_enforcePermission();
+
ArrayList<String> ports = new ArrayList<String>();
for (int i = 0; i < mSerialPorts.length; i++) {
String path = mSerialPorts[i];
@@ -51,6 +53,8 @@
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public ParcelFileDescriptor openSerialPort(String path) {
+ super.openSerialPort_enforcePermission();
+
for (int i = 0; i < mSerialPorts.length; i++) {
if (mSerialPorts[i].equals(path)) {
return native_open(path);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 72876f6..fcfee5b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1251,6 +1251,8 @@
// Binder entry point for kicking off an immediate fstrim
@Override
public void runMaintenance() {
+ super.runMaintenance_enforcePermission();
+
runIdleMaintenance(null);
}
@@ -2167,6 +2169,8 @@
@Override
public void shutdown(final IStorageShutdownObserver observer) {
+ super.shutdown_enforcePermission();
+
Slog.i(TAG, "Shutting down");
mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
}
@@ -2175,6 +2179,8 @@
@Override
public void mount(String volId) {
+ super.mount_enforcePermission();
+
final VolumeInfo vol = findVolumeByIdOrThrow(volId);
if (isMountDisallowed(vol)) {
throw new SecurityException("Mounting " + volId + " restricted by policy");
@@ -2243,6 +2249,8 @@
@Override
public void unmount(String volId) {
+ super.unmount_enforcePermission();
+
final VolumeInfo vol = findVolumeByIdOrThrow(volId);
unmount(vol);
}
@@ -2267,6 +2275,8 @@
@Override
public void format(String volId) {
+ super.format_enforcePermission();
+
final VolumeInfo vol = findVolumeByIdOrThrow(volId);
final String fsUuid = vol.fsUuid;
try {
@@ -2286,6 +2296,8 @@
@Override
public void benchmark(String volId, IVoldTaskListener listener) {
+ super.benchmark_enforcePermission();
+
try {
mVold.benchmark(volId, new IVoldTaskListener.Stub() {
@Override
@@ -2325,6 +2337,8 @@
@Override
public void partitionPublic(String diskId) {
+ super.partitionPublic_enforcePermission();
+
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
@@ -2337,6 +2351,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
@Override
public void partitionPrivate(String diskId) {
+ super.partitionPrivate_enforcePermission();
+
enforceAdminUser();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
@@ -2351,6 +2367,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)
@Override
public void partitionMixed(String diskId, int ratio) {
+ super.partitionMixed_enforcePermission();
+
enforceAdminUser();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
@@ -2366,6 +2384,8 @@
@Override
public void setVolumeNickname(String fsUuid, String nickname) {
+ super.setVolumeNickname_enforcePermission();
+
Objects.requireNonNull(fsUuid);
synchronized (mLock) {
final VolumeRecord rec = mRecords.get(fsUuid);
@@ -2379,6 +2399,8 @@
@Override
public void setVolumeUserFlags(String fsUuid, int flags, int mask) {
+ super.setVolumeUserFlags_enforcePermission();
+
Objects.requireNonNull(fsUuid);
synchronized (mLock) {
final VolumeRecord rec = mRecords.get(fsUuid);
@@ -2392,6 +2414,8 @@
@Override
public void forgetVolume(String fsUuid) {
+ super.forgetVolume_enforcePermission();
+
Objects.requireNonNull(fsUuid);
synchronized (mLock) {
@@ -2416,6 +2440,8 @@
@Override
public void forgetAllVolumes() {
+ super.forgetAllVolumes_enforcePermission();
+
synchronized (mLock) {
for (int i = 0; i < mRecords.size(); i++) {
final String fsUuid = mRecords.keyAt(i);
@@ -2448,6 +2474,8 @@
@Override
public void fstrim(int flags, IVoldTaskListener listener) {
+ super.fstrim_enforcePermission();
+
try {
// Block based checkpoint process runs fstrim. So, if checkpoint is in progress
// (first boot after OTA), We skip idle maintenance and make sure the last
@@ -2742,6 +2770,8 @@
@Override
public void setDebugFlags(int flags, int mask) {
+ super.setDebugFlags_enforcePermission();
+
if ((mask & (StorageManager.DEBUG_ADOPTABLE_FORCE_ON
| StorageManager.DEBUG_ADOPTABLE_FORCE_OFF)) != 0) {
final String value;
@@ -2812,6 +2842,8 @@
@Override
public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
+ super.setPrimaryStorageUuid_enforcePermission();
+
final VolumeInfo from;
final VolumeInfo to;
@@ -3020,6 +3052,8 @@
*/
@Override
public boolean needsCheckpoint() throws RemoteException {
+ super.needsCheckpoint_enforcePermission();
+
return mVold.needsCheckpoint();
}
@@ -3040,6 +3074,8 @@
@Override
public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+ super.createUserKey_enforcePermission();
+
try {
mVold.createUserKey(userId, serialNumber, ephemeral);
// New keys are always unlocked.
@@ -3055,6 +3091,8 @@
@Override
public void destroyUserKey(int userId) {
+ super.destroyUserKey_enforcePermission();
+
try {
mVold.destroyUserKey(userId);
// Destroying a key also locks it.
@@ -3070,6 +3108,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
@Override
public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
+ super.setUserKeyProtection_enforcePermission();
+
mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
}
@@ -3078,6 +3118,8 @@
@Override
public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
throws RemoteException {
+ super.unlockUserKey_enforcePermission();
+
if (StorageManager.isFileEncrypted()) {
mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
}
@@ -3090,6 +3132,8 @@
@Override
public void lockUserKey(int userId) {
// Do not lock user 0 data for headless system user
+ super.lockUserKey_enforcePermission();
+
if (userId == UserHandle.USER_SYSTEM
&& UserManager.isHeadlessSystemUserMode()) {
throw new IllegalArgumentException("Headless system user data cannot be locked..");
@@ -3153,6 +3197,8 @@
@Override
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+ super.prepareUserStorage_enforcePermission();
+
try {
prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags);
} catch (Exception e) {
@@ -3196,6 +3242,8 @@
@Override
public void destroyUserStorage(String volumeUuid, int userId, int flags) {
+ super.destroyUserStorage_enforcePermission();
+
try {
mVold.destroyUserStorage(volumeUuid, userId, flags);
} catch (Exception e) {
@@ -4247,6 +4295,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
@Override
public int getExternalStorageMountMode(int uid, String packageName) {
+ super.getExternalStorageMountMode_enforcePermission();
+
return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName);
}
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 9323d95..7f24c52 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -192,10 +193,12 @@
private static void dumpStackTraces() {
final ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
- ActivityManagerService.dumpStackTraces(pids, /* processCpuTracker= */null,
- /* lastPids= */null, Watchdog.getInterestingNativePids(),
+ ActivityManagerService.dumpStackTraces(pids,
+ /* processCpuTracker= */null, /* lastPids= */null,
+ CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()),
/* logExceptionCreatingFile= */null, /* subject= */null,
- /* criticalEventSection= */null, /* latencyTracker= */null);
+ /* criticalEventSection= */null, Runnable::run,
+ /* latencyTracker= */null);
}
@Override
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 83d86cd..953e850 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -371,6 +371,7 @@
// 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
// called onUserSwitching(), so calling it before onUserStarting() make it more
// consistent with that
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
}
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
@@ -381,14 +382,30 @@
*
* <p><b>NOTE: </b>this method should only be called when a user that is already running become
* visible; if the user is starting visible, callers should call
- * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead
+ * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead.
*/
public void onUserVisible(@UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
onUser(USER_VISIBLE, userId);
}
/**
+ * Updates the visibility of the system user.
+ *
+ * <p>Since the system user never stops, this method must be called when it's switched from / to
+ * foreground.
+ */
+ public void onSystemUserVisibilityChanged(boolean visible) {
+ int userId = UserHandle.USER_SYSTEM;
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+ if (visible) {
+ onUser(USER_VISIBLE, userId);
+ } else {
+ onUser(USER_INVISIBLE, userId);
+ }
+ }
+
+ /**
* Unlocks the given user.
*/
public void onUserUnlocking(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b00dec0..6eeb906 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -49,7 +49,7 @@
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.ProcessCpuTracker;
@@ -75,6 +75,7 @@
import java.util.List;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -894,8 +895,9 @@
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
StringWriter tracesFileException = new StringWriter();
final File stack = ActivityManagerService.dumpStackTraces(
- pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(),
- tracesFileException, subject, criticalEvents, /* latencyTracker= */null);
+ pids, processCpuTracker, new SparseBooleanArray(),
+ CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException,
+ subject, criticalEvents, Runnable::run, /* latencyTracker= */null);
// Give some extra time to make sure the stack traces get written.
// The system's been hanging for a whlie, another second or two won't hurt much.
SystemClock.sleep(5000);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 062afe9..a17b5e2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -332,6 +332,7 @@
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -467,11 +468,15 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
+import java.util.function.Supplier;
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -3427,14 +3432,15 @@
* @param lastPids of dalvik VM processes to dump stack traces for last
* @param nativePids optional list of native pids to dump stack crawls
* @param logExceptionCreatingFile optional writer to which we log errors creating the file
+ * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
* @param latencyTracker the latency tracker instance of the current ANR.
*/
public static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
- ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
- AnrLatencyTracker latencyTracker) {
- return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
- logExceptionCreatingFile, null, null, null, latencyTracker);
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+ @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+ return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+ logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
}
/**
@@ -3445,14 +3451,17 @@
* @param logExceptionCreatingFile optional writer to which we log errors creating the file
* @param subject optional line related to the error
* @param criticalEventSection optional lines containing recent critical events.
+ * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
* @param latencyTracker the latency tracker instance of the current ANR.
*/
public static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
- ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
- String subject, String criticalEventSection, AnrLatencyTracker latencyTracker) {
- return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
- logExceptionCreatingFile, null, subject, criticalEventSection, latencyTracker);
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+ String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
+ AnrLatencyTracker latencyTracker) {
+ return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+ logExceptionCreatingFile, null, subject, criticalEventSection,
+ auxiliaryTaskExecutor, latencyTracker);
}
/**
@@ -3460,49 +3469,26 @@
* of the very first pid to be dumped.
*/
/* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
- ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
- AnrLatencyTracker latencyTracker) {
+ @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
try {
+
if (latencyTracker != null) {
latencyTracker.dumpStackTracesStarted();
}
- ArrayList<Integer> extraPids = null;
- Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
+ Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
// Measure CPU usage as soon as we're called in order to get a realistic sampling
// of the top users at the time of the request.
- if (processCpuTracker != null) {
- if (latencyTracker != null) {
- latencyTracker.processCpuTrackerMethodsCalled();
- }
- processCpuTracker.init();
- try {
- Thread.sleep(200);
- } catch (InterruptedException ignored) {
- }
-
- processCpuTracker.update();
-
- // We'll take the stack crawls of just the top apps using CPU.
- final int workingStatsNumber = processCpuTracker.countWorkingStats();
- extraPids = new ArrayList<>();
- for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
- ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
- if (lastPids.indexOfKey(stats.pid) >= 0) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
-
- extraPids.add(stats.pid);
- } else {
- Slog.i(TAG, "Skipping next CPU consuming process, not a java proc: "
- + stats.pid);
- }
- }
- if (latencyTracker != null) {
- latencyTracker.processCpuTrackerMethodsReturned();
- }
+ Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
+ ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
+ Future<ArrayList<Integer>> extraPidsFuture = null;
+ if (extraPidsSupplier != null) {
+ extraPidsFuture =
+ CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
}
final File tracesDir = new File(ANR_TRACE_DIR);
@@ -3536,7 +3522,8 @@
}
long firstPidEndPos = dumpStackTraces(
- tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids, latencyTracker);
+ tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
+ extraPidsFuture, latencyTracker);
if (firstPidEndOffset != null) {
firstPidEndOffset.set(firstPidEndPos);
}
@@ -3554,6 +3541,42 @@
private static SimpleDateFormat sAnrFileDateFormat;
static final String ANR_FILE_PREFIX = "anr_";
+ private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
+ SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
+ if (latencyTracker != null) {
+ latencyTracker.processCpuTrackerMethodsCalled();
+ }
+ ArrayList<Integer> extraPids = new ArrayList<>();
+ processCpuTracker.init();
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {
+ }
+
+ processCpuTracker.update();
+
+ // We'll take the stack crawls of just the top apps using CPU.
+ final int workingStatsNumber = processCpuTracker.countWorkingStats();
+ for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
+ ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+ if (lastPids.indexOfKey(stats.pid) >= 0) {
+ if (DEBUG_ANR) {
+ Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+ }
+
+ extraPids.add(stats.pid);
+ } else {
+ Slog.i(TAG,
+ "Skipping next CPU consuming process, not a java proc: "
+ + stats.pid);
+ }
+ }
+ if (latencyTracker != null) {
+ latencyTracker.processCpuTrackerMethodsReturned();
+ }
+ return extraPids;
+ }
+
private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
if (sAnrFileDateFormat == null) {
sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
@@ -3660,8 +3683,8 @@
* @return The end offset of the trace of the very first PID
*/
public static long dumpStackTraces(String tracesFile,
- ArrayList<Integer> firstPids, ArrayList<Integer> nativePids,
- ArrayList<Integer> extraPids, AnrLatencyTracker latencyTracker) {
+ ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
+ Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
Slog.i(TAG, "Dumping to " + tracesFile);
@@ -3682,6 +3705,7 @@
if (latencyTracker != null) {
latencyTracker.dumpingFirstPidsStarted();
}
+
int num = firstPids.size();
for (int i = 0; i < num; i++) {
final int pid = firstPids.get(i);
@@ -3723,6 +3747,10 @@
}
// Next collect the stacks of the native pids
+ ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
+
+ Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
+
if (nativePids != null) {
if (latencyTracker != null) {
latencyTracker.dumpingNativePidsStarted();
@@ -3758,6 +3786,19 @@
}
// Lastly, dump stacks for all extra PIDs from the CPU tracker.
+ ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
+
+ if (extraPidsFuture != null) {
+ try {
+ extraPids = extraPidsFuture.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to collect extra pids", e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while collecting extra pids", e);
+ }
+ }
+ Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
+
if (extraPids != null) {
if (latencyTracker != null) {
latencyTracker.dumpingExtraPidsStarted();
@@ -3793,6 +3834,24 @@
return firstPidEnd;
}
+ private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
+ String logName) {
+
+ ArrayList<Integer> pids = null;
+
+ if (pidsFuture == null) {
+ return pids;
+ }
+ try {
+ pids = pidsFuture.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to collect " + logName, e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while collecting " + logName , e);
+ }
+ return pids;
+ }
+
@Override
public boolean clearApplicationUserData(final String packageName, boolean keepState,
final IPackageDataObserver observer, int userId) {
@@ -14664,6 +14723,15 @@
mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
}
+ final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+ final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+ final String callerPackage = info != null ? info.packageName : original.callerPackage;
+ if (callerPackage != null) {
+ mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+ original.callingUid, 0, callerPackage).sendToTarget();
+ }
+ }
+
final Intent verifyBroadcastLocked(Intent intent) {
// Refuse possible leaked file descriptors
if (intent != null && intent.hasFileDescriptors() == true) {
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 6de4118..71c80ea 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -25,10 +25,15 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.TimeoutRecord;
import com.android.server.wm.WindowProcessController;
import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -51,6 +56,14 @@
*/
private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2);
+ /**
+ * The keep alive time for the threads in the helper threadpool executor
+ */
+ private static final int AUX_THREAD_KEEP_ALIVE_SECOND = 10;
+
+ private static final ThreadFactory sDefaultThreadFactory = r ->
+ new Thread(r, "AnrAuxiliaryTaskExecutor");
+
@GuardedBy("mAnrRecords")
private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>();
private final AtomicBoolean mRunning = new AtomicBoolean(false);
@@ -66,8 +79,18 @@
@GuardedBy("mAnrRecords")
private int mProcessingPid = -1;
+ private final ExecutorService mAuxiliaryTaskExecutor;
+
AnrHelper(final ActivityManagerService service) {
+ this(service, new ThreadPoolExecutor(/* corePoolSize= */ 0, /* maximumPoolSize= */ 1,
+ /* keepAliveTime= */ AUX_THREAD_KEEP_ALIVE_SECOND, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(), sDefaultThreadFactory));
+ }
+
+ @VisibleForTesting
+ AnrHelper(ActivityManagerService service, ExecutorService auxExecutor) {
mService = service;
+ mAuxiliaryTaskExecutor = auxExecutor;
}
void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
@@ -108,7 +131,8 @@
}
timeoutRecord.mLatencyTracker.anrRecordPlacingOnQueueWithSize(mAnrRecords.size());
mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
- parentShortComponentName, parentProcess, aboveSystem, timeoutRecord));
+ parentShortComponentName, parentProcess, aboveSystem,
+ mAuxiliaryTaskExecutor, timeoutRecord));
}
startAnrConsumerIfNeeded();
} finally {
@@ -204,11 +228,12 @@
final ApplicationInfo mAppInfo;
final WindowProcessController mParentProcess;
final boolean mAboveSystem;
+ final ExecutorService mAuxiliaryTaskExecutor;
final long mTimestamp = SystemClock.uptimeMillis();
AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem,
- TimeoutRecord timeoutRecord) {
+ ExecutorService auxiliaryTaskExecutor, TimeoutRecord timeoutRecord) {
mApp = anrProcess;
mPid = anrProcess.mPid;
mActivityShortComponentName = activityShortComponentName;
@@ -217,6 +242,7 @@
mAppInfo = aInfo;
mParentProcess = parentProcess;
mAboveSystem = aboveSystem;
+ mAuxiliaryTaskExecutor = auxiliaryTaskExecutor;
}
void appNotResponding(boolean onlyDumpSelf) {
@@ -224,7 +250,7 @@
mTimeoutRecord.mLatencyTracker.anrProcessingStarted();
mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
mParentShortComponentName, mParentProcess, mAboveSystem,
- mTimeoutRecord, onlyDumpSelf);
+ mTimeoutRecord, mAuxiliaryTaskExecutor, onlyDumpSelf);
} finally {
mTimeoutRecord.mLatencyTracker.anrProcessingEnded();
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 2e12309..c994f13 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -135,14 +135,6 @@
private int mActiveIndex;
/**
- * When defined, the receiver actively being dispatched into this process
- * was considered "blocked" until at least the given count of other
- * receivers have reached a terminal state; typically used for ordered
- * broadcasts and priority traunches.
- */
- private int mActiveBlockedUntilTerminalCount;
-
- /**
* Count of {@link #mActive} broadcasts that have been dispatched since this
* queue was last idle.
*/
@@ -206,15 +198,11 @@
* given count of other receivers have reached a terminal state; typically
* used for ordered broadcasts and priority traunches.
*/
- public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
- int blockedUntilTerminalCount) {
+ public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
if (record.isReplacePending()) {
- boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex,
- blockedUntilTerminalCount)
- || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex,
- blockedUntilTerminalCount)
- || replaceBroadcastInQueue(mPendingOffload, record, recordIndex,
- blockedUntilTerminalCount);
+ boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex)
+ || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex)
+ || replaceBroadcastInQueue(mPendingOffload, record, recordIndex);
if (didReplace) {
return;
}
@@ -225,7 +213,6 @@
SomeArgs newBroadcastArgs = SomeArgs.obtain();
newBroadcastArgs.arg1 = record;
newBroadcastArgs.argi1 = recordIndex;
- newBroadcastArgs.argi2 = blockedUntilTerminalCount;
// Cross-broadcast prioritization policy: some broadcasts might warrant being
// issued ahead of others that are already pending, for example if this new
@@ -244,7 +231,7 @@
* {@code false} otherwise.
*/
private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
- @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+ @NonNull BroadcastRecord record, int recordIndex) {
final Iterator<SomeArgs> it = queue.descendingIterator();
final Object receiver = record.receivers.get(recordIndex);
while (it.hasNext()) {
@@ -259,7 +246,6 @@
// Exact match found; perform in-place swap
args.arg1 = record;
args.argi1 = recordIndex;
- args.argi2 = blockedUntilTerminalCount;
onBroadcastDequeued(testRecord, testRecordIndex);
onBroadcastEnqueued(record, recordIndex);
return true;
@@ -411,7 +397,6 @@
final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
- mActiveBlockedUntilTerminalCount = next.argi2;
mActiveCountSinceIdle++;
mActiveViaColdStart = false;
next.recycle();
@@ -424,7 +409,6 @@
public void makeActiveIdle() {
mActive = null;
mActiveIndex = 0;
- mActiveBlockedUntilTerminalCount = -1;
mActiveCountSinceIdle = 0;
mActiveViaColdStart = false;
invalidateRunnableAt();
@@ -705,7 +689,7 @@
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
- final int blockedUntilTerminalCount = next.argi2;
+ final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
final long runnableAt = r.enqueueTime;
// We might be blocked waiting for other receivers to finish,
@@ -871,19 +855,19 @@
pw.println();
pw.increaseIndent();
if (mActive != null) {
- dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
+ dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
}
for (SomeArgs args : mPendingUrgent) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord("URGENT", now, pw, r, args.argi1, args.argi2);
+ dumpRecord("URGENT", now, pw, r, args.argi1);
}
for (SomeArgs args : mPending) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord(null, now, pw, r, args.argi1, args.argi2);
+ dumpRecord(null, now, pw, r, args.argi1);
}
for (SomeArgs args : mPendingOffload) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord("OFFLOAD", now, pw, r, args.argi1, args.argi2);
+ dumpRecord("OFFLOAD", now, pw, r, args.argi1);
}
pw.decreaseIndent();
pw.println();
@@ -891,8 +875,7 @@
@NeverCompile
private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
- @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex,
- int blockedUntilTerminalCount) {
+ @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
TimeUtils.formatDuration(record.enqueueTime, now, pw);
pw.print(' ');
pw.println(record.toShortString());
@@ -918,6 +901,7 @@
pw.print(info.activityInfo.name);
}
pw.println();
+ final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
if (blockedUntilTerminalCount != -1) {
pw.print(" blocked until ");
pw.print(blockedUntilTerminalCount);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e0fab2c..153ad1e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -77,6 +77,18 @@
}
}
+ static void checkState(boolean expression, @NonNull String msg) {
+ if (!expression) {
+ throw new IllegalStateException(msg);
+ }
+ }
+
+ static void checkStateWtf(boolean expression, @NonNull String msg) {
+ if (!expression) {
+ Slog.wtf(TAG, new IllegalStateException(msg));
+ }
+ }
+
static int traceBegin(@NonNull String methodName) {
final int cookie = methodName.hashCode();
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index ffc54d9..454b284 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1690,13 +1690,7 @@
System.identityHashCode(original));
}
- final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
- final String callerPackage = info != null ? info.packageName : original.callerPackage;
- if (callerPackage != null) {
- mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
- original.callingUid, 0, callerPackage).sendToTarget();
- }
-
+ mService.notifyBroadcastFinishedLocked(original);
mHistory.addBroadcastToHistoryLocked(original);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index af2a97e..c3839a9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -25,14 +25,12 @@
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
-import static com.android.server.am.BroadcastRecord.getReceiverPriority;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
@@ -321,7 +319,6 @@
return;
}
- final int cookie = traceBegin("updateRunnableList");
final boolean wantQueue = queue.isRunnable();
final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
|| (queue.runnableAtNext != null);
@@ -348,8 +345,6 @@
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
}
-
- traceEnd(cookie);
}
/**
@@ -592,36 +587,11 @@
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
- int lastPriority = 0;
- int lastPriorityIndex = 0;
-
for (int i = 0; i < r.receivers.size(); i++) {
final Object receiver = r.receivers.get(i);
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
getReceiverProcessName(receiver), getReceiverUid(receiver));
-
- final int blockedUntilTerminalCount;
- if (r.ordered) {
- // When sending an ordered broadcast, we need to block this
- // receiver until all previous receivers have terminated
- blockedUntilTerminalCount = i;
- } else if (r.prioritized) {
- // When sending a prioritized broadcast, we only need to wait
- // for the previous traunch of receivers to be terminated
- final int thisPriority = getReceiverPriority(receiver);
- if ((i == 0) || (thisPriority != lastPriority)) {
- lastPriority = thisPriority;
- lastPriorityIndex = i;
- blockedUntilTerminalCount = i;
- } else {
- blockedUntilTerminalCount = lastPriorityIndex;
- }
- } else {
- // Otherwise we don't need to block at all
- blockedUntilTerminalCount = -1;
- }
-
- queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount);
+ queue.enqueueOrReplaceBroadcast(r, i);
updateRunnableList(queue);
enqueueUpdateRunningList();
}
@@ -757,7 +727,7 @@
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
- enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
return;
}
@@ -917,9 +887,12 @@
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
@DeliveryState int deliveryState, @NonNull String reason) {
- final int cookie = traceBegin("finishReceiver");
- checkState(queue.isActive(), "isActive");
+ if (!queue.isActive()) {
+ logw("Ignoring finish; no active broadcast for " + queue);
+ return false;
+ }
+ final int cookie = traceBegin("finishReceiver");
final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
@@ -1429,6 +1402,7 @@
final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
+ mService.notifyBroadcastFinishedLocked(r);
mHistory.addBroadcastToHistoryLocked(r);
r.finishTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 65f9b9b..6ea2dee 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -96,6 +96,7 @@
final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
+ final int[] blockedUntilTerminalCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@Nullable IIntentReceiver resultTo; // who receives final result if non-null
boolean deferred;
@@ -375,6 +376,7 @@
options = _options;
receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
delivery = new int[_receivers != null ? _receivers.size() : 0];
+ blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
@@ -385,7 +387,7 @@
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
- prioritized = isPrioritized(receivers);
+ prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
userId = _userId;
nextReceiver = 0;
state = IDLE;
@@ -427,6 +429,7 @@
options = from.options;
receivers = from.receivers;
delivery = from.delivery;
+ blockedUntilTerminalCount = from.blockedUntilTerminalCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
resultToApp = from.resultToApp;
@@ -690,22 +693,60 @@
}
/**
- * Return if given receivers list has more than one traunch of priorities.
+ * Determine if the result of {@link #calculateBlockedUntilTerminalCount}
+ * has prioritized tranches of receivers.
*/
@VisibleForTesting
- static boolean isPrioritized(@NonNull List<Object> receivers) {
- int firstPriority = 0;
- for (int i = 0; i < receivers.size(); i++) {
- final int thisPriority = getReceiverPriority(receivers.get(i));
- if (i == 0) {
- firstPriority = thisPriority;
- } else if (thisPriority != firstPriority) {
- return true;
- }
- }
- return false;
+ static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+ boolean ordered) {
+ return !ordered && (blockedUntilTerminalCount.length > 0)
+ && (blockedUntilTerminalCount[0] != -1);
}
+ /**
+ * Calculate the {@link #terminalCount} that each receiver should be
+ * considered blocked until.
+ * <p>
+ * For example, in an ordered broadcast, receiver {@code N} is blocked until
+ * receiver {@code N-1} reaches a terminal state. Similarly, in a
+ * prioritized broadcast, receiver {@code N} is blocked until all receivers
+ * of a higher priority reach a terminal state.
+ * <p>
+ * When there are no terminal count constraints, the blocked value for each
+ * receiver is {@code -1}.
+ */
+ @VisibleForTesting
+ static @NonNull int[] calculateBlockedUntilTerminalCount(
+ @NonNull List<Object> receivers, boolean ordered) {
+ final int N = receivers.size();
+ final int[] blockedUntilTerminalCount = new int[N];
+ int lastPriority = 0;
+ int lastPriorityIndex = 0;
+ for (int i = 0; i < N; i++) {
+ if (ordered) {
+ // When sending an ordered broadcast, we need to block this
+ // receiver until all previous receivers have terminated
+ blockedUntilTerminalCount[i] = i;
+ } else {
+ // When sending a prioritized broadcast, we only need to wait
+ // for the previous tranche of receivers to be terminated
+ final int thisPriority = getReceiverPriority(receivers.get(i));
+ if ((i == 0) || (thisPriority != lastPriority)) {
+ lastPriority = thisPriority;
+ lastPriorityIndex = i;
+ blockedUntilTerminalCount[i] = i;
+ } else {
+ blockedUntilTerminalCount[i] = lastPriorityIndex;
+ }
+ }
+ }
+ // If the entire list is in the same priority tranche, mark as -1 to
+ // indicate that none of them need to wait
+ if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
+ Arrays.fill(blockedUntilTerminalCount, -1);
+ }
+ return blockedUntilTerminalCount;
+ }
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index dec8b62..60e6754 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -116,7 +116,7 @@
30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
30087 ssm_user_stopped (userId|1|5)
30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
-30089 ssm_user_visible (userId|1|5)
+30089 ssm_user_visibility_changed (userId|1|5),(visible|1)
# Foreground service start/stop events.
30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index f461f3d..68d906b 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -44,7 +44,7 @@
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
@@ -62,8 +62,12 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
+
/**
* The error state of the process, such as if it's crashing/ANR etc.
*/
@@ -259,12 +263,13 @@
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, TimeoutRecord timeoutRecord,
- boolean onlyDumpSelf) {
+ ExecutorService auxiliaryTaskExecutor, boolean onlyDumpSelf) {
String annotation = timeoutRecord.mReason;
AnrLatencyTracker latencyTracker = timeoutRecord.mLatencyTracker;
+ Future<?> updateCpuStatsNowFirstCall = null;
ArrayList<Integer> firstPids = new ArrayList<>(5);
- SparseArray<Boolean> lastPids = new SparseArray<>(20);
+ SparseBooleanArray lastPids = new SparseBooleanArray(20);
mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
latencyTracker.waitingOnAMSLockStarted();
@@ -277,10 +282,15 @@
});
long anrTime = SystemClock.uptimeMillis();
+
if (isMonitorCpuUsage()) {
- latencyTracker.updateCpuStatsNowCalled();
- mService.updateCpuStatsNow();
- latencyTracker.updateCpuStatsNowReturned();
+ updateCpuStatsNowFirstCall = auxiliaryTaskExecutor.submit(
+ () -> {
+ latencyTracker.updateCpuStatsNowCalled();
+ mService.updateCpuStatsNow();
+ latencyTracker.updateCpuStatsNowReturned();
+ });
+
}
final boolean isSilentAnr;
@@ -369,7 +379,7 @@
firstPids.add(myPid);
if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
} else {
- lastPids.put(myPid, Boolean.TRUE);
+ lastPids.put(myPid, true);
if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
}
}
@@ -432,30 +442,40 @@
report.append(currentPsiState);
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
- latencyTracker.nativePidCollectionStarted();
- // don't dump native PIDs for background ANRs unless it is the process of interest
- String[] nativeProcs = null;
- if (isSilentAnr || onlyDumpSelf) {
- for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
- if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
- nativeProcs = new String[] { mApp.processName };
- break;
- }
- }
- } else {
- nativeProcs = NATIVE_STACKS_OF_INTEREST;
- }
+ // We push the native pids collection task to the helper thread through
+ // the Anr auxiliary task executor, and wait on it later after dumping the first pids
+ Future<ArrayList<Integer>> nativePidsFuture =
+ auxiliaryTaskExecutor.submit(
+ () -> {
+ latencyTracker.nativePidCollectionStarted();
+ // don't dump native PIDs for background ANRs unless
+ // it is the process of interest
+ String[] nativeProcs = null;
+ if (isSilentAnr || onlyDumpSelf) {
+ for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
+ if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
+ nativeProcs = new String[] { mApp.processName };
+ break;
+ }
+ }
+ } else {
+ nativeProcs = NATIVE_STACKS_OF_INTEREST;
+ }
- int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
- ArrayList<Integer> nativePids = null;
+ int[] pids = nativeProcs == null
+ ? null : Process.getPidsForCommands(nativeProcs);
+ ArrayList<Integer> nativePids = null;
- if (pids != null) {
- nativePids = new ArrayList<>(pids.length);
- for (int i : pids) {
- nativePids.add(i);
- }
- }
- latencyTracker.nativePidCollectionEnded();
+ if (pids != null) {
+ nativePids = new ArrayList<>(pids.length);
+ for (int i : pids) {
+ nativePids.add(i);
+ }
+ }
+ latencyTracker.nativePidCollectionEnded();
+ return nativePids;
+ });
+
// For background ANRs, don't pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
StringWriter tracesFileException = new StringWriter();
@@ -463,10 +483,18 @@
final AtomicLong firstPidEndOffset = new AtomicLong(-1);
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
- nativePids, tracesFileException, firstPidEndOffset, annotation, criticalEventLog,
- latencyTracker);
+ nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
+ criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
if (isMonitorCpuUsage()) {
+ // Wait for the first call to finish
+ try {
+ updateCpuStatsNowFirstCall.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to update the CPU stats", e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while updating the CPU stats", e);
+ }
mService.updateCpuStatsNow();
mService.mAppProfiler.printCurrentCpuState(report, anrTime);
info.append(processCpuTracker.printCurrentLoad());
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8d3890c..af55980 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2190,7 +2190,7 @@
if (oldUserId == UserHandle.USER_SYSTEM) {
// System user is never stopped, but its visibility is changed (as it is brought to the
// background)
- updateSystemUserVisibility(/* visible= */ false);
+ updateSystemUserVisibility(t, /* visible= */ false);
}
t.traceEnd(); // end continueUserSwitch
@@ -2549,10 +2549,15 @@
// TODO(b/242195409): remove this method if initial system user boot logic is refactored?
void onSystemUserStarting() {
- updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
+ if (!UserManager.isHeadlessSystemUserMode()) {
+ // Don't need to call on HSUM because it will be called when the system user is
+ // restarted on background
+ mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true);
+ }
}
- private void updateSystemUserVisibility(boolean visible) {
+ private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
+ t.traceBegin("update-system-userVisibility-" + visible);
if (DEBUG_MU) {
Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
}
@@ -2564,7 +2569,8 @@
mVisibleUsers.delete(userId);
}
}
- mInjector.onUserStarting(userId, visible);
+ mInjector.notifySystemUserVisibilityChanged(visible);
+ t.traceEnd();
}
/**
@@ -3673,5 +3679,8 @@
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
visible);
}
+ void notifySystemUserVisibilityChanged(boolean visible) {
+ getSystemServiceManager().onSystemUserVisibilityChanged(visible);
+ }
}
}
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 4aaf1ab..908cb3f 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -185,6 +185,8 @@
@Override
@EnforcePermission(MANAGE_GAME_ACTIVITY)
public void createGameSession(int taskId) {
+ super.createGameSession_enforcePermission();
+
mBackgroundExecutor.execute(() -> {
GameServiceProviderInstanceImpl.this.createGameSession(taskId);
});
@@ -197,6 +199,8 @@
@EnforcePermission(MANAGE_GAME_ACTIVITY)
public void takeScreenshot(int taskId,
@NonNull AndroidFuture gameScreenshotResultFuture) {
+ super.takeScreenshot_enforcePermission();
+
mBackgroundExecutor.execute(() -> {
GameServiceProviderInstanceImpl.this.takeScreenshot(taskId,
gameScreenshotResultFuture);
@@ -206,6 +210,8 @@
@Override
@EnforcePermission(MANAGE_GAME_ACTIVITY)
public void restartGame(int taskId) {
+ super.restartGame_enforcePermission();
+
mBackgroundExecutor.execute(() -> {
GameServiceProviderInstanceImpl.this.restartGame(taskId);
});
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index bbffc89..2fe06094 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -286,22 +286,9 @@
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
}
-
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- AudioDeviceAttributes device = null;
- if (on) {
- device = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
- } else {
- CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
- if (client == null || !client.requestsSpeakerphone()) {
- return;
- }
- }
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource));
- }
- }
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+ cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
}
/**
@@ -311,6 +298,9 @@
* @param device Device selected or null to unselect.
* @param eventSource for logging purposes
*/
+
+ private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
+
/*package*/ boolean setCommunicationDevice(
IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
@@ -318,21 +308,53 @@
Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
}
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- AudioDeviceAttributes deviceAttr = null;
- if (device != null) {
- deviceAttr = new AudioDeviceAttributes(device);
- } else {
- CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
- if (client == null) {
- return false;
+ AudioDeviceAttributes deviceAttr =
+ (device != null) ? new AudioDeviceAttributes(device) : null;
+ CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
+ device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+ postSetCommunicationDeviceForClient(deviceInfo);
+ boolean status;
+ synchronized (deviceInfo) {
+ final long start = System.currentTimeMillis();
+ long elapsed = 0;
+ while (deviceInfo.mWaitForStatus) {
+ try {
+ deviceInfo.wait(SET_COMMUNICATION_DEVICE_TIMEOUT_MS - elapsed);
+ } catch (InterruptedException e) {
+ elapsed = System.currentTimeMillis() - start;
+ if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) {
+ deviceInfo.mStatus = false;
+ deviceInfo.mWaitForStatus = false;
}
}
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource));
+ }
+ status = deviceInfo.mStatus;
+ }
+ return status;
+ }
+
+ /**
+ * Sets or resets the communication device for matching client. If no client matches and the
+ * request is to reset for a given device (deviceInfo.mOn == false), the method is a noop.
+ * @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo}
+ * @return true if the communication device is set or reset
+ */
+ @GuardedBy("mDeviceStateLock")
+ /*package*/ boolean onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
+ }
+ if (!deviceInfo.mOn) {
+ CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+ if (client == null || (deviceInfo.mDevice != null
+ && !deviceInfo.mDevice.equals(client.getDevice()))) {
+ return false;
}
}
+
+ AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
+ setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
+ deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
return true;
}
@@ -390,7 +412,7 @@
mBtHelper.stopBluetoothSco(eventSource);
}
- sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
+ updateCommunicationRoute(eventSource);
}
/**
@@ -424,7 +446,7 @@
CommunicationRouteClient crc = topCommunicationRouteClient();
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "requestedCommunicationDevice, device: "
+ Log.v(TAG, "requestedCommunicationDevice: "
+ device + " mAudioModeOwner: " + mAudioModeOwner.toString());
}
return device;
@@ -822,37 +844,22 @@
@NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid);
+ Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
}
-
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- AudioDeviceAttributes device =
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
-
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, device, scoAudioMode, eventSource));
- }
- }
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+ cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ true, scoAudioMode, eventSource, false));
}
/*package*/ void stopBluetoothScoForClient(
IBinder cb, int pid, @NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid);
+ Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
}
-
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
- if (client == null || !client.requestsBluetoothSco()) {
- return;
- }
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource));
- }
- }
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+ cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
}
/*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -990,7 +997,8 @@
}
//---------------------------------------------------------------------
- // Message handling on behalf of helper classes
+ // Message handling on behalf of helper classes.
+ // Each of these methods posts a message to mBrokerHandler message queue.
/*package*/ void postBroadcastScoConnectionState(int state) {
sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
}
@@ -1046,28 +1054,34 @@
sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
}
- /*package*/ void postSetCommunicationRouteForClient(CommunicationClientInfo info) {
- sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT, SENDMSG_QUEUE, info);
+ /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
+ sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info);
}
/*package*/ void postScoAudioStateChanged(int state) {
sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
}
- /*package*/ static final class CommunicationClientInfo {
- final @NonNull IBinder mCb;
- final int mPid;
- final @NonNull AudioDeviceAttributes mDevice;
- final int mScoAudioMode;
- final @NonNull String mEventSource;
+ /*package*/ static final class CommunicationDeviceInfo {
+ final @NonNull IBinder mCb; // Identifies the requesting client for death handler
+ final int mPid; // Requester process ID
+ final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
+ final boolean mOn; // true if setting, false if resetting
+ final int mScoAudioMode; // only used for SCO: requested audio mode
+ final @NonNull String mEventSource; // caller identifier for logging
+ boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
+ boolean mStatus = false; // completion status only used if mWaitForStatus is true
- CommunicationClientInfo(@NonNull IBinder cb, int pid, @NonNull AudioDeviceAttributes device,
- int scoAudioMode, @NonNull String eventSource) {
+ CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+ @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
+ @NonNull String eventSource, boolean waitForStatus) {
mCb = cb;
mPid = pid;
mDevice = device;
+ mOn = on;
mScoAudioMode = scoAudioMode;
mEventSource = eventSource;
+ mWaitForStatus = waitForStatus;
}
// redefine equality op so we can match messages intended for this client
@@ -1079,21 +1093,24 @@
if (this == o) {
return true;
}
- if (!(o instanceof CommunicationClientInfo)) {
+ if (!(o instanceof CommunicationDeviceInfo)) {
return false;
}
- return mCb.equals(((CommunicationClientInfo) o).mCb)
- && mPid == ((CommunicationClientInfo) o).mPid;
+ return mCb.equals(((CommunicationDeviceInfo) o).mCb)
+ && mPid == ((CommunicationDeviceInfo) o).mPid;
}
@Override
public String toString() {
- return "CommunicationClientInfo mCb=" + mCb.toString()
- +"mPid=" + mPid
- +"mDevice=" + mDevice.toString()
- +"mScoAudioMode=" + mScoAudioMode
- +"mEventSource=" + mEventSource;
+ return "CommunicationDeviceInfo mCb=" + mCb.toString()
+ + " mPid=" + mPid
+ + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
+ + " mOn=" + mOn
+ + " mScoAudioMode=" + mScoAudioMode
+ + " mEventSource=" + mEventSource
+ + " mWaitForStatus=" + mWaitForStatus
+ + " mStatus=" + mStatus;
}
}
@@ -1297,7 +1314,7 @@
updateActiveCommunicationDevice();
mDeviceInventory.onRestoreDevices();
mBtHelper.onAudioServerDiedRestoreA2dp();
- onUpdateCommunicationRoute("MSG_RESTORE_DEVICES");
+ updateCommunicationRoute("MSG_RESTORE_DEVICES");
}
}
break;
@@ -1392,12 +1409,19 @@
}
break;
- case MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT:
+ case MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT:
+ CommunicationDeviceInfo deviceInfo = (CommunicationDeviceInfo) msg.obj;
+ boolean status;
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- CommunicationClientInfo info = (CommunicationClientInfo) msg.obj;
- setCommunicationRouteForClient(info.mCb, info.mPid, info.mDevice,
- info.mScoAudioMode, info.mEventSource);
+ status = onSetCommunicationDeviceForClient(deviceInfo);
+ }
+ }
+ synchronized (deviceInfo) {
+ if (deviceInfo.mWaitForStatus) {
+ deviceInfo.mStatus = status;
+ deviceInfo.mWaitForStatus = false;
+ deviceInfo.notify();
}
}
break;
@@ -1410,14 +1434,6 @@
}
break;
- case MSG_L_UPDATE_COMMUNICATION_ROUTE:
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- onUpdateCommunicationRoute((String) msg.obj);
- }
- }
- break;
-
case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
@@ -1568,8 +1584,7 @@
private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37;
private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
- private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
- private static final int MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT = 42;
+ private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
@@ -1793,18 +1808,6 @@
AudioDeviceAttributes getDevice() {
return mDevice;
}
-
- boolean requestsBluetoothSco() {
- return mDevice != null
- && mDevice.getType()
- == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
- }
-
- boolean requestsSpeakerphone() {
- return mDevice != null
- && mDevice.getType()
- == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
- }
}
// @GuardedBy("mSetModeLock")
@@ -1852,14 +1855,14 @@
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
- private void onUpdateCommunicationRoute(String eventSource) {
+ private void updateCommunicationRoute(String eventSource) {
AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice();
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ Log.v(TAG, "updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource);
}
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ "updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource)));
if (preferredCommunicationDevice == null
@@ -1895,7 +1898,7 @@
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRouteClient(String eventSource) {
- onUpdateCommunicationRoute(eventSource);
+ updateCommunicationRoute(eventSource);
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9d6fa9e..f8a6462 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1859,6 +1859,8 @@
* @see AudioManager#setSupportedSystemUsages(int[])
*/
public void setSupportedSystemUsages(@NonNull @AttributeSystemUsage int[] systemUsages) {
+ super.setSupportedSystemUsages_enforcePermission();
+
verifySystemUsages(systemUsages);
synchronized (mSupportedSystemUsagesLock) {
@@ -1872,6 +1874,8 @@
* @see AudioManager#getSupportedSystemUsages()
*/
public @NonNull @AttributeSystemUsage int[] getSupportedSystemUsages() {
+ super.getSupportedSystemUsages_enforcePermission();
+
synchronized (mSupportedSystemUsagesLock) {
return Arrays.copyOf(mSupportedSystemUsages, mSupportedSystemUsages.length);
}
@@ -1893,6 +1897,8 @@
@NonNull
public List<AudioProductStrategy> getAudioProductStrategies() {
// verify permissions
+ super.getAudioProductStrategies_enforcePermission();
+
return AudioProductStrategy.getAudioProductStrategies();
}
@@ -1904,6 +1910,8 @@
@NonNull
public List<AudioVolumeGroup> getAudioVolumeGroups() {
// verify permissions
+ super.getAudioVolumeGroups_enforcePermission();
+
return AudioVolumeGroup.getAudioVolumeGroups();
}
@@ -2782,6 +2790,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */
public int removePreferredDevicesForStrategy(int strategy) {
+ super.removePreferredDevicesForStrategy_enforcePermission();
+
final String logString =
String.format("removePreferredDeviceForStrategy strat:%d", strategy);
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2799,6 +2809,8 @@
* @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy)
*/
public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(int strategy) {
+ super.getPreferredDevicesForStrategy_enforcePermission();
+
List<AudioDeviceAttributes> devices = new ArrayList<>();
final long identity = Binder.clearCallingIdentity();
final int status = AudioSystem.getDevicesForRoleAndStrategy(
@@ -2869,6 +2881,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */
public int clearPreferredDevicesForCapturePreset(int capturePreset) {
+ super.clearPreferredDevicesForCapturePreset_enforcePermission();
+
final String logString = String.format(
"removePreferredDeviceForCapturePreset source:%d", capturePreset);
sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2885,6 +2899,8 @@
* @see AudioManager#getPreferredDevicesForCapturePreset(int)
*/
public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) {
+ super.getPreferredDevicesForCapturePreset_enforcePermission();
+
List<AudioDeviceAttributes> devices = new ArrayList<>();
final long identity = Binder.clearCallingIdentity();
final int status = AudioSystem.getDevicesForRoleAndCapturePreset(
@@ -3618,6 +3634,8 @@
/** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
String callingPackage, String attributionTag) {
+ super.setVolumeIndexForAttributes_enforcePermission();
+
Objects.requireNonNull(attr, "attr must not be null");
final int volumeGroup = getVolumeGroupIdForAttributes(attr);
if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
@@ -3661,6 +3679,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/** @see AudioManager#getVolumeIndexForAttributes(attr) */
public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+ super.getVolumeIndexForAttributes_enforcePermission();
+
Objects.requireNonNull(attr, "attr must not be null");
final int volumeGroup = getVolumeGroupIdForAttributes(attr);
if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
@@ -3673,6 +3693,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+ super.getMaxVolumeIndexForAttributes_enforcePermission();
+
Objects.requireNonNull(attr, "attr must not be null");
return AudioSystem.getMaxVolumeIndexForAttributes(attr);
}
@@ -3680,6 +3702,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/** @see AudioManager#getMinVolumeIndexForAttributes(attr) */
public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+ super.getMinVolumeIndexForAttributes_enforcePermission();
+
Objects.requireNonNull(attr, "attr must not be null");
return AudioSystem.getMinVolumeIndexForAttributes(attr);
}
@@ -3786,6 +3810,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND)
/** @see AudioManager#isUltrasoundSupported() */
public boolean isUltrasoundSupported() {
+ super.isUltrasoundSupported_enforcePermission();
+
return AudioSystem.isUltrasoundSupported();
}
@@ -4603,6 +4629,8 @@
/** @see AudioManager#setMasterMute(boolean, int) */
public void setMasterMute(boolean mute, int flags, String callingPackage, int userId,
String attributionTag) {
+ super.setMasterMute_enforcePermission();
+
setMasterMuteInternal(mute, flags, callingPackage,
Binder.getCallingUid(), userId, Binder.getCallingPid(), attributionTag);
}
@@ -4647,6 +4675,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE)
/** Get last audible volume before stream was muted. */
public int getLastAudibleStreamVolume(int streamType) {
+ super.getLastAudibleStreamVolume_enforcePermission();
+
ensureValidStreamType(streamType);
int device = getDeviceForStream(streamType);
return (mStreamStates[streamType].getIndex(device) + 5) / 10;
@@ -5500,6 +5530,8 @@
/** @see AudioManager#isPstnCallAudioInterceptable() */
public boolean isPstnCallAudioInterceptable() {
+ super.isPstnCallAudioInterceptable_enforcePermission();
+
boolean uplinkDeviceFound = false;
boolean downlinkDeviceFound = false;
AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_ALL);
@@ -6891,6 +6923,8 @@
@AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) {
// verify permissions
// verify arguments
+ super.setDeviceVolumeBehavior_enforcePermission();
+
Objects.requireNonNull(device);
AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
@@ -7046,6 +7080,8 @@
*/
public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
@ConnectionState int state, String caller) {
+ super.setWiredDeviceConnectionState_enforcePermission();
+
if (state != CONNECTION_STATE_CONNECTED
&& state != CONNECTION_STATE_DISCONNECTED) {
throw new IllegalArgumentException("Invalid state " + state);
@@ -9178,24 +9214,32 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#isAvailableForDevice(AudioDeviceAttributes) */
public boolean isSpatializerAvailableForDevice(@NonNull AudioDeviceAttributes device) {
+ super.isSpatializerAvailableForDevice_enforcePermission();
+
return mSpatializerHelper.isAvailableForDevice(Objects.requireNonNull(device));
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#hasHeadTracker(AudioDeviceAttributes) */
public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) {
+ super.hasHeadTracker_enforcePermission();
+
return mSpatializerHelper.hasHeadTracker(Objects.requireNonNull(device));
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#setHeadTrackerEnabled(boolean, AudioDeviceAttributes) */
public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) {
+ super.setHeadTrackerEnabled_enforcePermission();
+
mSpatializerHelper.setHeadTrackerEnabled(enabled, Objects.requireNonNull(device));
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#isHeadTrackerEnabled(AudioDeviceAttributes) */
public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) {
+ super.isHeadTrackerEnabled_enforcePermission();
+
return mSpatializerHelper.isHeadTrackerEnabled(Objects.requireNonNull(device));
}
@@ -9207,6 +9251,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#setSpatializerEnabled(boolean) */
public void setSpatializerEnabled(boolean enabled) {
+ super.setSpatializerEnabled_enforcePermission();
+
mSpatializerHelper.setFeatureEnabled(enabled);
}
@@ -9236,6 +9282,8 @@
/** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
public void registerSpatializerHeadTrackingCallback(
@NonNull ISpatializerHeadTrackingModeCallback cb) {
+ super.registerSpatializerHeadTrackingCallback_enforcePermission();
+
Objects.requireNonNull(cb);
mSpatializerHelper.registerHeadTrackingModeCallback(cb);
}
@@ -9244,6 +9292,8 @@
/** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
public void unregisterSpatializerHeadTrackingCallback(
@NonNull ISpatializerHeadTrackingModeCallback cb) {
+ super.unregisterSpatializerHeadTrackingCallback_enforcePermission();
+
Objects.requireNonNull(cb);
mSpatializerHelper.unregisterHeadTrackingModeCallback(cb);
}
@@ -9259,6 +9309,8 @@
/** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */
public void registerHeadToSoundstagePoseCallback(
@NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+ super.registerHeadToSoundstagePoseCallback_enforcePermission();
+
Objects.requireNonNull(cb);
mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb);
}
@@ -9267,6 +9319,8 @@
/** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */
public void unregisterHeadToSoundstagePoseCallback(
@NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+ super.unregisterHeadToSoundstagePoseCallback_enforcePermission();
+
Objects.requireNonNull(cb);
mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb);
}
@@ -9274,12 +9328,16 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#getSpatializerCompatibleAudioDevices() */
public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() {
+ super.getSpatializerCompatibleAudioDevices_enforcePermission();
+
return mSpatializerHelper.getCompatibleAudioDevices();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ super.addSpatializerCompatibleAudioDevice_enforcePermission();
+
Objects.requireNonNull(ada);
mSpatializerHelper.addCompatibleAudioDevice(ada);
}
@@ -9287,6 +9345,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ super.removeSpatializerCompatibleAudioDevice_enforcePermission();
+
Objects.requireNonNull(ada);
mSpatializerHelper.removeCompatibleAudioDevice(ada);
}
@@ -9294,24 +9354,32 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#getSupportedHeadTrackingModes() */
public int[] getSupportedHeadTrackingModes() {
+ super.getSupportedHeadTrackingModes_enforcePermission();
+
return mSpatializerHelper.getSupportedHeadTrackingModes();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#getHeadTrackingMode() */
public int getActualHeadTrackingMode() {
+ super.getActualHeadTrackingMode_enforcePermission();
+
return mSpatializerHelper.getActualHeadTrackingMode();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#getDesiredHeadTrackingMode() */
public int getDesiredHeadTrackingMode() {
+ super.getDesiredHeadTrackingMode_enforcePermission();
+
return mSpatializerHelper.getDesiredHeadTrackingMode();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#setGlobalTransform */
public void setSpatializerGlobalTransform(@NonNull float[] transform) {
+ super.setSpatializerGlobalTransform_enforcePermission();
+
Objects.requireNonNull(transform);
mSpatializerHelper.setGlobalTransform(transform);
}
@@ -9319,12 +9387,16 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#recenterHeadTracker() */
public void recenterHeadTracker() {
+ super.recenterHeadTracker_enforcePermission();
+
mSpatializerHelper.recenterHeadTracker();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#setDesiredHeadTrackingMode */
public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
+ super.setDesiredHeadTrackingMode_enforcePermission();
+
switch(mode) {
case Spatializer.HEAD_TRACKING_MODE_DISABLED:
case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
@@ -9339,6 +9411,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#setEffectParameter */
public void setSpatializerParameter(int key, @NonNull byte[] value) {
+ super.setSpatializerParameter_enforcePermission();
+
Objects.requireNonNull(value);
mSpatializerHelper.setEffectParameter(key, value);
}
@@ -9346,6 +9420,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#getEffectParameter */
public void getSpatializerParameter(int key, @NonNull byte[] value) {
+ super.getSpatializerParameter_enforcePermission();
+
Objects.requireNonNull(value);
mSpatializerHelper.getEffectParameter(key, value);
}
@@ -9353,12 +9429,16 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#getOutput */
public int getSpatializerOutput() {
+ super.getSpatializerOutput_enforcePermission();
+
return mSpatializerHelper.getOutput();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#setOnSpatializerOutputChangedListener */
public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ super.registerSpatializerOutputCallback_enforcePermission();
+
Objects.requireNonNull(cb);
mSpatializerHelper.registerSpatializerOutputCallback(cb);
}
@@ -9366,6 +9446,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
/** @see Spatializer#clearOnSpatializerOutputChangedListener */
public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ super.unregisterSpatializerOutputCallback_enforcePermission();
+
Objects.requireNonNull(cb);
mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
}
@@ -9483,6 +9565,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/** @see AudioManager#getMutingExpectedDevice */
public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+ super.getMutingExpectedDevice_enforcePermission();
+
synchronized (mMuteAwaitConnectionLock) {
return mMutingExpectedDevice;
}
@@ -9524,6 +9608,8 @@
/** @see AudioManager#registerMuteAwaitConnectionCallback */
public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb,
boolean register) {
+ super.registerMuteAwaitConnectionDispatcher_enforcePermission();
+
if (register) {
mMuteAwaitConnectionDispatchers.register(cb);
} else {
@@ -11049,6 +11135,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/** @see AudioPolicy#getFocusStack() */
public List<AudioFocusInfo> getFocusStack() {
+ super.getFocusStack_enforcePermission();
+
return mMediaFocusControl.getFocusStack();
}
@@ -11826,6 +11914,8 @@
// Multi Audio Focus
//======================
public void setMultiAudioFocusEnabled(boolean enabled) {
+ super.setMultiAudioFocusEnabled_enforcePermission();
+
if (mMediaFocusControl != null) {
boolean mafEnabled = mMediaFocusControl.getMultiAudioFocusEnabled();
if (mafEnabled != enabled) {
@@ -11928,6 +12018,8 @@
/** @see AudioManager#addAssistantServicesUids(int []) */
@Override
public void addAssistantServicesUids(int [] assistantUids) {
+ super.addAssistantServicesUids_enforcePermission();
+
Objects.requireNonNull(assistantUids);
synchronized (mSettingsLock) {
@@ -11939,6 +12031,8 @@
/** @see AudioManager#removeAssistantServicesUids(int []) */
@Override
public void removeAssistantServicesUids(int [] assistantUids) {
+ super.removeAssistantServicesUids_enforcePermission();
+
Objects.requireNonNull(assistantUids);
synchronized (mSettingsLock) {
removeAssistantServiceUidsLocked(assistantUids);
@@ -11949,6 +12043,8 @@
/** @see AudioManager#getAssistantServicesUids() */
@Override
public int[] getAssistantServicesUids() {
+ super.getAssistantServicesUids_enforcePermission();
+
int [] assistantUids;
synchronized (mSettingsLock) {
assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray();
@@ -11960,6 +12056,8 @@
/** @see AudioManager#setActiveAssistantServiceUids(int []) */
@Override
public void setActiveAssistantServiceUids(int [] activeAssistantUids) {
+ super.setActiveAssistantServiceUids_enforcePermission();
+
Objects.requireNonNull(activeAssistantUids);
synchronized (mSettingsLock) {
mActiveAssistantServiceUids = activeAssistantUids;
@@ -11971,6 +12069,8 @@
/** @see AudioManager#getActiveAssistantServiceUids() */
@Override
public int[] getActiveAssistantServiceUids() {
+ super.getActiveAssistantServiceUids_enforcePermission();
+
int [] activeAssistantUids;
synchronized (mSettingsLock) {
activeAssistantUids = mActiveAssistantServiceUids.clone();
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 74bfa80..0bc4b20 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,12 +17,12 @@
package com.android.server.audio;
import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_CLIENT_VOLUME;
-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_MUTE_VOLUME_SHAPER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
@@ -1155,22 +1155,22 @@
if (mEventValue <= 0) {
builder.append("none ");
} else {
- if ((mEventValue & PLAYER_MUTE_MASTER) != 0) {
+ if ((mEventValue & MUTED_BY_MASTER) != 0) {
builder.append("masterMute ");
}
- if ((mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+ if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) {
builder.append("streamVolume ");
}
- if ((mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0) {
+ if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) {
builder.append("streamMute ");
}
- if ((mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
- builder.append("playbackRestricted ");
+ if ((mEventValue & MUTED_BY_APP_OPS) != 0) {
+ builder.append("appOps ");
}
- if ((mEventValue & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+ if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
builder.append("clientVolume ");
}
- if ((mEventValue & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+ if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
builder.append("volumeShaper ");
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d2016c47..4358ee2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -178,6 +178,8 @@
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) throws RemoteException {
+ super.createTestSession_enforcePermission();
+
final long identity = Binder.clearCallingIdentity();
try {
return mInjector.getBiometricService()
@@ -192,6 +194,8 @@
public List<SensorPropertiesInternal> getSensorProperties(String opPackageName)
throws RemoteException {
+ super.getSensorProperties_enforcePermission();
+
final long identity = Binder.clearCallingIdentity();
try {
// Get the result from BiometricService, since it is the source of truth for all
@@ -206,6 +210,8 @@
@Override
public String getUiPackage() {
+ super.getUiPackage_enforcePermission();
+
return getContext().getResources()
.getString(R.string.config_biometric_prompt_ui_package);
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index c29755a..cd30d26 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -495,6 +495,8 @@
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) throws RemoteException {
+ super.createTestSession_enforcePermission();
+
for (BiometricSensor sensor : mSensors) {
if (sensor.id == sensorId) {
return sensor.impl.createTestSession(callback, opPackageName);
@@ -510,6 +512,8 @@
public List<SensorPropertiesInternal> getSensorProperties(String opPackageName)
throws RemoteException {
+ super.getSensorProperties_enforcePermission();
+
final List<SensorPropertiesInternal> sensors = new ArrayList<>();
for (BiometricSensor sensor : mSensors) {
// Explicitly re-create as the super class, since AIDL doesn't play nicely with
@@ -526,6 +530,8 @@
@Override // Binder call
public void onReadyForAuthentication(long requestId, int cookie) {
+ super.onReadyForAuthentication_enforcePermission();
+
mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie));
}
@@ -534,6 +540,8 @@
public long authenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
+ super.authenticate_enforcePermission();
+
if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
return -1;
@@ -564,6 +572,8 @@
@Override // Binder call
public void cancelAuthentication(IBinder token, String opPackageName, long requestId) {
+ super.cancelAuthentication_enforcePermission();
+
SomeArgs args = SomeArgs.obtain();
args.arg1 = token;
args.arg2 = opPackageName;
@@ -577,6 +587,8 @@
public int canAuthenticate(String opPackageName, int userId, int callingUserId,
@Authenticators.Types int authenticators) {
+ super.canAuthenticate_enforcePermission();
+
Slog.d(TAG, "canAuthenticate: User=" + userId
+ ", Caller=" + callingUserId
+ ", Authenticators=" + authenticators);
@@ -599,6 +611,8 @@
@Override
public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
+ super.hasEnrolledBiometrics_enforcePermission();
+
try {
for (BiometricSensor sensor : mSensors) {
if (sensor.impl.hasEnrolledTemplates(userId, opPackageName)) {
@@ -618,6 +632,8 @@
@Authenticators.Types int strength,
@NonNull IBiometricAuthenticator authenticator) {
+ super.registerAuthenticator_enforcePermission();
+
Slog.d(TAG, "Registering ID: " + id
+ " Modality: " + modality
+ " Strength: " + strength);
@@ -664,6 +680,8 @@
public void registerEnabledOnKeyguardCallback(
IBiometricEnabledOnKeyguardCallback callback, int callingUserId) {
+ super.registerEnabledOnKeyguardCallback_enforcePermission();
+
mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
try {
callback.onChanged(mSettingObserver.getEnabledOnKeyguard(callingUserId),
@@ -678,6 +696,8 @@
public void invalidateAuthenticatorIds(int userId, int fromSensorId,
IInvalidationCallback callback) {
+ super.invalidateAuthenticatorIds_enforcePermission();
+
InvalidationTracker.start(getContext(), mSensors, userId, fromSensorId, callback);
}
@@ -685,6 +705,8 @@
@Override // Binder call
public long[] getAuthenticatorIds(int callingUserId) {
+ super.getAuthenticatorIds_enforcePermission();
+
final List<Long> authenticatorIds = new ArrayList<>();
for (BiometricSensor sensor : mSensors) {
try {
@@ -717,6 +739,8 @@
int userId, byte[] hardwareAuthToken) {
// Check originating strength
+ super.resetLockoutTimeBound_enforcePermission();
+
if (!Utils.isAtLeastStrength(getSensorForId(fromSensorId).getCurrentStrength(),
Authenticators.BIOMETRIC_STRONG)) {
Slog.w(TAG, "Sensor: " + fromSensorId + " is does not meet the required strength to"
@@ -754,6 +778,8 @@
@Override // Binder call
public int getCurrentStrength(int sensorId) {
+ super.getCurrentStrength_enforcePermission();
+
for (BiometricSensor sensor : mSensors) {
if (sensor.id == sensorId) {
return sensor.getCurrentStrength();
@@ -772,6 +798,8 @@
@Authenticators.Types int authenticators) {
+ super.getCurrentModality_enforcePermission();
+
Slog.d(TAG, "getCurrentModality: User=" + userId
+ ", Caller=" + callingUserId
+ ", Authenticators=" + authenticators);
@@ -794,6 +822,8 @@
@Override // Binder call
public int getSupportedModalities(@Authenticators.Types int authenticators) {
+ super.getSupportedModalities_enforcePermission();
+
Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators);
if (!Utils.isValidAuthenticatorConfig(authenticators)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7a5b584..33d3b64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -99,6 +99,8 @@
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
+ super.createTestSession_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
@@ -112,6 +114,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+ super.dumpSensorServiceStateProto_enforcePermission();
+
final ProtoOutputStream proto = new ProtoOutputStream();
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider != null) {
@@ -125,6 +129,8 @@
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
+ super.getSensorPropertiesInternal_enforcePermission();
+
return mRegistry.getAllProperties();
}
@@ -132,6 +138,8 @@
@Override // Binder call
public FaceSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
+ super.getSensorProperties_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
@@ -146,6 +154,8 @@
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFaceServiceReceiver receiver, String opPackageName) {
+ super.generateChallenge_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
@@ -159,6 +169,8 @@
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
+ super.revokeChallenge_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
@@ -173,6 +185,8 @@
public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
+ super.enroll_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
@@ -201,12 +215,16 @@
final IFaceServiceReceiver receiver, final String opPackageName,
final int[] disabledFeatures) {
// TODO(b/145027036): Implement this.
+ super.enrollRemotely_enforcePermission();
+
return -1;
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
+ super.cancelEnrollment_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
@@ -224,6 +242,8 @@
// TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
// lockdown, something wrong happened. See similar path in FingerprintService.
+ super.authenticate_enforcePermission();
+
final boolean restricted = false; // Face APIs are private
final int statsClient = Utils.isKeyguard(getContext(), opPackageName)
? BiometricsProtoEnums.CLIENT_KEYGUARD
@@ -249,6 +269,8 @@
@Override // Binder call
public long detectFace(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
+ super.detectFace_enforcePermission();
+
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName);
return -1;
@@ -278,6 +300,8 @@
IBinder token, long operationId, int userId,
IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
int cookie, boolean allowBackgroundAuthentication) {
+ super.prepareForAuthentication_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
@@ -295,6 +319,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
+ super.startPreparedClient_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
@@ -308,6 +334,8 @@
@Override // Binder call
public void cancelAuthentication(final IBinder token, final String opPackageName,
final long requestId) {
+ super.cancelAuthentication_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthentication");
@@ -321,6 +349,8 @@
@Override // Binder call
public void cancelFaceDetect(final IBinder token, final String opPackageName,
final long requestId) {
+ super.cancelFaceDetect_enforcePermission();
+
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFaceDetect called from non-sysui package: "
+ opPackageName);
@@ -340,6 +370,8 @@
@Override // Binder call
public void cancelAuthenticationFromService(int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
+ super.cancelAuthenticationFromService_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
@@ -353,6 +385,8 @@
@Override // Binder call
public void remove(final IBinder token, final int faceId, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
+ super.remove_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
@@ -367,6 +401,8 @@
@Override // Binder call
public void removeAll(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
+ super.removeAll_enforcePermission();
+
final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() {
int sensorsFinishedRemoving = 0;
final int numSensors = getSensorPropertiesInternal(
@@ -399,6 +435,8 @@
@Override // Binder call
public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
final String opPackageName) {
+ super.addLockoutResetCallback_enforcePermission();
+
mLockoutResetDispatcher.addCallback(callback, opPackageName);
}
@@ -458,6 +496,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
+ super.isHardwareDetected_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
@@ -474,6 +514,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) {
+ super.getEnrolledFaces_enforcePermission();
+
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
@@ -490,6 +532,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
+ super.hasEnrolledFaces_enforcePermission();
+
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
@@ -506,6 +550,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
+ super.getLockoutModeForUser_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
@@ -519,6 +565,8 @@
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
+ super.invalidateAuthenticatorId_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
@@ -531,6 +579,8 @@
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
+ super.getAuthenticatorId_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
@@ -544,6 +594,8 @@
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken,
String opPackageName) {
+ super.resetLockout_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
@@ -558,6 +610,8 @@
public void setFeature(final IBinder token, int userId, int feature, boolean enabled,
final byte[] hardwareAuthToken, IFaceServiceReceiver receiver,
final String opPackageName) {
+ super.setFeature_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for setFeature");
@@ -572,6 +626,8 @@
@Override
public void getFeature(final IBinder token, int userId, int feature,
IFaceServiceReceiver receiver, final String opPackageName) {
+ super.getFeature_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for getFeature");
@@ -615,6 +671,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public void registerAuthenticators(
@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
+ super.registerAuthenticators_enforcePermission();
+
mRegistry.registerAll(() -> {
final List<ServiceProvider> providers = new ArrayList<>();
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index cfbb5dc..7a13c91 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -139,6 +139,8 @@
@Override
public void setTestHalEnabled(boolean enabled) {
+ super.setTestHalEnabled_enforcePermission();
+
mProvider.setTestHalEnabled(enabled);
mSensor.setTestHalEnabled(enabled);
}
@@ -147,6 +149,8 @@
@Override
public void startEnroll(int userId) {
+ super.startEnroll_enforcePermission();
+
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
null /* previewSurface */, false /* debugConsent */);
@@ -156,6 +160,8 @@
@Override
public void finishEnroll(int userId) {
+ super.finishEnroll_enforcePermission();
+
int nextRandomId = mRandom.nextInt();
while (mEnrollmentIds.contains(nextRandomId)) {
nextRandomId = mRandom.nextInt();
@@ -171,6 +177,8 @@
public void acceptAuthentication(int userId) {
// Fake authentication with any of the existing faces
+ super.acceptAuthentication_enforcePermission();
+
List<Face> faces = FaceUtils.getInstance(mSensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -186,6 +194,8 @@
@Override
public void rejectAuthentication(int userId) {
+ super.rejectAuthentication_enforcePermission();
+
mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
}
@@ -194,6 +204,8 @@
@Override
public void notifyAcquired(int userId, int acquireInfo) {
+ super.notifyAcquired_enforcePermission();
+
BaseFrame data = new BaseFrame();
data.acquiredInfo = (byte) acquireInfo;
@@ -210,6 +222,8 @@
@Override
public void notifyError(int userId, int errorCode) {
+ super.notifyError_enforcePermission();
+
mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
0 /* vendorCode */);
}
@@ -218,6 +232,8 @@
@Override
public void cleanupInternalState(int userId) {
+ super.cleanupInternalState_enforcePermission();
+
Slog.d(TAG, "cleanupInternalState: " + userId);
mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 7a6a274f..151ffaa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -130,6 +130,8 @@
@Override
public void setTestHalEnabled(boolean enabled) {
+ super.setTestHalEnabled_enforcePermission();
+
mFace10.setTestHalEnabled(enabled);
}
@@ -137,6 +139,8 @@
@Override
public void startEnroll(int userId) {
+ super.startEnroll_enforcePermission();
+
mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
null /* previewSurface */, false /* debugConsent */);
@@ -146,6 +150,8 @@
@Override
public void finishEnroll(int userId) {
+ super.finishEnroll_enforcePermission();
+
int nextRandomId = mRandom.nextInt();
while (mEnrollmentIds.contains(nextRandomId)) {
nextRandomId = mRandom.nextInt();
@@ -161,6 +167,8 @@
public void acceptAuthentication(int userId) {
// Fake authentication with any of the existing fingers
+ super.acceptAuthentication_enforcePermission();
+
List<Face> faces = FaceUtils.getLegacyInstance(mSensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -176,6 +184,8 @@
@Override
public void rejectAuthentication(int userId) {
+ super.rejectAuthentication_enforcePermission();
+
mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* faceId */, userId, null);
}
@@ -183,6 +193,8 @@
@Override
public void notifyAcquired(int userId, int acquireInfo) {
+ super.notifyAcquired_enforcePermission();
+
mHalResultController.onAcquired(0 /* deviceId */, userId, acquireInfo, 0 /* vendorCode */);
}
@@ -190,6 +202,8 @@
@Override
public void notifyError(int userId, int errorCode) {
+ super.notifyError_enforcePermission();
+
mHalResultController.onError(0 /* deviceId */, userId, errorCode, 0 /* vendorCode */);
}
@@ -197,6 +211,8 @@
@Override
public void cleanupInternalState(int userId) {
+ super.cleanupInternalState_enforcePermission();
+
mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 156e6bb..70470e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -133,6 +133,8 @@
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
+ super.createTestSession_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
@@ -146,6 +148,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) {
+ super.dumpSensorServiceStateProto_enforcePermission();
+
final ProtoOutputStream proto = new ProtoOutputStream();
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider != null) {
@@ -169,6 +173,8 @@
@Override
public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId,
@NonNull String opPackageName) {
+ super.getSensorProperties_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId
@@ -182,6 +188,8 @@
@Override // Binder call
public void generateChallenge(IBinder token, int sensorId, int userId,
IFingerprintServiceReceiver receiver, String opPackageName) {
+ super.generateChallenge_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
@@ -195,6 +203,8 @@
@Override // Binder call
public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName,
long challenge) {
+ super.revokeChallenge_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId);
@@ -210,6 +220,8 @@
public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
+ super.enroll_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for enroll");
@@ -223,6 +235,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override // Binder call
public void cancelEnrollment(final IBinder token, long requestId) {
+ super.cancelEnrollment_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for cancelEnrollment");
@@ -399,6 +413,8 @@
@Override
public long detectFingerprint(final IBinder token, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
+ super.detectFingerprint_enforcePermission();
+
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
return -1;
@@ -427,6 +443,8 @@
public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
long requestId, int cookie, boolean allowBackgroundAuthentication) {
+ super.prepareForAuthentication_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for prepareForAuthentication");
@@ -443,6 +461,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC)
@Override // Binder call
public void startPreparedClient(int sensorId, int cookie) {
+ super.startPreparedClient_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for startPreparedClient");
@@ -486,6 +506,8 @@
@Override // Binder call
public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
final long requestId) {
+ super.cancelFingerprintDetect_enforcePermission();
+
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
+ opPackageName);
@@ -507,6 +529,8 @@
@Override // Binder call
public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
final String opPackageName, final long requestId) {
+ super.cancelAuthenticationFromService_enforcePermission();
+
Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId);
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
@@ -522,6 +546,8 @@
@Override // Binder call
public void remove(final IBinder token, final int fingerId, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
+ super.remove_enforcePermission();
+
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for remove");
@@ -536,6 +562,8 @@
public void removeAll(final IBinder token, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
+ super.removeAll_enforcePermission();
+
final FingerprintServiceReceiver internalReceiver = new FingerprintServiceReceiver() {
int sensorsFinishedRemoving = 0;
final int numSensors = getSensorPropertiesInternal(
@@ -568,6 +596,8 @@
@Override // Binder call
public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback,
final String opPackageName) {
+ super.addLockoutResetCallback_enforcePermission();
+
mLockoutResetDispatcher.addCallback(callback, opPackageName);
}
@@ -651,6 +681,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean isHardwareDetected(int sensorId, String opPackageName) {
+ super.isHardwareDetected_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
@@ -663,6 +695,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override // Binder call
public void rename(final int fingerId, final int userId, final String name) {
+ super.rename_enforcePermission();
+
if (!Utils.isCurrentUserOrProfile(getContext(), userId)) {
return;
}
@@ -718,6 +752,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
+ super.hasEnrolledFingerprints_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName);
@@ -730,6 +766,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) {
+ super.getLockoutModeForUser_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getLockoutModeForUser");
@@ -742,6 +780,8 @@
@Override
public void invalidateAuthenticatorId(int sensorId, int userId,
IInvalidationCallback callback) {
+ super.invalidateAuthenticatorId_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for invalidateAuthenticatorId");
@@ -753,6 +793,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public long getAuthenticatorId(int sensorId, int userId) {
+ super.getAuthenticatorId_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for getAuthenticatorId");
@@ -765,6 +807,8 @@
@Override // Binder call
public void resetLockout(IBinder token, int sensorId, int userId,
@Nullable byte[] hardwareAuthToken, String opPackageName) {
+ super.resetLockout_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
@@ -777,18 +821,24 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override
public boolean isClientActive() {
+ super.isClientActive_enforcePermission();
+
return mGestureAvailabilityDispatcher.isAnySensorActive();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override
public void addClientActiveCallback(IFingerprintClientActiveCallback callback) {
+ super.addClientActiveCallback_enforcePermission();
+
mGestureAvailabilityDispatcher.registerCallback(callback);
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@Override
public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) {
+ super.removeClientActiveCallback_enforcePermission();
+
mGestureAvailabilityDispatcher.removeCallback(callback);
}
@@ -796,6 +846,8 @@
@Override // Binder call
public void registerAuthenticators(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
+ super.registerAuthenticators_enforcePermission();
+
mRegistry.registerAll(() -> {
final List<ServiceProvider> providers = new ArrayList<>();
providers.addAll(getHidlProviders(hidlSensors));
@@ -814,12 +866,16 @@
@Override
public void addAuthenticatorsRegisteredCallback(
IFingerprintAuthenticatorsRegisteredCallback callback) {
+ super.addAuthenticatorsRegisteredCallback_enforcePermission();
+
mRegistry.addAllRegisteredCallback(callback);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ super.registerBiometricStateListener_enforcePermission();
+
mBiometricStateCallback.registerBiometricStateListener(listener);
}
@@ -827,6 +883,8 @@
@Override
public void onPointerDown(long requestId, int sensorId, int x, int y,
float minor, float major) {
+ super.onPointerDown_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
@@ -838,6 +896,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPointerUp(long requestId, int sensorId) {
+ super.onPointerUp_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
@@ -849,6 +909,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onUiReady(long requestId, int sensorId) {
+ super.onUiReady_enforcePermission();
+
final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId);
if (provider == null) {
Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId);
@@ -860,6 +922,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
+ super.setUdfpsOverlayController_enforcePermission();
+
for (ServiceProvider provider : mRegistry.getProviders()) {
provider.setUdfpsOverlayController(controller);
}
@@ -868,6 +932,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void setSidefpsController(@NonNull ISidefpsController controller) {
+ super.setSidefpsController_enforcePermission();
+
for (ServiceProvider provider : mRegistry.getProviders()) {
provider.setSidefpsController(controller);
}
@@ -884,6 +950,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void onPowerPressed() {
+ super.onPowerPressed_enforcePermission();
+
for (ServiceProvider provider : mRegistry.getProviders()) {
provider.onPowerPressed();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 4181b99..135eccf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -135,6 +135,8 @@
@Override
public void setTestHalEnabled(boolean enabled) {
+ super.setTestHalEnabled_enforcePermission();
+
mProvider.setTestHalEnabled(enabled);
mSensor.setTestHalEnabled(enabled);
}
@@ -143,6 +145,8 @@
@Override
public void startEnroll(int userId) {
+ super.startEnroll_enforcePermission();
+
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@@ -151,6 +155,8 @@
@Override
public void finishEnroll(int userId) {
+ super.finishEnroll_enforcePermission();
+
int nextRandomId = mRandom.nextInt();
while (mEnrollmentIds.contains(nextRandomId)) {
nextRandomId = mRandom.nextInt();
@@ -166,6 +172,8 @@
public void acceptAuthentication(int userId) {
// Fake authentication with any of the existing fingers
+ super.acceptAuthentication_enforcePermission();
+
List<Fingerprint> fingerprints = FingerprintUtils.getInstance(mSensorId)
.getBiometricsForUser(mContext, userId);
if (fingerprints.isEmpty()) {
@@ -181,6 +189,8 @@
@Override
public void rejectAuthentication(int userId) {
+ super.rejectAuthentication_enforcePermission();
+
mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
}
@@ -188,6 +198,8 @@
@Override
public void notifyAcquired(int userId, int acquireInfo) {
+ super.notifyAcquired_enforcePermission();
+
mSensor.getSessionForUser(userId).getHalSessionCallback()
.onAcquired((byte) acquireInfo, 0 /* vendorCode */);
}
@@ -196,6 +208,8 @@
@Override
public void notifyError(int userId, int errorCode) {
+ super.notifyError_enforcePermission();
+
mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode,
0 /* vendorCode */);
}
@@ -204,6 +218,8 @@
@Override
public void cleanupInternalState(int userId) {
+ super.cleanupInternalState_enforcePermission();
+
Slog.d(TAG, "cleanupInternalState: " + userId);
mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 7f1fb1c..49bd44b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -72,7 +72,6 @@
class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
- private static final int MESSAGE_IGNORE_AUTH = 1;
private static final int MESSAGE_AUTH_SUCCESS = 2;
private static final int MESSAGE_FINGER_UP = 3;
@NonNull
@@ -249,12 +248,6 @@
() -> {
long delay = 0;
if (authenticated && mSensorProps.isAnySidefpsType()) {
- if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
- Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
- 0, true);
- return;
- }
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
if (mSideFpsLastAcquireStartTime != -1) {
@@ -515,18 +508,15 @@
if (mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): onPowerPressed");
mHandler.post(() -> {
- if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
- Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
- mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
- // Do not call onError() as that will send an additional callback to coex.
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
- mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
- mBiometricStrength, getSensorId(), getRequestId());
- }
- mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
- mHandler.postDelayed(() -> {
- }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+ Slog.i(TAG, "(sideFPS): finishing auth");
+ // Ignore auths after a power has been detected
+ mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+ // Do not call onError() as that will send an additional callback to coex.
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+ 0, true);
+ mSensorOverlays.hide(getSensorId());
+ mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
+ mBiometricStrength, getSensorId(), getRequestId());
});
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 682c005..86a9f79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -136,6 +136,8 @@
@Override
public void setTestHalEnabled(boolean enabled) {
+ super.setTestHalEnabled_enforcePermission();
+
mFingerprint21.setTestHalEnabled(enabled);
}
@@ -143,6 +145,8 @@
@Override
public void startEnroll(int userId) {
+ super.startEnroll_enforcePermission();
+
mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@@ -151,6 +155,8 @@
@Override
public void finishEnroll(int userId) {
+ super.finishEnroll_enforcePermission();
+
int nextRandomId = mRandom.nextInt();
while (mEnrollmentIds.contains(nextRandomId)) {
nextRandomId = mRandom.nextInt();
@@ -166,6 +172,8 @@
public void acceptAuthentication(int userId) {
// Fake authentication with any of the existing fingers
+ super.acceptAuthentication_enforcePermission();
+
List<Fingerprint> fingerprints = FingerprintUtils.getLegacyInstance(mSensorId)
.getBiometricsForUser(mContext, userId);
if (fingerprints.isEmpty()) {
@@ -181,6 +189,8 @@
@Override
public void rejectAuthentication(int userId) {
+ super.rejectAuthentication_enforcePermission();
+
mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
}
@@ -188,6 +198,8 @@
@Override
public void notifyAcquired(int userId, int acquireInfo) {
+ super.notifyAcquired_enforcePermission();
+
mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
}
@@ -195,6 +207,8 @@
@Override
public void notifyError(int userId, int errorCode) {
+ super.notifyError_enforcePermission();
+
mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
}
@@ -202,6 +216,8 @@
@Override
public void cleanupInternalState(int userId) {
+ super.cleanupInternalState_enforcePermission();
+
mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index ff1e762..35ea36c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -63,6 +63,8 @@
// to wait, and some of the operations below might take a significant amount of time to
// complete (calls to the HALs). To avoid blocking the rest of system server we put
// this on a background thread.
+ super.registerAuthenticators_enforcePermission();
+
final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
true /* allowIo */);
thread.start();
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 11eb782..b882c47 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -841,6 +841,7 @@
streamProtos[i].histogramCounts = streamStats.getHistogramCounts();
streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile();
streamProtos[i].streamUseCase = streamStats.getStreamUseCase();
+ streamProtos[i].colorSpace = streamStats.getColorSpace();
if (CameraServiceProxy.DEBUG) {
String histogramTypeName =
@@ -863,7 +864,8 @@
+ ", histogramCounts "
+ Arrays.toString(streamProtos[i].histogramCounts)
+ ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile
- + ", streamUseCase " + streamProtos[i].streamUseCase);
+ + ", streamUseCase " + streamProtos[i].streamUseCase
+ + ", colorSpace " + streamProtos[i].colorSpace);
}
}
}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 387e00f..2c83c6f 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -95,6 +95,8 @@
@Override
@EnforcePermission(LOG_COMPAT_CHANGE)
public void reportChange(long changeId, ApplicationInfo appInfo) {
+ super.reportChange_enforcePermission();
+
reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
}
@@ -102,6 +104,8 @@
@EnforcePermission(LOG_COMPAT_CHANGE)
public void reportChangeByPackageName(long changeId, String packageName,
@UserIdInt int userId) {
+ super.reportChangeByPackageName_enforcePermission();
+
ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
if (appInfo != null) {
reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED);
@@ -111,6 +115,8 @@
@Override
@EnforcePermission(LOG_COMPAT_CHANGE)
public void reportChangeByUid(long changeId, int uid) {
+ super.reportChangeByUid_enforcePermission();
+
reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
}
@@ -121,6 +127,8 @@
@Override
@EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
+ super.isChangeEnabled_enforcePermission();
+
return isChangeEnabledInternal(changeId, appInfo);
}
@@ -128,6 +136,8 @@
@EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
public boolean isChangeEnabledByPackageName(long changeId, String packageName,
@UserIdInt int userId) {
+ super.isChangeEnabledByPackageName_enforcePermission();
+
ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
if (appInfo == null) {
return mCompatConfig.willChangeBeEnabled(changeId, packageName);
@@ -138,6 +148,8 @@
@Override
@EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
public boolean isChangeEnabledByUid(long changeId, int uid) {
+ super.isChangeEnabledByUid_enforcePermission();
+
String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
if (packages == null || packages.length == 0) {
return mCompatConfig.defaultChangeIdValue(changeId);
@@ -199,6 +211,8 @@
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
+ super.setOverrides_enforcePermission();
+
Map<Long, PackageOverride> overridesMap = new HashMap<>();
for (long change : overrides.enabledChanges()) {
overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
@@ -215,6 +229,8 @@
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
+ super.setOverridesForTest_enforcePermission();
+
Map<Long, PackageOverride> overridesMap = new HashMap<>();
for (long change : overrides.enabledChanges()) {
overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build());
@@ -231,6 +247,8 @@
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
public void putAllOverridesOnReleaseBuilds(
CompatibilityOverridesByPackageConfig overridesByPackage) {
+ super.putAllOverridesOnReleaseBuilds_enforcePermission();
+
for (CompatibilityOverrideConfig overrides :
overridesByPackage.packageNameToOverrides.values()) {
checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
@@ -242,6 +260,8 @@
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides,
String packageName) {
+ super.putOverridesOnReleaseBuilds_enforcePermission();
+
checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true);
}
@@ -249,6 +269,8 @@
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public int enableTargetSdkChanges(String packageName, int targetSdkVersion) {
+ super.enableTargetSdkChanges_enforcePermission();
+
int numChanges =
mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion);
killPackage(packageName);
@@ -258,6 +280,8 @@
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public int disableTargetSdkChanges(String packageName, int targetSdkVersion) {
+ super.disableTargetSdkChanges_enforcePermission();
+
int numChanges =
mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion);
killPackage(packageName);
@@ -267,6 +291,8 @@
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public void clearOverrides(String packageName) {
+ super.clearOverrides_enforcePermission();
+
mCompatConfig.removePackageOverrides(packageName);
killPackage(packageName);
}
@@ -274,12 +300,16 @@
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public void clearOverridesForTest(String packageName) {
+ super.clearOverridesForTest_enforcePermission();
+
mCompatConfig.removePackageOverrides(packageName);
}
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public boolean clearOverride(long changeId, String packageName) {
+ super.clearOverride_enforcePermission();
+
boolean existed = mCompatConfig.removeOverride(changeId, packageName);
killPackage(packageName);
return existed;
@@ -288,6 +318,8 @@
@Override
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG)
public boolean clearOverrideForTest(long changeId, String packageName) {
+ super.clearOverrideForTest_enforcePermission();
+
return mCompatConfig.removeOverride(changeId, packageName);
}
@@ -295,6 +327,8 @@
@EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD)
public void removeAllOverridesOnReleaseBuilds(
CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) {
+ super.removeAllOverridesOnReleaseBuilds_enforcePermission();
+
for (CompatibilityOverridesToRemoveConfig overridesToRemove :
overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) {
checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
@@ -307,6 +341,8 @@
public void removeOverridesOnReleaseBuilds(
CompatibilityOverridesToRemoveConfig overridesToRemove,
String packageName) {
+ super.removeOverridesOnReleaseBuilds_enforcePermission();
+
checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds);
mCompatConfig.removePackageOverrides(overridesToRemove, packageName);
}
@@ -314,12 +350,16 @@
@Override
@EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG})
public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
+ super.getAppConfig_enforcePermission();
+
return mCompatConfig.getAppConfig(appInfo);
}
@Override
@EnforcePermission(READ_COMPAT_CHANGE_CONFIG)
public CompatibilityChangeInfo[] listAllChanges() {
+ super.listAllChanges_enforcePermission();
+
return mCompatConfig.dumpChanges();
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index aa9f2dc..726326f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -33,7 +33,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.net.Uri;
import android.os.Handler;
import android.os.IThermalEventListener;
@@ -2359,39 +2359,39 @@
}
}
- private class UdfpsObserver extends IUdfpsHbmListener.Stub {
- private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
+ private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub {
+ private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray();
public void observe() {
StatusBarManagerInternal statusBar =
LocalServices.getService(StatusBarManagerInternal.class);
if (statusBar != null) {
- statusBar.setUdfpsHbmListener(this);
+ statusBar.setUdfpsRefreshRateCallback(this);
}
}
@Override
- public void onHbmEnabled(int displayId) {
+ public void onRequestEnabled(int displayId) {
synchronized (mLock) {
- updateHbmStateLocked(displayId, true /*enabled*/);
+ updateRefreshRateStateLocked(displayId, true /*enabled*/);
}
}
@Override
- public void onHbmDisabled(int displayId) {
+ public void onRequestDisabled(int displayId) {
synchronized (mLock) {
- updateHbmStateLocked(displayId, false /*enabled*/);
+ updateRefreshRateStateLocked(displayId, false /*enabled*/);
}
}
- private void updateHbmStateLocked(int displayId, boolean enabled) {
- mLocalHbmEnabled.put(displayId, enabled);
+ private void updateRefreshRateStateLocked(int displayId, boolean enabled) {
+ mUdfpsRefreshRateEnabled.put(displayId, enabled);
updateVoteLocked(displayId);
}
private void updateVoteLocked(int displayId) {
final Vote vote;
- if (mLocalHbmEnabled.get(displayId)) {
+ if (mUdfpsRefreshRateEnabled.get(displayId)) {
Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
float maxRefreshRate = 0f;
for (Display.Mode mode : modes) {
@@ -2409,10 +2409,10 @@
void dumpLocked(PrintWriter pw) {
pw.println(" UdfpsObserver");
- pw.println(" mLocalHbmEnabled: ");
- for (int i = 0; i < mLocalHbmEnabled.size(); i++) {
- final int displayId = mLocalHbmEnabled.keyAt(i);
- final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
+ pw.println(" mUdfpsRefreshRateEnabled: ");
+ for (int i = 0; i < mUdfpsRefreshRateEnabled.size(); i++) {
+ final int displayId = mUdfpsRefreshRateEnabled.keyAt(i);
+ final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled";
pw.println(" Display " + displayId + ": " + enabled);
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 21a8518..5824887 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1682,6 +1682,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
@Override
public boolean isSaturationActivated() {
+ super.isSaturationActivated_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
return !mGlobalSaturationTintController.isActivatedStateNotSet()
@@ -1694,6 +1696,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
@Override
public boolean setAppSaturationLevel(String packageName, int level) {
+ super.setAppSaturationLevel_enforcePermission();
+
final String callingPackageName = LocalServices.getService(PackageManagerInternal.class)
.getNameForUid(Binder.getCallingUid());
final long token = Binder.clearCallingIdentity();
@@ -1706,6 +1710,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
public int getTransformCapabilities() {
+ super.getTransformCapabilities_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
return getTransformCapabilitiesInternal();
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 4ca4817..87327cb 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -211,7 +211,7 @@
mDozeConfig = new AmbientDisplayConfiguration(mContext);
mUiEventLogger = new UiEventLoggerImpl();
mDreamUiEventLogger = new DreamUiEventLoggerImpl(
- mContext.getResources().getString(R.string.config_loggable_dream_prefix));
+ mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
mDreamsOnlyEnabledForSystemUser =
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
index 26ca74a..96ebcbb 100644
--- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -26,10 +26,10 @@
* @hide
*/
public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
- final String mLoggableDreamPrefix;
+ private final String[] mLoggableDreamPrefixes;
- DreamUiEventLoggerImpl(String loggableDreamPrefix) {
- mLoggableDreamPrefix = loggableDreamPrefix;
+ DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) {
+ mLoggableDreamPrefixes = loggableDreamPrefixes;
}
@Override
@@ -38,13 +38,20 @@
if (eventID <= 0) {
return;
}
- final boolean isFirstPartyDream =
- mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
- mLoggableDreamPrefix);
FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
/* uid = 1 */ 0,
/* event_id = 2 */ eventID,
/* instance_id = 3 */ 0,
- /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+ /* dream_component_name = 4 */
+ isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other");
+ }
+
+ private boolean isFirstPartyDream(String dreamComponentName) {
+ for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) {
+ if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 326d720..2817d1b 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -68,6 +68,8 @@
@RequiresPermission(Manifest.permission.UPDATE_FONTS)
@Override
public FontConfig getFontConfig() {
+ super.getFontConfig_enforcePermission();
+
return getSystemFontConfig();
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1225d99..fcdb1f5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3925,10 +3925,11 @@
@EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
- public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode,
- int displayId) {
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
+ super.showInputMethodPickerFromSystem_enforcePermission();
+
mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
.sendToTarget();
}
@@ -3938,6 +3939,8 @@
*/
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
public boolean isInputMethodPickerShownForTest() {
+ super.isInputMethodPickerShownForTest_enforcePermission();
+
synchronized (ImfLock.class) {
return mMenuController.isisInputMethodPickerShownForTestLocked();
}
@@ -4217,6 +4220,8 @@
@EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
public void removeImeSurface() {
+ super.removeImeSurface_enforcePermission();
+
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
@@ -4416,6 +4421,8 @@
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@Override
public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
+ super.addVirtualStylusIdForTestSession_enforcePermission();
+
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) {
@@ -4441,6 +4448,8 @@
@Override
public void setStylusWindowIdleTimeoutForTest(
IInputMethodClient client, @DurationMillisLong long timeout) {
+ super.setStylusWindowIdleTimeoutForTest_enforcePermission();
+
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) {
@@ -4538,6 +4547,8 @@
@EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
@Override
public void startImeTrace() {
+ super.startImeTrace_enforcePermission();
+
ImeTracing.getInstance().startTrace(null /* printwriter */);
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
@@ -4554,6 +4565,8 @@
@EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
@Override
public void stopImeTrace() {
+ super.stopImeTrace_enforcePermission();
+
ImeTracing.getInstance().stopTrace(null /* printwriter */);
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 9bd48f2..dcec0aa 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -950,6 +950,8 @@
@Override
public void injectLocation(Location location) {
+ super.injectLocation_enforcePermission();
+
Preconditions.checkArgument(location.isComplete());
int userId = UserHandle.getCallingUserId();
@@ -1160,6 +1162,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
@Override
public void setExtraLocationControllerPackage(String packageName) {
+ super.setExtraLocationControllerPackage_enforcePermission();
+
synchronized (mLock) {
mExtraLocationControllerPackage = packageName;
}
@@ -1175,6 +1179,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE)
@Override
public void setExtraLocationControllerPackageEnabled(boolean enabled) {
+ super.setExtraLocationControllerPackageEnabled_enforcePermission();
+
synchronized (mLock) {
mExtraLocationControllerPackageEnabled = enabled;
}
@@ -1234,6 +1240,8 @@
@RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
public void setAutomotiveGnssSuspended(boolean suspended) {
+ super.setAutomotiveGnssSuspended_enforcePermission();
+
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException(
"setAutomotiveGnssSuspended only allowed on automotive devices");
@@ -1247,6 +1255,8 @@
@RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
public boolean isAutomotiveGnssSuspended() {
+ super.isAutomotiveGnssSuspended_enforcePermission();
+
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException(
"isAutomotiveGnssSuspended only allowed on automotive devices");
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 6232028..90245b5e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -207,6 +207,14 @@
handleClientMessageCallback(mContextHubId, hostEndpointId, message, nanoappPermissions,
messagePermissions);
}
+
+ @Override
+ public void handleServiceRestart() {
+ Log.i(TAG, "Starting Context Hub Service restart");
+ initExistingCallbacks();
+ resetSettings();
+ Log.i(TAG, "Finished Context Hub Service restart");
+ }
}
public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) {
@@ -381,6 +389,20 @@
}
/**
+ * Initializes existing callbacks with the mContextHubWrapper for every context hub
+ */
+ private void initExistingCallbacks() {
+ for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
+ try {
+ mContextHubWrapper.registerExistingCallback(contextHubId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while registering existing service callback for hub "
+ + "(ID = " + contextHubId + ")", e);
+ }
+ }
+ }
+
+ /**
* Handles the initialization of location settings notifications
*/
private void initLocationSettingNotifications() {
@@ -508,6 +530,17 @@
mContext.registerReceiver(btReceiver, filter);
}
+ /**
+ * Resets the settings. Called when a context hub restarts or the AIDL HAL dies
+ */
+ private void resetSettings() {
+ sendLocationSettingUpdate();
+ sendWifiSettingUpdate(/* forceUpdate= */ true);
+ sendAirplaneModeSettingUpdate();
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
+ sendBtSettingUpdate(/* forceUpdate= */ true);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
@@ -517,6 +550,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@Override
public int registerCallback(IContextHubCallback callback) throws RemoteException {
+ super.registerCallback_enforcePermission();
+
mCallbacksList.register(callback);
Log.d(TAG, "Added callback, total callbacks " +
@@ -527,12 +562,16 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@Override
public int[] getContextHubHandles() throws RemoteException {
+ super.getContextHubHandles_enforcePermission();
+
return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet());
}
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@Override
public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+ super.getContextHubInfo_enforcePermission();
+
if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) {
Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo");
return null;
@@ -549,6 +588,8 @@
*/
@Override
public List<ContextHubInfo> getContextHubs() throws RemoteException {
+ super.getContextHubs_enforcePermission();
+
return mContextHubInfoList;
}
@@ -615,6 +656,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@Override
public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException {
+ super.loadNanoApp_enforcePermission();
+
if (mContextHubWrapper == null) {
return -1;
}
@@ -642,6 +685,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@Override
public int unloadNanoApp(int nanoAppHandle) throws RemoteException {
+ super.unloadNanoApp_enforcePermission();
+
if (mContextHubWrapper == null) {
return -1;
}
@@ -668,6 +713,8 @@
@Override
public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) throws RemoteException {
+ super.getNanoAppInstanceInfo_enforcePermission();
+
return mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle);
}
@@ -676,6 +723,8 @@
public int[] findNanoAppOnHub(
int contextHubHandle, NanoAppFilter filter) throws RemoteException {
+ super.findNanoAppOnHub_enforcePermission();
+
ArrayList<Integer> foundInstances = new ArrayList<>();
if (filter != null) {
mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> {
@@ -720,6 +769,8 @@
@Override
public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg)
throws RemoteException {
+ super.sendMessage_enforcePermission();
+
if (mContextHubWrapper == null) {
return -1;
}
@@ -841,11 +892,7 @@
ContextHubEventLogger.getInstance().logContextHubRestart(contextHubId);
- sendLocationSettingUpdate();
- sendWifiSettingUpdate(/* forceUpdate= */ true);
- sendAirplaneModeSettingUpdate();
- sendMicrophoneDisableSettingUpdateForCurrentUser();
- sendBtSettingUpdate(/* forceUpdate= */ true);
+ resetSettings();
mTransactionManager.onHubReset();
queryNanoAppsInternal(contextHubId);
@@ -916,6 +963,8 @@
public IContextHubClient createClient(
int contextHubId, IContextHubClientCallback clientCallback,
@Nullable String attributionTag, String packageName) throws RemoteException {
+ super.createClient_enforcePermission();
+
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
}
@@ -944,6 +993,8 @@
public IContextHubClient createPendingIntentClient(
int contextHubId, PendingIntent pendingIntent, long nanoAppId,
@Nullable String attributionTag) throws RemoteException {
+ super.createPendingIntentClient_enforcePermission();
+
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
}
@@ -966,6 +1017,8 @@
public void loadNanoAppOnHub(
int contextHubId, IContextHubTransactionCallback transactionCallback,
NanoAppBinary nanoAppBinary) throws RemoteException {
+ super.loadNanoAppOnHub_enforcePermission();
+
if (!checkHalProxyAndContextHubId(
contextHubId, transactionCallback, ContextHubTransaction.TYPE_LOAD_NANOAPP)) {
return;
@@ -995,6 +1048,8 @@
public void unloadNanoAppFromHub(
int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
throws RemoteException {
+ super.unloadNanoAppFromHub_enforcePermission();
+
if (!checkHalProxyAndContextHubId(
contextHubId, transactionCallback, ContextHubTransaction.TYPE_UNLOAD_NANOAPP)) {
return;
@@ -1018,6 +1073,8 @@
public void enableNanoApp(
int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
throws RemoteException {
+ super.enableNanoApp_enforcePermission();
+
if (!checkHalProxyAndContextHubId(
contextHubId, transactionCallback, ContextHubTransaction.TYPE_ENABLE_NANOAPP)) {
return;
@@ -1041,6 +1098,8 @@
public void disableNanoApp(
int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId)
throws RemoteException {
+ super.disableNanoApp_enforcePermission();
+
if (!checkHalProxyAndContextHubId(
contextHubId, transactionCallback, ContextHubTransaction.TYPE_DISABLE_NANOAPP)) {
return;
@@ -1062,6 +1121,8 @@
@Override
public void queryNanoApps(int contextHubId, IContextHubTransactionCallback transactionCallback)
throws RemoteException {
+ super.queryNanoApps_enforcePermission();
+
if (!checkHalProxyAndContextHubId(
contextHubId, transactionCallback, ContextHubTransaction.TYPE_QUERY_NANOAPPS)) {
return;
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 432b097..48152b4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -32,6 +32,7 @@
import android.hardware.location.NanoAppState;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -92,6 +93,11 @@
*/
void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
List<String> nanoappPermissions, List<String> messagePermissions);
+
+ /**
+ * Handles a restart of the service
+ */
+ void handleServiceRestart();
}
/**
@@ -170,12 +176,9 @@
}
/**
- * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
- *
- * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+ * Attempts to connect to the AIDL HAL and returns the proxy IContextHub.
*/
- @Nullable
- public static IContextHubWrapper maybeConnectToAidl() {
+ public static android.hardware.contexthub.IContextHub maybeConnectToAidlGetProxy() {
android.hardware.contexthub.IContextHub proxy = null;
final String aidlServiceName =
android.hardware.contexthub.IContextHub.class.getCanonicalName() + "/default";
@@ -188,8 +191,18 @@
} else {
Log.d(TAG, "Context Hub AIDL service is not declared");
}
+ return proxy;
+ }
- return (proxy == null) ? null : new ContextHubWrapperAidl(proxy);
+ /**
+ * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
+ *
+ * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+ */
+ @Nullable
+ public static IContextHubWrapper maybeConnectToAidl() {
+ android.hardware.contexthub.IContextHub proxy = maybeConnectToAidlGetProxy();
+ return proxy == null ? null : new ContextHubWrapperAidl(proxy);
}
/**
@@ -354,12 +367,22 @@
public abstract void registerCallback(int contextHubId, @NonNull ICallback callback)
throws RemoteException;
- private static class ContextHubWrapperAidl extends IContextHubWrapper {
+ /**
+ * Registers an existing callback with the Context Hub.
+ *
+ * @param contextHubId The ID of the Context Hub to register the callback with.
+ */
+ public abstract void registerExistingCallback(int contextHubId) throws RemoteException;
+
+ private static class ContextHubWrapperAidl extends IContextHubWrapper
+ implements IBinder.DeathRecipient {
private android.hardware.contexthub.IContextHub mHub;
private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap =
new HashMap<>();
+ private Runnable mHandleServiceRestartCallback = null;
+
// Use this thread in case where the execution requires to be on a service thread.
// For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
private HandlerThread mHandlerThread =
@@ -419,17 +442,51 @@
}
ContextHubWrapperAidl(android.hardware.contexthub.IContextHub hub) {
- mHub = hub;
+ setHub(hub);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ linkWrapperToHubDeath();
+ }
+
+ private synchronized android.hardware.contexthub.IContextHub getHub() {
+ return mHub;
+ }
+
+ private synchronized void setHub(android.hardware.contexthub.IContextHub hub) {
+ mHub = hub;
+ }
+
+ @Override
+ public void binderDied() {
+ Log.i(TAG, "Context Hub AIDL HAL died");
+
+ setHub(maybeConnectToAidlGetProxy());
+ if (getHub() == null) {
+ // TODO(b/256860015): Make this reconnection more robust
+ Log.e(TAG, "Could not reconnect to Context Hub AIDL HAL");
+ return;
+ }
+ linkWrapperToHubDeath();
+
+ if (mHandleServiceRestartCallback != null) {
+ mHandleServiceRestartCallback.run();
+ } else {
+ Log.e(TAG, "mHandleServiceRestartCallback is not set");
+ }
}
public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
+ new ArrayList<String>());
+ }
+
Set<String> supportedPermissions = new HashSet<>();
ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
- for (android.hardware.contexthub.ContextHubInfo hub : mHub.getContextHubs()) {
- hubInfoList.add(new ContextHubInfo(hub));
- for (String permission : hub.supportedPermissions) {
+ for (android.hardware.contexthub.ContextHubInfo hubInfo : hub.getContextHubs()) {
+ hubInfoList.add(new ContextHubInfo(hubInfo));
+ for (String permission : hubInfo.supportedPermissions) {
supportedPermissions.add(permission);
}
}
@@ -489,8 +546,13 @@
@Override
public void onHostEndpointConnected(HostEndpointInfo info) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
try {
- mHub.onHostEndpointConnected(info);
+ hub.onHostEndpointConnected(info);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
}
@@ -498,8 +560,13 @@
@Override
public void onHostEndpointDisconnected(short hostEndpointId) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
try {
- mHub.onHostEndpointDisconnected((char) hostEndpointId);
+ hub.onHostEndpointDisconnected((char) hostEndpointId);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
}
@@ -509,8 +576,13 @@
public int sendMessageToContextHub(
short hostEndpointId, int contextHubId, NanoAppMessage message)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.sendMessageToHub(contextHubId,
+ hub.sendMessageToHub(contextHubId,
ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException e) {
@@ -523,10 +595,15 @@
@ContextHubTransaction.Result
public int loadNanoapp(int contextHubId, NanoAppBinary binary,
int transactionId) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
android.hardware.contexthub.NanoappBinary aidlNanoAppBinary =
ContextHubServiceUtil.createAidlNanoAppBinary(binary);
try {
- mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
+ hub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -538,8 +615,13 @@
@ContextHubTransaction.Result
public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
+ hub.unloadNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -551,8 +633,13 @@
@ContextHubTransaction.Result
public int enableNanoapp(int contextHubId, long nanoappId, int transactionId)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
+ hub.enableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -564,8 +651,13 @@
@ContextHubTransaction.Result
public int disableNanoapp(int contextHubId, long nanoappId, int transactionId)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
+ hub.disableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -576,8 +668,13 @@
@ContextHubTransaction.Result
public int queryNanoapps(int contextHubId) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.queryNanoapps(contextHubId);
+ hub.queryNanoapps(contextHubId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -586,22 +683,65 @@
}
}
- public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
- mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+ public void registerExistingCallback(int contextHubId) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ ContextHubAidlCallback callback = mAidlCallbackMap.get(contextHubId);
+ if (callback == null) {
+ Log.e(TAG, "Could not find existing callback to register for context hub ID = "
+ + contextHubId);
+ return;
+ }
+
try {
- mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
+ hub.registerCallback(contextHubId, callback);
} catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
Log.e(TAG, "Exception while registering callback: " + e.getMessage());
}
}
+ public void registerCallback(int contextHubId, ICallback callback) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ mHandleServiceRestartCallback = callback::handleServiceRestart;
+ mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+ registerExistingCallback(contextHubId);
+ }
+
private void onSettingChanged(byte setting, boolean enabled) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
try {
- mHub.onSettingChanged(setting, enabled);
+ hub.onSettingChanged(setting, enabled);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception while sending setting update: " + e.getMessage());
}
}
+
+ /**
+ * Links the mHub death handler to this
+ */
+ private void linkWrapperToHubDeath() {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ try {
+ hub.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException exception) {
+ Log.e(TAG, "Context Hub AIDL service death receipt could not be linked");
+ }
+ }
}
/**
@@ -729,6 +869,17 @@
mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId));
}
+ public void registerExistingCallback(int contextHubId) throws RemoteException {
+ ContextHubWrapperHidlCallback callback = mHidlCallbackMap.get(contextHubId);
+ if (callback == null) {
+ Log.e(TAG, "Could not find existing callback for context hub with ID = "
+ + contextHubId);
+ return;
+ }
+
+ mHub.registerCallback(contextHubId, callback);
+ }
+
public boolean supportsBtSettingNotifications() {
return false;
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index c67d54f..f5ec880 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -131,8 +131,6 @@
UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
}
}
-
- mEventLogger.enqueue(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
}
};
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index eb37ceb..5dcbb16 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3422,6 +3422,8 @@
@Override
public void setToastRateLimitingEnabled(boolean enable) {
+ super.setToastRateLimitingEnabled_enforcePermission();
+
synchronized (mToastQueue) {
int uid = Binder.getCallingUid();
int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 6735d55..bac8916 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -120,6 +120,8 @@
@Nullable
@EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
public String getLockName() {
+ super.getLockName_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
return mOemLock.getLockName();
@@ -131,6 +133,8 @@
@Override
@EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) {
+ super.setOemUnlockAllowedByCarrier_enforcePermission();
+
enforceUserIsAdmin();
final long token = Binder.clearCallingIdentity();
@@ -144,6 +148,8 @@
@Override
@EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE)
public boolean isOemUnlockAllowedByCarrier() {
+ super.isOemUnlockAllowedByCarrier_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
return mOemLock.isOemUnlockAllowedByCarrier();
@@ -157,6 +163,8 @@
@Override
@EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE)
public void setOemUnlockAllowedByUser(boolean allowedByUser) {
+ super.setOemUnlockAllowedByUser_enforcePermission();
+
if (ActivityManager.isUserAMonkey()) {
// Prevent a monkey from changing this
return;
@@ -183,6 +191,8 @@
@Override
@EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE)
public boolean isOemUnlockAllowedByUser() {
+ super.isOemUnlockAllowedByUser_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
return mOemLock.isOemUnlockAllowedByDevice();
@@ -199,6 +209,8 @@
@Override
@EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE})
public boolean isOemUnlockAllowed() {
+ super.isOemUnlockAllowed_enforcePermission();
+
final long token = Binder.clearCallingIdentity();
try {
boolean allowed = mOemLock.isOemUnlockAllowedByCarrier()
@@ -213,6 +225,8 @@
@Override
@EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE})
public boolean isDeviceOemUnlocked() {
+ super.isDeviceOemUnlocked_enforcePermission();
+
String locked = SystemProperties.get(FLASH_LOCK_PROP);
switch (locked) {
case FLASH_LOCK_UNLOCKED:
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index baa471c..3421eb7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -55,10 +55,12 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import android.content.res.ApkAssets;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FabricatedOverlayInternal;
@@ -880,14 +882,14 @@
}
Slog.d(TAG, "commit failed: " + e.getMessage(), e);
throw new SecurityException("commit failed"
- + (DEBUG ? ": " + e.getMessage() : ""));
+ + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
- private Set<PackageAndUser> executeRequest(
+ private Set<UserPackage> executeRequest(
@NonNull final OverlayManagerTransaction.Request request)
throws OperationFailedException {
Objects.requireNonNull(request, "Transaction contains a null request");
@@ -932,7 +934,7 @@
try {
switch (request.type) {
case TYPE_SET_ENABLED:
- Set<PackageAndUser> result = null;
+ Set<UserPackage> result = null;
result = CollectionUtils.addAll(result,
mImpl.setEnabled(request.overlay, true, realUserId));
result = CollectionUtils.addAll(result,
@@ -973,7 +975,7 @@
synchronized (mLock) {
// execute the requests (as calling user)
- Set<PackageAndUser> affectedPackagesToUpdate = null;
+ Set<UserPackage> affectedPackagesToUpdate = null;
for (final OverlayManagerTransaction.Request request : transaction) {
affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
executeRequest(request));
@@ -1370,13 +1372,13 @@
}
}
- private void updateTargetPackagesLocked(@Nullable PackageAndUser updatedTarget) {
+ private void updateTargetPackagesLocked(@Nullable UserPackage updatedTarget) {
if (updatedTarget != null) {
updateTargetPackagesLocked(Set.of(updatedTarget));
}
}
- private void updateTargetPackagesLocked(@Nullable Set<PackageAndUser> updatedTargets) {
+ private void updateTargetPackagesLocked(@Nullable Set<UserPackage> updatedTargets) {
if (CollectionUtils.isEmpty(updatedTargets)) {
return;
}
@@ -1405,7 +1407,7 @@
@Nullable
private static SparseArray<ArraySet<String>> groupTargetsByUserId(
- @Nullable final Set<PackageAndUser> targetsAndUsers) {
+ @Nullable final Set<UserPackage> targetsAndUsers) {
final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
CollectionUtils.forEach(targetsAndUsers, target -> {
ArraySet<String> targets = userTargets.get(target.userId);
@@ -1472,7 +1474,7 @@
@NonNull
private SparseArray<List<String>> updatePackageManagerLocked(
- @Nullable Set<PackageAndUser> targets) {
+ @Nullable Set<UserPackage> targets) {
if (CollectionUtils.isEmpty(targets)) {
return new SparseArray<>();
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 17bb39c..6ffe60d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -35,6 +35,7 @@
import android.content.om.CriticalOverlayInfo;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
+import android.content.pm.UserPackage;
import android.content.pm.overlay.OverlayPaths;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.os.FabricatedOverlayInfo;
@@ -154,13 +155,13 @@
* set of targets that had, but no longer have, active overlays.
*/
@NonNull
- ArraySet<PackageAndUser> updateOverlaysForUser(final int newUserId) {
+ ArraySet<UserPackage> updateOverlaysForUser(final int newUserId) {
if (DEBUG) {
Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId);
}
// Remove the settings of all overlays that are no longer installed for this user.
- final ArraySet<PackageAndUser> updatedTargets = new ArraySet<>();
+ final ArraySet<UserPackage> updatedTargets = new ArraySet<>();
final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser(
newUserId);
CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
@@ -185,7 +186,7 @@
// When a new user is switched to for the first time, package manager must be
// informed of the overlay paths for all overlaid packages installed in the user.
if (overlaidByOthers.contains(pkg.getPackageName())) {
- updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+ updatedTargets.add(UserPackage.of(newUserId, pkg.getPackageName()));
}
} catch (OperationFailedException e) {
Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
@@ -237,7 +238,7 @@
mSettings.setEnabled(overlay, newUserId, true);
if (updateState(oi, newUserId, 0)) {
CollectionUtils.add(updatedTargets,
- new PackageAndUser(oi.targetPackageName, oi.userId));
+ UserPackage.of(oi.userId, oi.targetPackageName));
}
}
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -258,40 +259,40 @@
}
@NonNull
- Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
+ Set<UserPackage> onPackageAdded(@NonNull final String pkgName,
final int userId) throws OperationFailedException {
- final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ final Set<UserPackage> updatedTargets = new ArraySet<>();
// Always update the overlays of newly added packages.
- updatedTargets.add(new PackageAndUser(pkgName, userId));
+ updatedTargets.add(UserPackage.of(userId, pkgName));
updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */));
return updatedTargets;
}
@NonNull
- Set<PackageAndUser> onPackageChanged(@NonNull final String pkgName,
+ Set<UserPackage> onPackageChanged(@NonNull final String pkgName,
final int userId) throws OperationFailedException {
return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
}
@NonNull
- Set<PackageAndUser> onPackageReplacing(@NonNull final String pkgName, final int userId)
+ Set<UserPackage> onPackageReplacing(@NonNull final String pkgName, final int userId)
throws OperationFailedException {
return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
}
@NonNull
- Set<PackageAndUser> onPackageReplaced(@NonNull final String pkgName, final int userId)
+ Set<UserPackage> onPackageReplaced(@NonNull final String pkgName, final int userId)
throws OperationFailedException {
return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
}
@NonNull
- Set<PackageAndUser> onPackageRemoved(@NonNull final String pkgName, final int userId) {
+ Set<UserPackage> onPackageRemoved(@NonNull final String pkgName, final int userId) {
if (DEBUG) {
Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId);
}
// Update the state of all overlays that target this package.
- final Set<PackageAndUser> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
+ final Set<UserPackage> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
// Remove all the overlays this package declares.
return CollectionUtils.addAll(targets,
@@ -299,15 +300,15 @@
}
@NonNull
- private Set<PackageAndUser> removeOverlaysForUser(
+ private Set<UserPackage> removeOverlaysForUser(
@NonNull final Predicate<OverlayInfo> condition, final int userId) {
final List<OverlayInfo> overlays = mSettings.removeIf(
io -> userId == io.userId && condition.test(io));
- Set<PackageAndUser> targets = Collections.emptySet();
+ Set<UserPackage> targets = Collections.emptySet();
for (int i = 0, n = overlays.size(); i < n; i++) {
final OverlayInfo info = overlays.get(i);
targets = CollectionUtils.add(targets,
- new PackageAndUser(info.targetPackageName, userId));
+ UserPackage.of(userId, info.targetPackageName));
// Remove the idmap if the overlay is no longer installed for any user.
removeIdmapIfPossible(info);
@@ -316,7 +317,7 @@
}
@NonNull
- private Set<PackageAndUser> updateOverlaysForTarget(@NonNull final String targetPackage,
+ private Set<UserPackage> updateOverlaysForTarget(@NonNull final String targetPackage,
final int userId, final int flags) {
boolean modified = false;
final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId);
@@ -332,18 +333,18 @@
if (!modified) {
return Collections.emptySet();
}
- return Set.of(new PackageAndUser(targetPackage, userId));
+ return Set.of(UserPackage.of(userId, targetPackage));
}
@NonNull
- private Set<PackageAndUser> updatePackageOverlays(@NonNull AndroidPackage pkg,
+ private Set<UserPackage> updatePackageOverlays(@NonNull AndroidPackage pkg,
final int userId, final int flags) throws OperationFailedException {
if (pkg.getOverlayTarget() == null) {
// This package does not have overlays declared in its manifest.
return Collections.emptySet();
}
- Set<PackageAndUser> updatedTargets = Collections.emptySet();
+ Set<UserPackage> updatedTargets = Collections.emptySet();
final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName());
final int priority = getPackageConfiguredPriority(pkg);
try {
@@ -353,7 +354,7 @@
// If the targetPackageName has changed, the package that *used* to
// be the target must also update its assets.
updatedTargets = CollectionUtils.add(updatedTargets,
- new PackageAndUser(currentInfo.targetPackageName, userId));
+ UserPackage.of(userId, currentInfo.targetPackageName));
}
currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(),
@@ -367,13 +368,13 @@
// reinitialized. Reorder the overlay and update its target package.
mSettings.setPriority(overlay, userId, priority);
updatedTargets = CollectionUtils.add(updatedTargets,
- new PackageAndUser(currentInfo.targetPackageName, userId));
+ UserPackage.of(userId, currentInfo.targetPackageName));
}
// Update the enabled state of the overlay.
if (updateState(currentInfo, userId, flags)) {
updatedTargets = CollectionUtils.add(updatedTargets,
- new PackageAndUser(currentInfo.targetPackageName, userId));
+ UserPackage.of(userId, currentInfo.targetPackageName));
}
} catch (OverlayManagerSettings.BadKeyException e) {
throw new OperationFailedException("failed to update settings", e);
@@ -382,14 +383,14 @@
}
@NonNull
- private Set<PackageAndUser> reconcileSettingsForPackage(@NonNull final String pkgName,
+ private Set<UserPackage> reconcileSettingsForPackage(@NonNull final String pkgName,
final int userId, final int flags) throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId);
}
// Update the state of overlays that target this package.
- Set<PackageAndUser> updatedTargets = Collections.emptySet();
+ Set<UserPackage> updatedTargets = Collections.emptySet();
updatedTargets = CollectionUtils.addAll(updatedTargets,
updateOverlaysForTarget(pkgName, userId, flags));
@@ -423,7 +424,7 @@
}
@NonNull
- Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
+ Set<UserPackage> setEnabled(@NonNull final OverlayIdentifier overlay,
final boolean enable, final int userId) throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d",
@@ -442,7 +443,7 @@
modified |= updateState(oi, userId, 0);
if (modified) {
- return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+ return Set.of(UserPackage.of(userId, oi.targetPackageName));
}
return Set.of();
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -450,7 +451,7 @@
}
}
- Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
+ Optional<UserPackage> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
boolean withinCategory, final int userId) throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, String.format("setEnabledExclusive overlay=%s"
@@ -493,7 +494,7 @@
modified |= updateState(enabledInfo, userId, 0);
if (modified) {
- return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId));
+ return Optional.of(UserPackage.of(userId, enabledInfo.targetPackageName));
}
return Optional.empty();
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -502,7 +503,7 @@
}
@NonNull
- Set<PackageAndUser> registerFabricatedOverlay(
+ Set<UserPackage> registerFabricatedOverlay(
@NonNull final FabricatedOverlayInternal overlay)
throws OperationFailedException {
if (FrameworkParsingPackageUtils.validateName(overlay.overlayName,
@@ -516,7 +517,7 @@
throw new OperationFailedException("failed to create fabricated overlay");
}
- final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ final Set<UserPackage> updatedTargets = new ArraySet<>();
for (int userId : mSettings.getUsers()) {
updatedTargets.addAll(registerFabricatedOverlay(info, userId));
}
@@ -524,13 +525,13 @@
}
@NonNull
- private Set<PackageAndUser> registerFabricatedOverlay(
+ private Set<UserPackage> registerFabricatedOverlay(
@NonNull final FabricatedOverlayInfo info, int userId)
throws OperationFailedException {
final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
info.packageName, info.overlayName);
- final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ final Set<UserPackage> updatedTargets = new ArraySet<>();
OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
if (oi != null) {
if (!oi.isFabricated) {
@@ -543,7 +544,7 @@
if (oi != null) {
// If the fabricated overlay changes its target package, update the previous
// target package so it no longer is overlaid.
- updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+ updatedTargets.add(UserPackage.of(userId, oi.targetPackageName));
}
oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName,
info.targetOverlayable, info.path, true, false,
@@ -554,7 +555,7 @@
mSettings.setBaseCodePath(overlayIdentifier, userId, info.path);
}
if (updateState(oi, userId, 0)) {
- updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+ updatedTargets.add(UserPackage.of(userId, oi.targetPackageName));
}
} catch (OverlayManagerSettings.BadKeyException e) {
throw new OperationFailedException("failed to update settings", e);
@@ -564,8 +565,8 @@
}
@NonNull
- Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
- final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+ Set<UserPackage> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
+ final Set<UserPackage> updatedTargets = new ArraySet<>();
for (int userId : mSettings.getUsers()) {
updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId));
}
@@ -573,7 +574,7 @@
}
@NonNull
- private Set<PackageAndUser> unregisterFabricatedOverlay(
+ private Set<UserPackage> unregisterFabricatedOverlay(
@NonNull final OverlayIdentifier overlay, int userId) {
final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId);
if (oi != null) {
@@ -581,7 +582,7 @@
if (oi.isEnabled()) {
// Removing a fabricated overlay only changes the overlay path of a package if it is
// currently enabled.
- return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+ return Set.of(UserPackage.of(userId, oi.targetPackageName));
}
}
return Set.of();
@@ -627,7 +628,7 @@
return mOverlayConfig.isEnabled(overlay.getPackageName());
}
- Optional<PackageAndUser> setPriority(@NonNull final OverlayIdentifier overlay,
+ Optional<UserPackage> setPriority(@NonNull final OverlayIdentifier overlay,
@NonNull final OverlayIdentifier newParentOverlay, final int userId)
throws OperationFailedException {
try {
@@ -644,7 +645,7 @@
}
if (mSettings.setPriority(overlay, newParentOverlay, userId)) {
- return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+ return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName));
}
return Optional.empty();
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -652,7 +653,7 @@
}
}
- Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
+ Set<UserPackage> setHighestPriority(@NonNull final OverlayIdentifier overlay,
final int userId) throws OperationFailedException {
try{
if (DEBUG) {
@@ -667,7 +668,7 @@
}
if (mSettings.setHighestPriority(overlay, userId)) {
- return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+ return Set.of(UserPackage.of(userId, overlayInfo.targetPackageName));
}
return Set.of();
} catch (OverlayManagerSettings.BadKeyException e) {
@@ -675,7 +676,7 @@
}
}
- Optional<PackageAndUser> setLowestPriority(@NonNull final OverlayIdentifier overlay,
+ Optional<UserPackage> setLowestPriority(@NonNull final OverlayIdentifier overlay,
final int userId) throws OperationFailedException {
try{
if (DEBUG) {
@@ -690,7 +691,7 @@
}
if (mSettings.setLowestPriority(overlay, userId)) {
- return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+ return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName));
}
return Optional.empty();
} catch (OverlayManagerSettings.BadKeyException e) {
diff --git a/services/core/java/com/android/server/pm/CloneProfileResolver.java b/services/core/java/com/android/server/pm/CloneProfileResolver.java
new file mode 100644
index 0000000..d3113e1
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CloneProfileResolver.java
@@ -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.server.pm;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.resolution.ComponentResolverApi;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Cross Profile intent resolution strategy used for and to clone profile.
+ */
+public class CloneProfileResolver extends CrossProfileResolver {
+
+ public CloneProfileResolver(ComponentResolverApi componentResolver,
+ UserManagerService userManagerService) {
+ super(componentResolver, userManagerService);
+ }
+
+ /**
+ * This is resolution strategy for Clone Profile.
+ * In case of clone profile, the profile is supposed to be transparent to end user. To end user
+ * clone and owner profile should be part of same user space. Hence, the resolution strategy
+ * would resolve intent in both profile and return combined result without any filtering of the
+ * results.
+ *
+ * @param computer ComputerEngine instance that would be needed by ComponentResolverApi
+ * @param intent request
+ * @param resolvedType the MIME data type of intent request
+ * @param userId source/initiating user
+ * @param targetUserId target user id
+ * @param flags of intent request
+ * @param pkgName the application package name this Intent is limited to
+ * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
+ * targeting the targetUserId
+ * @param hasNonNegativePriorityResult if source have any non-negative(active and valid)
+ * resolveInfo in their profile.
+ * @param pkgSettingFunction function to find PackageStateInternal for given package
+ * @return list of {@link CrossProfileDomainInfo}
+ */
+ @Override
+ public List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent,
+ String resolvedType, int userId, int targetUserId, long flags,
+ String pkgName, List<CrossProfileIntentFilter> matchingFilters,
+ boolean hasNonNegativePriorityResult,
+ Function<String, PackageStateInternal> pkgSettingFunction) {
+ List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer,
+ intent, resolvedType, flags, targetUserId);
+ List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
+ if (resolveInfos != null) {
+
+ for (int index = 0; index < resolveInfos.size(); index++) {
+ crossProfileDomainInfos.add(new CrossProfileDomainInfo(resolveInfos.get(index),
+ DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE,
+ targetUserId));
+ }
+ }
+ return filterIfNotSystemUser(crossProfileDomainInfos, userId);
+ }
+
+ /**
+ * As clone and owner profile are going to be part of the same userspace, we need no filtering
+ * out of any clone profile's result
+ * @param intent request
+ * @param crossProfileDomainInfos resolved in target user
+ * @param flags for intent resolution
+ * @param sourceUserId source user
+ * @param targetUserId target user
+ * @param highestApprovalLevel highest level of domain approval
+ * @return list of CrossProfileDomainInfo
+ */
+ @Override
+ public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(Intent intent,
+ List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, int sourceUserId,
+ int targetUserId, int highestApprovalLevel) {
+ // no filtering for clone profile
+ return crossProfileDomainInfos;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index d381e32..b285136 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -460,7 +460,7 @@
mBackgroundDexOptService = args.service.mBackgroundDexOptService;
mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
- mUserManager, mDomainVerificationManager, mDefaultAppProvider);
+ mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext);
// Used to reference PMS attributes that are primitives and which are not
// updated under control of the PMS lock.
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 0cd698a..798217f 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -46,6 +46,9 @@
private static final String ATTR_FILTER = "filter";
private static final String ATTR_ACCESS_CONTROL = "accessControl";
+ //flag to decide if intent needs to be resolved cross profile if pkgName is already defined
+ public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
+
private static final String TAG = "CrossProfileIntentFilter";
/**
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
index 9ea16d3..2581878 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static com.android.server.pm.CrossProfileIntentFilter.FLAG_IS_PACKAGE_FOR_FILTER;
+
import android.annotation.NonNull;
import android.content.IntentFilter;
@@ -37,7 +39,7 @@
@Override
protected boolean isPackageForFilter(String packageName, CrossProfileIntentFilter filter) {
- return false;
+ return (FLAG_IS_PACKAGE_FOR_FILTER & filter.mFlags) != 0;
}
@Override
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 7752fdf..5ae4cab 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -25,12 +25,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.Process;
import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -54,13 +56,15 @@
private final UserManagerService mUserManager;
private final DomainVerificationManagerInternal mDomainVerificationManager;
private final DefaultAppProvider mDefaultAppProvider;
+ private final Context mContext;
public CrossProfileIntentResolverEngine(UserManagerService userManager,
DomainVerificationManagerInternal domainVerificationManager,
- DefaultAppProvider defaultAppProvider) {
+ DefaultAppProvider defaultAppProvider, Context context) {
mUserManager = userManager;
mDomainVerificationManager = domainVerificationManager;
mDefaultAppProvider = defaultAppProvider;
+ mContext = context;
}
/**
@@ -196,6 +200,21 @@
@SuppressWarnings("unused")
private CrossProfileResolver chooseCrossProfileResolver(@NonNull Computer computer,
UserInfo sourceUserInfo, UserInfo targetUserInfo) {
+ //todo change isCloneProfile to user properties b/241532322
+ /**
+ * If source or target user is clone profile, using {@link CloneProfileResolver}
+ * We would allow CloneProfileResolver only if flag
+ * SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE is enabled
+ */
+ if (sourceUserInfo.isCloneProfile() || targetUserInfo.isCloneProfile()) {
+ if (FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE)) {
+ return new CloneProfileResolver(computer.getComponentResolver(),
+ mUserManager);
+ } else {
+ return null;
+ }
+ }
return new DefaultCrossProfileResolver(computer.getComponentResolver(),
mUserManager, mDomainVerificationManager);
}
@@ -313,8 +332,6 @@
}
if (pkgName == null && intent.hasWebURI()) {
- // If instant apps are not allowed and there is result only from current or cross
- // profile return it
if (!addInstant && ((candidates.size() <= 1 && crossProfileCandidates.isEmpty())
|| (candidates.isEmpty() && !crossProfileCandidates.isEmpty()))) {
candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b979e7a..8089af3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -107,6 +107,7 @@
import android.content.pm.SuspendDialogInfo;
import android.content.pm.TestUtilityService;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.overlay.OverlayPaths;
@@ -2986,6 +2987,7 @@
@Override
public void notifyPackageRemoved(String packageName, int uid) {
mPackageObserverHelper.notifyRemoved(packageName, uid);
+ UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName);
}
void sendPackageAddedForUser(@NonNull Computer snapshot, String packageName,
@@ -5061,8 +5063,11 @@
"getUnsuspendablePackagesForUser");
final int callingUid = Binder.getCallingUid();
if (UserHandle.getUserId(callingUid) != userId) {
- throw new SecurityException("Calling uid " + callingUid
- + " cannot query getUnsuspendablePackagesForUser for user " + userId);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "Calling uid " + callingUid
+ + " cannot query getUnsuspendablePackagesForUser for user "
+ + userId);
}
return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(),
packageNames, userId, callingUid);
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 0d99075..00f7dc4 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -20,6 +20,7 @@
import android.annotation.UserIdInt;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
+import android.content.pm.UserPackage;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -30,7 +31,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.ShortcutService.DumpFilter;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
import libcore.io.IoUtils;
@@ -70,7 +70,7 @@
/**
* Package name -> IDs.
*/
- final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
+ private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
@UserIdInt int ownerUserId, @NonNull String packageName,
@@ -101,12 +101,12 @@
* Called when the new package can't receive the backup, due to signature or version mismatch.
*/
private void onRestoreBlocked() {
- final ArrayList<PackageWithUser> pinnedPackages =
+ final ArrayList<UserPackage> pinnedPackages =
new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
- final PackageWithUser pu = pinnedPackages.get(i);
- final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName);
+ final UserPackage up = pinnedPackages.get(i);
+ final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(up.packageName);
if (p != null) {
p.refreshPinnedFlags();
}
@@ -135,13 +135,13 @@
return; // No need to instantiate.
}
- final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
+ final UserPackage up = UserPackage.of(packageUserId, packageName);
final int idSize = ids.size();
if (idSize == 0) {
- mPinnedShortcuts.remove(pu);
+ mPinnedShortcuts.remove(up);
} else {
- final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
+ final ArraySet<String> prevSet = mPinnedShortcuts.get(up);
// Actually pin shortcuts.
// This logic here is to make sure a launcher cannot pin a shortcut that is not dynamic
@@ -165,7 +165,7 @@
newSet.add(id);
}
}
- mPinnedShortcuts.put(pu, newSet);
+ mPinnedShortcuts.put(up, newSet);
}
packageShortcuts.refreshPinnedFlags();
}
@@ -176,7 +176,7 @@
@Nullable
public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
@UserIdInt int packageUserId) {
- return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
+ return mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName));
}
/**
@@ -207,7 +207,7 @@
}
boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
- return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
+ return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null;
}
public void ensurePackageInfo() {
@@ -241,15 +241,15 @@
getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
for (int i = 0; i < size; i++) {
- final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+ final UserPackage up = mPinnedShortcuts.keyAt(i);
- if (forBackup && (pu.userId != getOwnerUserId())) {
+ if (forBackup && (up.userId != getOwnerUserId())) {
continue; // Target package on a different user, skip. (i.e. work profile)
}
out.startTag(null, TAG_PACKAGE);
- ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
- ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, up.packageName);
+ ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, up.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -345,7 +345,7 @@
ATTR_PACKAGE_USER_ID, ownerUserId);
ids = new ArraySet<>();
ret.mPinnedShortcuts.put(
- PackageWithUser.of(packageUserId, packageName), ids);
+ UserPackage.of(packageUserId, packageName), ids);
continue;
}
}
@@ -386,14 +386,14 @@
for (int i = 0; i < size; i++) {
pw.println();
- final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+ final UserPackage up = mPinnedShortcuts.keyAt(i);
pw.print(prefix);
pw.print(" ");
pw.print("Package: ");
- pw.print(pu.packageName);
+ pw.print(up.packageName);
pw.print(" User: ");
- pw.println(pu.userId);
+ pw.println(up.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -418,7 +418,7 @@
@VisibleForTesting
ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
- return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
+ return new ArraySet<>(mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName)));
}
@Override
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index b6f09ff..83720f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -57,6 +57,7 @@
import android.content.pm.ShortcutManager;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
@@ -118,7 +119,6 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
import com.android.server.uri.UriGrantsManagerInternal;
import libcore.io.IoUtils;
@@ -3774,7 +3774,7 @@
final long start = getStatStartTime();
try {
- final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
+ final ArrayList<UserPackage> gonePackages = new ArrayList<>();
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
@@ -3789,13 +3789,14 @@
Slog.d(TAG, "Uninstalled: " + spi.getPackageName()
+ " user " + spi.getPackageUserId());
}
- gonePackages.add(PackageWithUser.of(spi));
+ gonePackages.add(
+ UserPackage.of(spi.getPackageUserId(), spi.getPackageName()));
}
});
if (gonePackages.size() > 0) {
for (int i = gonePackages.size() - 1; i >= 0; i--) {
- final PackageWithUser pu = gonePackages.get(i);
- cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
+ final UserPackage up = gonePackages.get(i);
+ cleanUpPackageLocked(up.packageName, ownerUserId, up.userId,
/* appStillExists = */ false);
}
}
@@ -5274,7 +5275,7 @@
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
- return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName));
+ return user.getAllLaunchersForTest().get(UserPackage.of(userId, packageName));
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 20bbf46..94eb6bb 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -21,6 +21,7 @@
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchSession;
import android.content.pm.ShortcutManager;
+import android.content.pm.UserPackage;
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.FileUtils;
@@ -52,7 +53,6 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -82,44 +82,6 @@
private static final String KEY_LAUNCHERS = "launchers";
private static final String KEY_PACKAGES = "packages";
- static final class PackageWithUser {
- final int userId;
- final String packageName;
-
- private PackageWithUser(int userId, String packageName) {
- this.userId = userId;
- this.packageName = Objects.requireNonNull(packageName);
- }
-
- public static PackageWithUser of(int userId, String packageName) {
- return new PackageWithUser(userId, packageName);
- }
-
- public static PackageWithUser of(ShortcutPackageItem spi) {
- return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
- }
-
- @Override
- public int hashCode() {
- return packageName.hashCode() ^ userId;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof PackageWithUser)) {
- return false;
- }
- final PackageWithUser that = (PackageWithUser) obj;
-
- return userId == that.userId && packageName.equals(that.packageName);
- }
-
- @Override
- public String toString() {
- return String.format("[Package: %d, %s]", userId, packageName);
- }
- }
-
final ShortcutService mService;
final AppSearchManager mAppSearchManager;
final Executor mExecutor;
@@ -129,7 +91,7 @@
private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
- private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
+ private final ArrayMap<UserPackage, ShortcutLauncher> mLaunchers = new ArrayMap<>();
/** In-memory-cached default launcher. */
private String mCachedLauncher;
@@ -204,20 +166,20 @@
// We don't expose this directly to non-test code because only ShortcutUser should add to/
// remove from it.
@VisibleForTesting
- ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
+ ArrayMap<UserPackage, ShortcutLauncher> getAllLaunchersForTest() {
return mLaunchers;
}
private void addLauncher(ShortcutLauncher launcher) {
launcher.replaceUser(this);
- mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
+ mLaunchers.put(UserPackage.of(launcher.getPackageUserId(),
launcher.getPackageName()), launcher);
}
@Nullable
public ShortcutLauncher removeLauncher(
@UserIdInt int packageUserId, @NonNull String packageName) {
- return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
+ return mLaunchers.remove(UserPackage.of(packageUserId, packageName));
}
@Nullable
@@ -242,7 +204,7 @@
@NonNull
public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
@UserIdInt int launcherUserId) {
- final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
+ final UserPackage key = UserPackage.of(launcherUserId, packageName);
ShortcutLauncher ret = mLaunchers.get(key);
if (ret == null) {
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index aeb11b7..fbfc84a 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -196,8 +196,11 @@
appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
true /* migrateAppData */);
}
- } catch (IllegalStateException e) {
- // Device was probably ejected, and we'll process that event momentarily
+ } catch (RuntimeException e) {
+ // The volume was probably already unmounted. We'll probably process the unmount
+ // event momentarily. TODO(b/256909937): ignoring errors from prepareUserStorage()
+ // is very dangerous. Instead, we should fix the race condition that allows this
+ // code to run on an unmounted volume in the first place.
Slog.w(TAG, "Failed to prepare storage: " + e);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cf0ea43..f85b11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -19,6 +19,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import android.Manifest;
import android.accounts.Account;
@@ -52,6 +53,7 @@
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserPackage;
import android.content.pm.UserProperties;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.res.Configuration;
@@ -313,7 +315,7 @@
@VisibleForTesting
static class UserData {
// Basic user information and properties
- UserInfo info;
+ @NonNull UserInfo info;
// Account name used when there is a strong association between a user and an account
String account;
// Account information for seeding into a newly created user. This could also be
@@ -3267,11 +3269,39 @@
}
}
+ /** Checks whether the device is currently in headless system user mode (for any reason). */
+ @Override
+ public boolean isHeadlessSystemUserMode() {
+ synchronized (mUsersLock) {
+ final UserData systemUserData = mUsers.get(UserHandle.USER_SYSTEM);
+ return !systemUserData.info.isFull();
+ }
+ }
+
/**
- * Checks whether the device is really headless system user mode, ignoring system user mode
- * emulation.
+ * Checks whether the default state of the device is headless system user mode, i.e. what the
+ * mode would be if we did a fresh factory reset.
+ * If the mode is being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be
+ * returned instead.
+ * Note that, even in the absence of emulation, a device might deviate from the current default
+ * due to an OTA changing the default (which won't change the already-decided mode).
*/
- private boolean isReallyHeadlessSystemUserMode() {
+ private boolean isDefaultHeadlessSystemUserMode() {
+ if (!Build.isDebuggable()) {
+ return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
+ }
+
+ final String emulatedValue = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
+ if (!TextUtils.isEmpty(emulatedValue)) {
+ if (UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS.equals(emulatedValue)) return true;
+ if (UserManager.SYSTEM_USER_MODE_EMULATION_FULL.equals(emulatedValue)) return false;
+ if (!UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT.equals(emulatedValue)) {
+ Slogf.e(LOG_TAG, "isDefaultHeadlessSystemUserMode(): ignoring invalid valued of "
+ + "property %s: %s",
+ SYSTEM_USER_MODE_EMULATION_PROPERTY, emulatedValue);
+ }
+ }
+
return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
}
@@ -3283,30 +3313,11 @@
if (!Build.isDebuggable()) {
return;
}
-
- final String emulatedValue = SystemProperties
- .get(UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY);
- if (TextUtils.isEmpty(emulatedValue)) {
+ if (TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
return;
}
- final boolean newHeadlessSystemUserMode;
- switch (emulatedValue) {
- case UserManager.SYSTEM_USER_MODE_EMULATION_FULL:
- newHeadlessSystemUserMode = false;
- break;
- case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS:
- newHeadlessSystemUserMode = true;
- break;
- case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT:
- newHeadlessSystemUserMode = isReallyHeadlessSystemUserMode();
- break;
- default:
- Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): ignoring invalid valued of "
- + "property %s: %s", UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY,
- emulatedValue);
- return;
- }
+ final boolean newHeadlessSystemUserMode = isDefaultHeadlessSystemUserMode();
// Update system user type
synchronized (mPackagesLock) {
@@ -3342,7 +3353,7 @@
}
}
- // Update emulated mode, which will used to triger an update on user packages
+ // Update emulated mode, which will used to trigger an update on user packages
mUpdatingSystemUserMode = true;
}
@@ -3532,7 +3543,11 @@
synchronized (mUsersLock) {
UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
userData.info.flags |= UserInfo.FLAG_SYSTEM;
- if (!UserManager.isHeadlessSystemUserMode()) {
+ // We assume that isDefaultHeadlessSystemUserMode() does not change during the OTA
+ // from userVersion < 8 since it is documented that pre-R devices do not support its
+ // modification. Therefore, its current value should be the same as the pre-update
+ // version.
+ if (!isDefaultHeadlessSystemUserMode()) {
userData.info.flags |= UserInfo.FLAG_FULL;
}
userIdsToWrite.add(userData.info.id);
@@ -3741,7 +3756,7 @@
int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
| UserInfo.FLAG_PRIMARY;
// Create the system user
- String systemUserType = UserManager.isHeadlessSystemUserMode()
+ String systemUserType = isDefaultHeadlessSystemUserMode()
? UserManager.USER_TYPE_SYSTEM_HEADLESS
: UserManager.USER_TYPE_FULL_SYSTEM;
flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
@@ -5848,6 +5863,7 @@
Slog.d(LOG_TAG, "updateUserIds(): userIds= " + Arrays.toString(mUserIds)
+ " includingPreCreated=" + Arrays.toString(mUserIdsIncludingPreCreated));
}
+ UserPackage.setValidUserIds(mUserIds);
}
}
@@ -6208,9 +6224,12 @@
com.android.internal.R.bool.config_guestUserEphemeral));
pw.println(" Force ephemeral users: " + mForceEphemeralUsers);
pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
- final boolean isHeadlessSystemUserMode = UserManager.isHeadlessSystemUserMode();
+ final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
pw.println(" Is headless-system mode: " + isHeadlessSystemUserMode);
- if (isHeadlessSystemUserMode != isReallyHeadlessSystemUserMode()) {
+ if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
+ pw.println(" (differs from the current default build value)");
+ }
+ if (!TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
pw.println(" (emulated by 'cmd user set-system-user-mode-emulation')");
if (mUpdatingSystemUserMode) {
pw.println(" (and being updated after boot)");
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b98d20e..5b6196f 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -128,7 +128,8 @@
.setIsCredentialSharableWithParent(true)
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
- .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT));
}
/**
@@ -163,7 +164,8 @@
.setIsCredentialSharableWithParent(true)
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
- .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE));
}
/**
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 8772de3..a7d4cea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.apex.ApexInfo;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.Attribution;
@@ -79,11 +78,8 @@
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.pkg.parsing.ParsingUtils;
-import libcore.util.EmptyArray;
-
import java.io.File;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -109,21 +105,9 @@
public static PackageInfo generate(AndroidPackage pkg, int[] gids,
@PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
- @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
- grantedPermissions, state, userId, null, pkgSetting);
- }
-
- /**
- * @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, long flags,
- @Nullable PackageStateInternal pkgSetting, @UserIdInt int userId) {
- return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
- PackageUserStateInternal.DEFAULT, userId, apexInfo, pkgSetting);
+ grantedPermissions, state, userId, pkgSetting);
}
/**
@@ -132,8 +116,7 @@
private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
@PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
- @UserIdInt int userId, @Nullable ApexInfo apexInfo,
- @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
pkgSetting);
if (applicationInfo == null) {
@@ -247,22 +230,6 @@
&= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
}
- if (apexInfo != null) {
- File apexFile = new File(apexInfo.modulePath);
-
- info.applicationInfo.sourceDir = apexFile.getPath();
- info.applicationInfo.publicSourceDir = apexFile.getPath();
- info.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
- info.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
- if (apexInfo.isFactory) {
- info.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- } else {
- info.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- }
- info.isApex = true;
- info.isActiveApex = apexInfo.isActive;
- }
-
final SigningDetails signingDetails = pkg.getSigningDetails();
// deprecated method of getting signing certificates
if ((flags & PackageManager.GET_SIGNATURES) != 0) {
@@ -294,7 +261,7 @@
info.coreApp = pkg.isCoreApp();
info.isApex = pkg.isApex();
- if (pkgSetting != null && !pkgSetting.hasSharedUser()) {
+ if (!pkgSetting.hasSharedUser()) {
// It is possible that this shared UID app has left
info.sharedUserId = null;
info.sharedUserLabel = 0;
@@ -452,7 +419,7 @@
public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
@PackageManager.ApplicationInfoFlagsBits long flags,
@NonNull PackageUserStateInternal state, @UserIdInt int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
if (pkg == null) {
return null;
}
@@ -463,35 +430,31 @@
}
// Make shallow copy so we can store the metadata/libraries safely
- ApplicationInfo info = AndroidPackageUtils.toAppInfoWithoutState(pkg);
+ ApplicationInfo info = AndroidPackageUtils.generateAppInfoWithoutState(pkg);
updateApplicationInfo(info, flags, state);
initForUser(info, pkg, userId);
- if (pkgSetting != null) {
- // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
- PackageStateUnserialized pkgState = pkgSetting.getTransientState();
- info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
- List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
- var usesLibraries = pkgState.getUsesLibraryInfos();
- var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
- for (int index = 0; index < usesLibraries.size(); index++) {
- usesLibraryInfos.add(usesLibraries.get(index).getInfo());
- }
- info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
- ? null : usesLibraryFiles.toArray(new String[0]);
- info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
- if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
- info.category = pkgSetting.getCategoryOverride();
- }
+ // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
+ PackageStateUnserialized pkgState = pkgSetting.getTransientState();
+ info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
+ List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
+ var usesLibraries = pkgState.getUsesLibraryInfos();
+ var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
+ for (int index = 0; index < usesLibraries.size(); index++) {
+ usesLibraryInfos.add(usesLibraries.get(index).getInfo());
+ }
+ info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+ ? null : usesLibraryFiles.toArray(new String[0]);
+ info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+ if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ info.category = pkgSetting.getCategoryOverride();
}
info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
- info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
- : pkgSetting.getPrimaryCpuAbi();
- info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
- : pkgSetting.getSecondaryCpuAbi();
+ info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+ info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
info.flags |= appInfoFlags(info.flags, pkgSetting);
info.privateFlags |= appInfoPrivateFlags(info.privateFlags, pkgSetting);
@@ -508,7 +471,7 @@
public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
@PackageManager.ComponentInfoFlagsBits long flags,
@NonNull PackageUserStateInternal state, @UserIdInt int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
}
@@ -520,7 +483,7 @@
public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
@PackageManager.ComponentInfoFlagsBits long flags,
@NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
- @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
if (a == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -597,7 +560,7 @@
@Nullable
public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
@PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
- @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
}
@@ -609,7 +572,7 @@
public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
@PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
@Nullable ApplicationInfo applicationInfo, int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
if (s == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -647,7 +610,7 @@
public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
@PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
@NonNull ApplicationInfo applicationInfo, int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
if (p == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -696,7 +659,7 @@
@Nullable
public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags,
- PackageUserStateInternal state, int userId, @Nullable PackageStateInternal pkgSetting) {
+ PackageUserStateInternal state, int userId, @NonNull PackageStateInternal pkgSetting) {
if (i == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -719,10 +682,8 @@
initForUser(info, pkg, userId);
- info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
- : pkgSetting.getPrimaryCpuAbi();
- info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
- : pkgSetting.getSecondaryCpuAbi();
+ info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+ info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
info.nativeLibraryDir = pkg.getNativeLibraryDir();
info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
@@ -820,12 +781,11 @@
* all uninstalled and hidden packages as well.
*/
public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
- PackageStateInternal pkgSetting, PackageUserStateInternal state,
+ @NonNull PackageStateInternal pkgSetting, PackageUserStateInternal state,
@PackageManager.PackageInfoFlagsBits long flags) {
// Returns false if the package is hidden system app until installed.
if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
&& !state.isInstalled()
- && pkgSetting != null
&& pkgSetting.getTransientState().isHiddenUntilInstalled()) {
return false;
}
@@ -878,7 +838,7 @@
private static void assignFieldsComponentInfoParsedMainComponent(
@NonNull ComponentInfo info, @NonNull ParsedMainComponent component,
- @Nullable PackageStateInternal pkgSetting, int userId) {
+ @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
assignFieldsComponentInfoParsedMainComponent(info, component);
Pair<CharSequence, Integer> labelAndIcon =
ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -889,7 +849,7 @@
private static void assignFieldsPackageItemInfoParsedComponent(
@NonNull PackageItemInfo info, @NonNull ParsedComponent component,
- @Nullable PackageStateInternal pkgSetting, int userId) {
+ @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
assignFieldsPackageItemInfoParsedComponent(info, component);
Pair<CharSequence, Integer> labelAndIcon =
ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -1141,7 +1101,7 @@
@Nullable
public ApplicationInfo generate(AndroidPackage pkg,
@PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
- int userId, @Nullable PackageStateInternal pkgSetting) {
+ int userId, @NonNull PackageStateInternal pkgSetting) {
ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
if (appInfo != null) {
return appInfo;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index a6f1b29..5b0cc51 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -321,8 +321,4 @@
info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
}
-
- public static ApplicationInfo toAppInfoWithoutState(AndroidPackage pkg) {
- return ((ParsingPackageHidden) pkg).toAppInfoWithoutState();
- }
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 3b9f0ba..1fa3b3b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -406,6 +406,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS)
@Override
public void stopOneTimePermissionSession(String packageName, @UserIdInt int userId) {
+ super.stopOneTimePermissionSession_enforcePermission();
+
Objects.requireNonNull(packageName);
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 61c21e6..ab35dc8 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -35,6 +35,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
+import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.hardware.SensorPrivacyManager.Sources.DIALOG;
@@ -664,6 +665,29 @@
.build());
}
+ private void showSensorStateChangedActivity(@SensorPrivacyManager.Sensors.Sensor int sensor,
+ @SensorPrivacyManager.ToggleType int toggleType) {
+ String activityName = mContext.getResources().getString(
+ R.string.config_sensorStateChangedActivity);
+ if (TextUtils.isEmpty(activityName)) {
+ return;
+ }
+
+ Intent dialogIntent = new Intent();
+ dialogIntent.setComponent(
+ ComponentName.unflattenFromString(activityName));
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setTaskOverlay(true, true);
+
+ dialogIntent.addFlags(
+ FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_USER_ACTION);
+
+ dialogIntent.putExtra(EXTRA_SENSOR, sensor);
+ dialogIntent.putExtra(EXTRA_TOGGLE_TYPE, toggleType);
+ mContext.startActivityAsUser(dialogIntent, options.toBundle(), UserHandle.SYSTEM);
+ }
+
private boolean isTelevision(Context context) {
int uiMode = context.getResources().getConfiguration().uiMode;
return (uiMode & Configuration.UI_MODE_TYPE_MASK)
@@ -1378,6 +1402,8 @@
mToggleSensorListeners.finishBroadcast();
}
}
+
+ mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
}
public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 4111446..43ffa81 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -18,11 +18,11 @@
import android.annotation.Nullable;
import android.app.ITransientNotificationCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.IBinder;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -158,7 +158,7 @@
/** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails);
/** @see com.android.internal.statusbar.IStatusBar#showTransient */
@@ -192,9 +192,10 @@
void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable);
/**
- * Sets the system-wide listener for UDFPS HBM status changes.
+ * Sets the system-wide callback for UDFPS refresh rate changes.
*
- * @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+ * @see com.android.internal.statusbar.IStatusBar#setUdfpsRefreshRateCallback
+ * (IUdfpsRefreshRateRequestCallback)
*/
- void setUdfpsHbmListener(IUdfpsHbmListener listener);
+ void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c59b90f..7281a47 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -56,7 +56,7 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.net.Uri;
@@ -83,7 +83,8 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -175,7 +176,7 @@
private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
@GuardedBy("mLock")
- private IUdfpsHbmListener mUdfpsHbmListener;
+ private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
@GuardedBy("mLock")
private IBiometricContextListener mBiometricContextListener;
@@ -617,15 +618,15 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName, LetterboxDetails[] letterboxDetails) {
getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
letterboxDetails);
if (mBar != null) {
try {
mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
letterboxDetails);
} catch (RemoteException ex) { }
}
@@ -694,13 +695,13 @@
}
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
synchronized (mLock) {
- mUdfpsHbmListener = listener;
+ mUdfpsRefreshRateRequestCallback = callback;
}
if (mBar != null) {
try {
- mBar.setUdfpsHbmListener(listener);
+ mBar.setUdfpsRefreshRateCallback(callback);
} catch (RemoteException ex) { }
}
}
@@ -944,11 +945,11 @@
}
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
enforceStatusBarService();
if (mBar != null) {
try {
- mBar.setUdfpsHbmListener(listener);
+ mBar.setUdfpsRefreshRateCallback(callback);
} catch (RemoteException ex) {
}
}
@@ -1211,7 +1212,7 @@
private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
private boolean mNavbarColorManagedByIme = false;
private @Behavior int mBehavior;
- private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
private String mPackageName = "none";
private int mDisabled1 = 0;
private int mDisabled2 = 0;
@@ -1223,14 +1224,14 @@
private void setBarAttributes(@Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName,
LetterboxDetails[] letterboxDetails) {
mAppearance = appearance;
mAppearanceRegions = appearanceRegions;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
- mRequestedVisibilities = requestedVisibilities;
+ mRequestedVisibleTypes = requestedVisibleTypes;
mPackageName = packageName;
mLetterboxDetails = letterboxDetails;
}
@@ -1363,7 +1364,7 @@
state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
state.mImeBackDisposition, state.mShowImeSwitcher,
gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
- state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
+ state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes,
state.mPackageName, transientBarTypes, state.mLetterboxDetails);
}
}
@@ -1374,11 +1375,11 @@
mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null);
});
// If StatusBarService dies, system_server doesn't get killed with it, so we need to make
- // sure the UDFPS listener is refreshed as well. Deferring to the handler just so to avoid
+ // sure the UDFPS callback is refreshed as well. Deferring to the handler just so to avoid
// making registerStatusBar re-entrant.
mHandler.post(() -> {
synchronized (mLock) {
- setUdfpsHbmListener(mUdfpsHbmListener);
+ setUdfpsRefreshRateCallback(mUdfpsRefreshRateRequestCallback);
setBiometicContextListener(mBiometricContextListener);
}
});
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4972412..8d106f7 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -28,7 +28,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import java.io.PrintWriter;
import java.util.Objects;
@@ -60,10 +60,10 @@
@Override
public void setConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
- ConfigurationChangeListener configurationChangeListener =
+ @NonNull StateChangeListener listener) {
+ StateChangeListener stateChangeListener =
() -> mHandler.post(listener::onChange);
- mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
+ mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
}
@Override
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 773b517..e9827ce 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -25,8 +25,8 @@
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -185,8 +185,7 @@
* ensure O(1) lookup performance when working out whether a listener should trigger.
*/
@GuardedBy("mListeners")
- private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
- new ArrayMap<>();
+ private final ArrayMap<StateChangeListener, HashSet<String>> mListeners = new ArrayMap<>();
private static final Object SLOCK = new Object();
@@ -213,7 +212,7 @@
private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
synchronized (mListeners) {
- for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
+ for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry
: mListeners.entrySet()) {
// It's unclear which set of the following two Sets is going to be larger in the
// average case: monitoredKeys will be a subset of the set of possible keys, but
@@ -249,7 +248,7 @@
* <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no
* associated remove method.
*/
- public void addListener(@NonNull ConfigurationChangeListener listener,
+ public void addListener(@NonNull StateChangeListener listener,
@NonNull Set<String> keys) {
Objects.requireNonNull(listener);
Objects.requireNonNull(keys);
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index a39f64c..ff180eb 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -19,7 +19,7 @@
import android.annotation.UserIdInt;
import android.app.time.TimeConfiguration;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
/**
* An interface that provides access to service configuration for time detection. This hides
@@ -33,18 +33,18 @@
* Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
* The listener is invoked on the main thread.
*/
- void addConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+ void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Removes a listener previously added via {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+ * #addConfigurationInternalChangeListener(StateChangeListener)}.
*/
- void removeConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+ void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
* snapshot so callers must use {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+ * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
* changes.
*/
@NonNull
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 71acf35..4ef713c 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -49,7 +49,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import java.time.Instant;
import java.util.ArrayList;
@@ -104,8 +104,8 @@
@NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier;
@GuardedBy("this")
- @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
- new ArrayList<>();
+ @NonNull
+ private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
/**
* If a newly calculated system clock time and the current system clock time differs by this or
@@ -167,20 +167,20 @@
}
private synchronized void handleConfigurationInternalChangeOnMainThread() {
- for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+ for (StateChangeListener changeListener : mConfigurationInternalListeners) {
changeListener.onChange();
}
}
@Override
public synchronized void addConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
}
@Override
public synchronized void removeConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 3cee19c..13ec753 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -44,8 +44,8 @@
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timezonedetector.ArrayMapWithHistory;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ReferenceWithHistory;
+import com.android.server.timezonedetector.StateChangeListener;
import java.io.PrintWriter;
import java.time.Duration;
@@ -136,11 +136,11 @@
public interface Environment {
/**
- * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
- * changes that could affect the content of {@link ConfigurationInternal}.
+ * Sets a {@link StateChangeListener} that will be invoked when there are any changes that
+ * could affect the content of {@link ConfigurationInternal}.
* This is invoked during system server setup.
*/
- void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+ void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/** Returns the {@link ConfigurationInternal} for the current user. */
@NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 8e2a5f4..0409a84 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -26,7 +26,6 @@
import android.annotation.UserIdInt;
import android.app.time.Capabilities.CapabilityState;
import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.os.UserHandle;
@@ -75,7 +74,7 @@
mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
- mUserId = builder.mUserId;
+ mUserId = Objects.requireNonNull(builder.mUserId, "userId must be set");
mUserConfigAllowed = builder.mUserConfigAllowed;
mLocationEnabledSetting = builder.mLocationEnabledSetting;
mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
@@ -151,8 +150,7 @@
* Returns true if the user is allowed to modify time zone configuration, e.g. can be false due
* to device policy (enterprise).
*
- * <p>See also {@link #createCapabilitiesAndConfig(boolean)} for situations where this value
- * are ignored.
+ * <p>See also {@link #asCapabilities(boolean)} for situations where this value is ignored.
*/
public boolean isUserConfigAllowed() {
return mUserConfigAllowed;
@@ -196,20 +194,8 @@
|| getGeoDetectionRunInBackgroundEnabled());
}
- /**
- * Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values.
- *
- * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
- * policy restrictions that should apply to actual users can be ignored
- */
- public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
- boolean bypassUserPolicyChecks) {
- return new TimeZoneCapabilitiesAndConfig(
- asCapabilities(bypassUserPolicyChecks), asConfiguration());
- }
-
@NonNull
- private TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
+ public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
UserHandle userHandle = UserHandle.of(mUserId);
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
@@ -262,7 +248,7 @@
}
/** Returns a {@link TimeZoneConfiguration} from the configuration values. */
- private TimeZoneConfiguration asConfiguration() {
+ public TimeZoneConfiguration asConfiguration() {
return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
.setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
@@ -335,8 +321,7 @@
*/
public static class Builder {
- private final @UserIdInt int mUserId;
-
+ private @UserIdInt Integer mUserId;
private boolean mUserConfigAllowed;
private boolean mTelephonyDetectionSupported;
private boolean mGeoDetectionSupported;
@@ -348,11 +333,9 @@
private boolean mGeoDetectionEnabledSetting;
/**
- * Creates a new Builder with only the userId set.
+ * Creates a new Builder.
*/
- public Builder(@UserIdInt int userId) {
- mUserId = userId;
- }
+ public Builder() {}
/**
* Creates a new Builder by copying values from an existing instance.
@@ -371,6 +354,14 @@
}
/**
+ * Sets the user ID the configuration is for.
+ */
+ public Builder setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ return this;
+ }
+
+ /**
* Sets whether the user is allowed to configure time zone settings on this device.
*/
public Builder setUserConfigAllowed(boolean configAllowed) {
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 4749f73..5cb48c2 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -18,8 +18,6 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -29,7 +27,6 @@
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import java.io.PrintWriter;
-import java.util.Objects;
/**
* The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
@@ -38,29 +35,7 @@
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
- @NonNull private final Context mContext;
- @NonNull private final Handler mHandler;
- @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-
- EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
- @NonNull ServiceConfigAccessor serviceConfigAccessor) {
- mContext = Objects.requireNonNull(context);
- mHandler = Objects.requireNonNull(handler);
- mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
- }
-
- @Override
- public void setConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
- ConfigurationChangeListener configurationChangeListener =
- () -> mHandler.post(listener::onChange);
- mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
- }
-
- @Override
- @NonNull
- public ConfigurationInternal getCurrentUserConfigurationInternal() {
- return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+ EnvironmentImpl() {
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 8da5d6a..4ac2ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -59,20 +59,18 @@
* Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
* The listener is invoked on the main thread.
*/
- void addConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener);
+ void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Removes a listener previously added via {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+ * #addConfigurationInternalChangeListener(StateChangeListener)}.
*/
- void removeConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener);
+ void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
* snapshot so callers must use {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+ * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
* changes.
*/
@NonNull
@@ -104,8 +102,7 @@
*
* <p>Note: Currently only for use by long-lived objects; there is no associated remove method.
*/
- void addLocationTimeZoneManagerConfigListener(
- @NonNull ConfigurationChangeListener listener);
+ void addLocationTimeZoneManagerConfigListener(@NonNull StateChangeListener listener);
/**
* Returns {@code true} if the telephony-based time zone detection feature is supported on the
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index e2f4246..dfb9619 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -22,7 +22,6 @@
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -104,8 +103,8 @@
@NonNull private final LocationManager mLocationManager;
@GuardedBy("this")
- @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
- new ArrayList<>();
+ @NonNull
+ private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
/**
* The mode to use for the primary location time zone provider in a test. Setting this
@@ -207,20 +206,20 @@
}
private synchronized void handleConfigurationInternalChangeOnMainThread() {
- for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+ for (StateChangeListener changeListener : mConfigurationInternalListeners) {
changeListener.onChange();
}
}
@Override
public synchronized void addConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
}
@Override
public synchronized void removeConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
}
@@ -237,10 +236,10 @@
@NonNull TimeZoneConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
Objects.requireNonNull(requestedConfiguration);
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
- .createCapabilitiesAndConfig(bypassUserPolicyChecks);
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
- TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
+ ConfigurationInternal configurationInternal = getConfigurationInternal(userId);
+ TimeZoneCapabilities capabilities =
+ configurationInternal.asCapabilities(bypassUserPolicyChecks);
+ TimeZoneConfiguration oldConfiguration = configurationInternal.asConfiguration();
final TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
@@ -292,7 +291,8 @@
@Override
@NonNull
public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
- return new ConfigurationInternal.Builder(userId)
+ return new ConfigurationInternal.Builder()
+ .setUserId(userId)
.setTelephonyDetectionFeatureSupported(
isTelephonyTimeZoneDetectionFeatureSupported())
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
@@ -354,7 +354,7 @@
@Override
public void addLocationTimeZoneManagerConfigListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH);
}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
similarity index 78%
rename from services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
rename to services/core/java/com/android/server/timezonedetector/StateChangeListener.java
index aa8ad37..2b5639c 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
+++ b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
@@ -17,11 +17,11 @@
package com.android.server.timezonedetector;
/**
- * A listener used to receive notification that configuration has / may have changed (depending on
+ * A listener used to receive notification that state has / may have changed (depending on
* the usecase).
*/
@FunctionalInterface
-public interface ConfigurationChangeListener {
- /** Called when the configuration may have changed. */
+public interface StateChangeListener {
+ /** Called when something (may have) changed. */
void onChange();
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index ce64eac..dfb44df 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -35,17 +35,14 @@
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final CurrentUserIdentityInjector mCurrentUserIdentityInjector;
- @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
@NonNull CurrentUserIdentityInjector currentUserIdentityInjector,
- @NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mCurrentUserIdentityInjector = Objects.requireNonNull(currentUserIdentityInjector);
- mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
}
@@ -53,10 +50,9 @@
@NonNull
public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfigForDpm() {
int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
- ConfigurationInternal configurationInternal =
- mServiceConfigAccessor.getConfigurationInternal(currentUserId);
final boolean bypassUserPolicyChecks = true;
- return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+ return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ currentUserId, bypassUserPolicyChecks);
}
@Override
@@ -65,7 +61,7 @@
int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
final boolean bypassUserPolicyChecks = true;
- return mServiceConfigAccessor.updateConfiguration(
+ return mTimeZoneDetectorStrategy.updateConfiguration(
currentUserId, configuration, bypassUserPolicyChecks);
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 13f1694..f415cf0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -83,7 +83,7 @@
ServiceConfigAccessor serviceConfigAccessor =
ServiceConfigAccessorImpl.getInstance(context);
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
- TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+ TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
DeviceActivityMonitor deviceActivityMonitor =
DeviceActivityMonitorImpl.create(context, handler);
@@ -99,16 +99,14 @@
CurrentUserIdentityInjector currentUserIdentityInjector =
CurrentUserIdentityInjector.REAL;
TimeZoneDetectorInternal internal = new TimeZoneDetectorInternalImpl(
- context, handler, currentUserIdentityInjector, serviceConfigAccessor,
- timeZoneDetectorStrategy);
+ context, handler, currentUserIdentityInjector, timeZoneDetectorStrategy);
publishLocalService(TimeZoneDetectorInternal.class, internal);
// Publish the binder service so it can be accessed from other (appropriately
// permissioned) processes.
CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
TimeZoneDetectorService service = new TimeZoneDetectorService(
- context, handler, callerIdentityInjector, serviceConfigAccessor,
- timeZoneDetectorStrategy);
+ context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
// Dump the device activity monitor when the service is dumped.
service.addDumpable(deviceActivityMonitor);
@@ -127,9 +125,6 @@
private final CallerIdentityInjector mCallerIdentityInjector;
@NonNull
- private final ServiceConfigAccessor mServiceConfigAccessor;
-
- @NonNull
private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
/**
@@ -150,18 +145,16 @@
@VisibleForTesting
public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
@NonNull CallerIdentityInjector callerIdentityInjector,
- @NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
- mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
// Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
- // the configuration changes for any reason.
- mServiceConfigAccessor.addConfigurationInternalChangeListener(
- () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
+ // the detector state changes for any reason.
+ mTimeZoneDetectorStrategy.addChangeListener(
+ () -> mHandler.post(this::handleChangeOnHandlerThread));
}
@Override
@@ -174,12 +167,15 @@
TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId) {
enforceManageTimeZoneDetectorPermission();
+ // Resolve constants like USER_CURRENT to the true user ID as needed.
+ int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, false, "getCapabilitiesAndConfig", null);
+
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- ConfigurationInternal configurationInternal =
- mServiceConfigAccessor.getConfigurationInternal(userId);
final boolean bypassUserPolicyChecks = false;
- return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+ return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ resolvedUserId, bypassUserPolicyChecks);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
@@ -204,7 +200,7 @@
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
final boolean bypassUserPolicyChecks = false;
- return mServiceConfigAccessor.updateConfiguration(
+ return mTimeZoneDetectorStrategy.updateConfiguration(
resolvedUserId, configuration, bypassUserPolicyChecks);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
@@ -285,8 +281,9 @@
}
}
- void handleConfigurationInternalChangedOnHandlerThread() {
- // Configuration has changed, but each user may have a different view of the configuration.
+ void handleChangeOnHandlerThread() {
+ // Detector state has changed. Each user may have a different view of the configuration so
+ // no information is passed; each client must query what they're interested in.
// It's possible that this will cause unnecessary notifications but that shouldn't be a
// problem.
synchronized (mListeners) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 69284e3..328cf72 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,6 +17,8 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -94,6 +96,48 @@
*/
public interface TimeZoneDetectorStrategy extends Dumpable {
+ /**
+ * Adds a listener that will be triggered when something changes that could affect the result
+ * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
+ * includes the current user changing. This is exposed so that (indirect) users like SettingsUI
+ * can monitor for changes to data derived from {@link TimeZoneCapabilitiesAndConfig} and update
+ * the UI accordingly.
+ */
+ void addChangeListener(StateChangeListener listener);
+
+ /**
+ * Returns a {@link TimeZoneCapabilitiesAndConfig} object for the specified user.
+ *
+ * <p>The strategy is dependent on device state like current user, settings and device config.
+ * These updates are usually handled asynchronously, so callers should expect some delay between
+ * a change being made directly to services like settings and the strategy becoming aware of
+ * them. Changes made via {@link #updateConfiguration} will be visible immediately.
+ *
+ * @param userId the user ID to retrieve the information for
+ * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+ * policy restrictions that should apply to actual users can be ignored
+ */
+ TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+ @UserIdInt int userId, boolean bypassUserPolicyChecks);
+
+ /**
+ * Updates the configuration properties that control a device's time zone behavior.
+ *
+ * <p>This method returns {@code true} if the configuration was changed, {@code false}
+ * otherwise.
+ *
+ * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
+ * subsequent calls.
+ *
+ * @param userId the current user ID, supplied to make sure that the asynchronous process
+ * that happens when users switch is completed when the call is made
+ * @param configuration the configuration changes
+ * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+ * policy restrictions that should apply to actual users can be ignored
+ */
+ boolean updateConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration,
+ boolean bypassUserPolicyChecks);
+
/** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
@NonNull
TimeZoneState getTimeZoneState();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 18c8885..ecf25e9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -31,10 +31,10 @@
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.content.Context;
import android.os.Handler;
import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
@@ -46,6 +46,7 @@
import java.io.PrintWriter;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -68,16 +69,6 @@
public interface Environment {
/**
- * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
- * changes that could affect the content of {@link ConfigurationInternal}.
- * This is invoked during system server setup.
- */
- void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
-
- /** Returns the {@link ConfigurationInternal} for the current user. */
- @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
-
- /**
* Returns the device's currently configured time zone. May return an empty string.
*/
@NonNull String getDeviceTimeZone();
@@ -206,6 +197,22 @@
private final ReferenceWithHistory<ManualTimeZoneSuggestion> mLatestManualSuggestion =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+ @NonNull
+ private final ServiceConfigAccessor mServiceConfigAccessor;
+
+ /** The handler used for asynchronous operations triggered by this. */
+ @NonNull
+ private final Handler mStateChangeHandler;
+
+ @GuardedBy("this")
+ @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+
+ /**
+ * A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
+ * because it is relatively heavyweight to obtain and is used more often than it is expected to
+ * change. Because many operations are asynchronous, this value may be out of date but should
+ * be "eventually consistent".
+ */
@GuardedBy("this")
@NonNull
private ConfigurationInternal mCurrentConfigurationInternal;
@@ -229,29 +236,93 @@
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
*/
public static TimeZoneDetectorStrategyImpl create(
- @NonNull Context context, @NonNull Handler handler,
- @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+ @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
- Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor);
- return new TimeZoneDetectorStrategyImpl(environment);
+ Environment environment = new EnvironmentImpl();
+ return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, handler, environment);
}
@VisibleForTesting
- public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
+ public TimeZoneDetectorStrategyImpl(
+ @NonNull ServiceConfigAccessor serviceConfigAccessor,
+ @NonNull Handler handler, @NonNull Environment environment) {
mEnvironment = Objects.requireNonNull(environment);
+ mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
+ mStateChangeHandler = Objects.requireNonNull(handler);
// Start with telephony fallback enabled.
mTelephonyTimeZoneFallbackEnabled =
new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
synchronized (this) {
- mEnvironment.setConfigurationInternalChangeListener(
- this::handleConfigurationInternalChanged);
- mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
+ // Listen for config and user changes and get an initial snapshot of configuration.
+ StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
+ mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
+ mCurrentConfigurationInternal =
+ mServiceConfigAccessor.getCurrentUserConfigurationInternal();
}
}
@Override
+ public synchronized TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+ @UserIdInt int userId, boolean bypassUserPolicyChecks) {
+ ConfigurationInternal configurationInternal;
+ if (mCurrentConfigurationInternal.getUserId() == userId) {
+ // Use the cached snapshot we have.
+ configurationInternal = mCurrentConfigurationInternal;
+ } else {
+ // This is not a common case: It would be unusual to want the configuration for a user
+ // other than the "current" user, but it is supported because it is trivial to do so.
+ // Unlike the current user config, there's no cached copy to worry about so read it
+ // directly from mServiceConfigAccessor.
+ configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
+ }
+ return new TimeZoneCapabilitiesAndConfig(
+ configurationInternal.asCapabilities(bypassUserPolicyChecks),
+ configurationInternal.asConfiguration());
+ }
+
+ @Override
+ public synchronized boolean updateConfiguration(
+ @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration,
+ boolean bypassUserPolicyChecks) {
+
+ // Write-through
+ boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
+ userId, configuration, bypassUserPolicyChecks);
+
+ // The update above will trigger config update listeners asynchronously if they are needed,
+ // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
+ // wouldn't see the update. So, handle the cache update and notifications here. When the
+ // async update listener triggers it will find everything already up to date and do nothing.
+ if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
+ ConfigurationInternal configurationInternal =
+ mServiceConfigAccessor.getConfigurationInternal(userId);
+
+ // If the configuration actually changed, update the cached copy synchronously and do
+ // other necessary house-keeping / (async) listener notifications.
+ if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
+ mCurrentConfigurationInternal = configurationInternal;
+
+ String logMsg = "updateConfiguration:"
+ + " userId=" + userId
+ + ", configuration=" + configuration
+ + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
+ + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
+ logTimeZoneDebugInfo(logMsg);
+
+ handleConfigurationInternalChanged(logMsg);
+ }
+ }
+ return updateSuccessful;
+ }
+
+ @Override
+ public synchronized void addChangeListener(StateChangeListener listener) {
+ mStateChangeListeners.add(listener);
+ }
+
+ @Override
public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
Objects.requireNonNull(timeZoneId);
@@ -334,9 +405,8 @@
String timeZoneId = suggestion.getZoneId();
String cause = "Manual time suggestion received: suggestion=" + suggestion;
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ currentUserConfig.asCapabilities(bypassUserPolicyChecks);
if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
+ ": capabilities=" + capabilities
@@ -735,18 +805,37 @@
return findBestTelephonySuggestion();
}
- private synchronized void handleConfigurationInternalChanged() {
+ /**
+ * Handles a configuration change notification.
+ */
+ private synchronized void handleConfigurationInternalMaybeChanged() {
ConfigurationInternal currentUserConfig =
- mEnvironment.getCurrentUserConfigurationInternal();
- String logMsg = "handleConfigurationInternalChanged:"
- + " oldConfiguration=" + mCurrentConfigurationInternal
- + ", newConfiguration=" + currentUserConfig;
- logTimeZoneDebugInfo(logMsg);
- mCurrentConfigurationInternal = currentUserConfig;
+ mServiceConfigAccessor.getCurrentUserConfigurationInternal();
- // The configuration change may have changed available suggestions or the way suggestions
- // are used, so re-run detection.
- doAutoTimeZoneDetection(currentUserConfig, logMsg);
+ // The configuration may not actually have changed so check before doing anything.
+ if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
+ String logMsg = "handleConfigurationInternalMaybeChanged:"
+ + " oldConfiguration=" + mCurrentConfigurationInternal
+ + ", newConfiguration=" + currentUserConfig;
+ logTimeZoneDebugInfo(logMsg);
+
+ mCurrentConfigurationInternal = currentUserConfig;
+
+ handleConfigurationInternalChanged(logMsg);
+ }
+ }
+
+ /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+ @GuardedBy("this")
+ private void handleConfigurationInternalChanged(@NonNull String logMsg) {
+ // Notify change listeners asynchronously.
+ for (StateChangeListener listener : mStateChangeListeners) {
+ mStateChangeHandler.post(listener::onChange);
+ }
+
+ // The configuration change may have changed available suggestions or the way
+ // suggestions are used, so re-run detection.
+ doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
}
/**
@@ -760,8 +849,7 @@
ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
final boolean bypassUserPolicyChecks = false;
ipw.println("[Capabilities="
- + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
- + "]");
+ + mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
ipw.println("mEnvironment.getDeviceTimeZoneConfidence()="
+ mEnvironment.getDeviceTimeZoneConfidence());
@@ -824,6 +912,11 @@
return mTelephonyTimeZoneFallbackEnabled.getValue();
}
+ @VisibleForTesting
+ public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+ return mCurrentConfigurationInternal;
+ }
+
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
index e7d16c8..5eeafc1 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
@@ -20,9 +20,9 @@
import android.annotation.NonNull;
import android.os.SystemClock;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
import java.time.Duration;
import java.util.Objects;
@@ -35,7 +35,7 @@
extends LocationTimeZoneProviderController.Environment {
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
- @NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener;
+ @NonNull private final StateChangeListener mConfigurationInternalChangeListener;
LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
@NonNull ServiceConfigAccessor serviceConfigAccessor,
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index cd0096b..c192057 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1752,6 +1752,8 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TRUST_LISTENER)
@Override
public boolean isTrustUsuallyManaged(int userId) {
+ super.isTrustUsuallyManaged_enforcePermission();
+
return isTrustUsuallyManagedInternal(userId);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6a35533..7dc4f97 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1549,8 +1549,9 @@
try {
mReply.sendResult(null);
} catch (RemoteException e) {
- Binder.restoreCallingIdentity(ident);
Slog.d(TAG, "failed to send callback!", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
mReply = null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17a9a63..a9d8d79 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -812,7 +812,6 @@
StartingData mStartingData;
WindowState mStartingWindow;
StartingSurfaceController.StartingSurface mStartingSurface;
- boolean startingDisplayed;
boolean startingMoved;
/** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@
/** Whether the input to this activity will be dropped during the current playing animation. */
private boolean mIsInputDroppedForAnimation;
- /**
- * If it is non-null, it requires all activities who have the same starting data to be drawn
- * to remove the starting window.
- * TODO(b/189385912): Remove starting window related fields after migrating them to task.
- */
- private StartingData mSharedStartingData;
-
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1201,14 +1193,11 @@
pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
pw.print(" mIsExiting="); pw.println(mIsExiting);
}
- if (mSharedStartingData != null) {
- pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
- }
- if (mStartingWindow != null || mStartingSurface != null
- || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+ if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+ || startingMoved || mVisibleSetFromTransferredStartingWindow) {
pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
pw.print(" startingSurface="); pw.print(mStartingSurface);
- pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+ pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
pw.print(" startingMoved="); pw.print(startingMoved);
pw.println(" mVisibleSetFromTransferredStartingWindow="
+ mVisibleSetFromTransferredStartingWindow);
@@ -2687,13 +2676,23 @@
}
}
+ boolean isStartingWindowDisplayed() {
+ final StartingData data = mStartingData != null ? mStartingData : task != null
+ ? task.mSharedStartingData : null;
+ return data != null && data.mIsDisplayed;
+ }
+
/** Called when the starting window is added to this activity. */
void attachStartingWindow(@NonNull WindowState startingWindow) {
startingWindow.mStartingData = mStartingData;
mStartingWindow = startingWindow;
- // The snapshot type may have called associateStartingDataWithTask().
- if (mStartingData != null && mStartingData.mAssociatedTask != null) {
- attachStartingSurfaceToAssociatedTask();
+ if (mStartingData != null) {
+ if (mStartingData.mAssociatedTask != null) {
+ // The snapshot type may have called associateStartingDataWithTask().
+ attachStartingSurfaceToAssociatedTask();
+ } else if (isEmbedded()) {
+ associateStartingWindowWithTaskIfNeeded();
+ }
}
}
@@ -2708,11 +2707,7 @@
/** Called when the starting window is not added yet but its data is known to fill the task. */
private void associateStartingDataWithTask() {
mStartingData.mAssociatedTask = task;
- task.forAllActivities(r -> {
- if (r.mVisibleRequested && !r.firstWindowDrawn) {
- r.mSharedStartingData = mStartingData;
- }
- });
+ task.mSharedStartingData = mStartingData;
}
/** Associates and attaches an added starting window to the current task. */
@@ -2743,10 +2738,8 @@
void removeStartingWindowAnimation(boolean prepareAnimation) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
- if (mSharedStartingData != null) {
- mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
- r.mSharedStartingData = null;
- });
+ if (task != null) {
+ task.mSharedStartingData = null;
}
if (mStartingWindow == null) {
if (mStartingData != null) {
@@ -2774,7 +2767,6 @@
mStartingData = null;
mStartingSurface = null;
mStartingWindow = null;
- startingDisplayed = false;
if (surface == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
+ "startingSurface==null, couldn't remove");
@@ -4247,7 +4239,7 @@
* @return {@code true} if starting window is in app's hierarchy.
*/
boolean hasStartingWindow() {
- if (startingDisplayed || mStartingData != null) {
+ if (mStartingData != null) {
return true;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4345,10 +4337,7 @@
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
mStartingSurface = fromActivity.mStartingSurface;
- startingDisplayed = fromActivity.startingDisplayed;
- fromActivity.startingDisplayed = false;
mStartingWindow = tStartingWindow;
reportedVisible = fromActivity.reportedVisible;
fromActivity.mStartingData = null;
@@ -4414,7 +4403,6 @@
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Moving pending starting from %s to %s", fromActivity, this);
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
fromActivity.mStartingData = null;
fromActivity.startingMoved = true;
scheduleAddStartingWindow();
@@ -6526,14 +6514,11 @@
// Remove starting window directly if is in a pure task. Otherwise if it is associated with
// a task (e.g. nested task fragment), then remove only if all visible windows in the task
// are drawn.
- final Task associatedTask =
- mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+ final Task associatedTask = task.mSharedStartingData != null ? task : null;
if (associatedTask == null) {
removeStartingWindow();
- } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
- // Don't block starting window removal if an Activity can't be a starting window
- // target.
- && r.mSharedStartingData != null) == null) {
+ } else if (associatedTask.getActivity(
+ r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -6748,7 +6733,6 @@
if (mLastTransactionSequence != mWmService.mTransactionSequence) {
mLastTransactionSequence = mWmService.mTransactionSequence;
mNumDrawnWindows = 0;
- startingDisplayed = false;
// There is the main base application window, even if it is exiting, wait for it
mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6792,9 +6776,9 @@
isInterestingAndDrawn = true;
}
}
- } else if (w.isDrawn()) {
+ } else if (mStartingData != null && w.isDrawn()) {
// The starting window for this container is drawn.
- startingDisplayed = true;
+ mStartingData.mIsDisplayed = true;
}
}
@@ -7552,7 +7536,8 @@
ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
+ ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
- this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+ this, reportedVisible, okToDisplay(), okToAnimate(),
+ isStartingWindowDisplayed());
// clean up thumbnail window
if (mThumbnail != null) {
@@ -9650,7 +9635,7 @@
if (mStartingWindow != null) {
mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
}
- proto.write(STARTING_DISPLAYED, startingDisplayed);
+ proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
proto.write(STARTING_MOVED, startingMoved);
proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index d42a74f..7c0d658 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -40,6 +40,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.OptionalInt;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -332,9 +333,10 @@
String criticalEvents =
CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
- null /* processCpuTracker */, null /* lastPids */, nativePids,
+ null /* processCpuTracker */, null /* lastPids */,
+ CompletableFuture.completedFuture(nativePids),
null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents,
- null/* AnrLatencyTracker */);
+ Runnable::run, null/* AnrLatencyTracker */);
if (tracesFile != null) {
tracesFile.renameTo(
new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c2e87e6..d1bda22 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1203,11 +1203,11 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.startingDisplayed,
+ activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+ if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
return false;
}
if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1cb83f12..d1d39af 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -243,16 +243,22 @@
} else if (currentActivity.isRootOfTask()) {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
- // TODO: Also check Task.shouldUpRecreateTaskLocked() for prevActivity logic
prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
removedWindowContainer = currentTask;
- prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevTask.isActivityTypeHome()) {
- backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+ // If it reaches the top activity, we will check the below task from parent.
+ // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
+ // or set the type to proper value when it's return to home or another task.
+ if (prevTask == null || prevTask.inMultiWindowMode()) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
- backType = BackNavigationInfo.TYPE_CROSS_TASK;
+ prevActivity = prevTask.getTopNonFinishingActivity();
+ if (prevTask.isActivityTypeHome()) {
+ backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+ mShowWallpaper = true;
+ } else {
+ backType = BackNavigationInfo.TYPE_CROSS_TASK;
+ }
}
- mShowWallpaper = true;
}
infoBuilder.setType(backType);
@@ -263,8 +269,10 @@
removedWindowContainer,
BackNavigationInfo.typeToString(backType));
- // For now, we only animate when going home.
- boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ // For now, we only animate when going home and cross task.
+ boolean prepareAnimation =
+ (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ || backType == BackNavigationInfo.TYPE_CROSS_TASK)
&& adapter != null;
// Only prepare animation if no leash has been created (no animation is running).
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index e7ab63e..13a1cb6 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -216,14 +216,10 @@
return;
}
- if (container != null) {
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- } else {
- t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
- }
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
t.setAlpha(d.mDimLayer, alpha);
t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
@@ -231,32 +227,6 @@
}
/**
- * Finish a dim started by dimAbove in the case there was no call to dimAbove.
- *
- * @param t A Transaction in which to finish the dim.
- */
- void stopDim(SurfaceControl.Transaction t) {
- if (mDimState != null) {
- t.hide(mDimState.mDimLayer);
- mDimState.isVisible = false;
- mDimState.mDontReset = false;
- }
- }
-
- /**
- * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
- * remove this effect. If the Dim can be assosciated with a particular child of the host
- * consider using the other variant of dimAbove which ties the Dim lifetime to the child
- * lifetime more explicitly.
- *
- * @param t A transaction in which to apply the Dim.
- * @param alpha The alpha at which to Dim.
- */
- void dimAbove(SurfaceControl.Transaction t, float alpha) {
- dim(t, null, 1, alpha, 0);
- }
-
- /**
* Place a dim above the given container, which should be a child of the host container.
* for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
* and the child should call dimAbove again to request the Dim to continue.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8723994..7bcea36 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -75,11 +75,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -119,11 +115,9 @@
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
-import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowLayout;
@@ -318,7 +312,6 @@
private int mLastAppearance;
private int mLastBehavior;
private int mLastRequestedVisibleTypes = Type.defaultVisible();
- private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
private LetterboxDetails[] mLastLetterboxDetails;
@@ -1341,90 +1334,6 @@
*/
int selectAnimation(WindowState win, int transit) {
ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);
- if (win == mStatusBar) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_top_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_top_enter;
- }
- } else if (win == mNavigationBar) {
- if (win.getAttrs().windowAnimations != 0) {
- return ANIMATION_STYLEABLE;
- }
- // This can be on either the bottom or the right or the left.
- if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
- return R.anim.dock_bottom_exit_keyguard;
- } else {
- return R.anim.dock_bottom_exit;
- }
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_bottom_enter;
- }
- } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_right_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_right_enter;
- }
- } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_left_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_left_enter;
- }
- }
- } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
- || win == mExtraNavBarAlt) {
- if (win.getAttrs().windowAnimations != 0) {
- return ANIMATION_STYLEABLE;
- }
-
- int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition;
-
- boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE;
- boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW;
-
- switch (pos) {
- case ALT_BAR_LEFT:
- if (isExitOrHide) {
- return R.anim.dock_left_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_left_enter;
- }
- break;
- case ALT_BAR_RIGHT:
- if (isExitOrHide) {
- return R.anim.dock_right_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_right_enter;
- }
- break;
- case ALT_BAR_BOTTOM:
- if (isExitOrHide) {
- return R.anim.dock_bottom_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_bottom_enter;
- }
- break;
- case ALT_BAR_TOP:
- if (isExitOrHide) {
- return R.anim.dock_top_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_top_enter;
- }
- break;
- }
- }
if (transit == TRANSIT_PREVIEW_DONE) {
if (win.hasAppShownWindows()) {
@@ -2246,35 +2155,16 @@
mService.mInputManager.setSystemUiLightsOut(
isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
}
- final InsetsVisibilities requestedVisibilities =
- mLastRequestedVisibleTypes == requestedVisibleTypes
- ? mRequestedVisibilities
- : toInsetsVisibilities(requestedVisibleTypes);
mLastAppearance = appearance;
mLastBehavior = behavior;
mLastRequestedVisibleTypes = requestedVisibleTypes;
- mRequestedVisibilities = requestedVisibilities;
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
mLastLetterboxDetails = letterboxDetails;
callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
- requestedVisibilities, focusedApp, letterboxDetails));
- }
-
- // TODO (253420890): Remove this when removing mRequestedVisibilities.
- private static InsetsVisibilities toInsetsVisibilities(@InsetsType int requestedVisibleTypes) {
- final @InsetsType int defaultVisibleTypes = WindowInsets.Type.defaultVisible();
- final InsetsVisibilities insetsVisibilities = new InsetsVisibilities();
- for (@InternalInsetsType int i = InsetsState.SIZE - 1; i >= 0; i--) {
- @InsetsType int type = InsetsState.toPublicType(i);
- if ((type & (requestedVisibleTypes ^ defaultVisibleTypes)) != 0) {
- // We only set the visibility if it is different from the default one.
- insetsVisibilities.setVisibility(i, (type & requestedVisibleTypes) != 0);
- }
- }
- return insetsVisibilities;
+ requestedVisibleTypes, focusedApp, letterboxDetails));
}
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a8d13c5..eaa08fd 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1578,7 +1578,9 @@
false /* forceRelayout */);
} else {
// Revert the rotation to our saved value if we transition from HALF_FOLDED.
- mRotation = mHalfFoldSavedRotation;
+ if (mHalfFoldSavedRotation != -1) {
+ mRotation = mHalfFoldSavedRotation;
+ }
// Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
// so we will override USER_ROTATION_LOCKED and allow a rotation).
mService.updateRotation(false /* alwaysSendConfiguration */,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index f11c2a7..dcb7fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -604,7 +604,10 @@
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
}
if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(false /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(false /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
} catch (RemoteException ex) {
@@ -619,7 +622,10 @@
void showLockTaskToast() {
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
try {
- getStatusBarService().showPinningEscapeToast();
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEscapeToast();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to send pinning escape toast", e);
}
@@ -727,7 +733,10 @@
// When lock task starts, we disable the status bars.
try {
if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(true /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(true /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(lockTaskModeState);
mLockTaskModeState = lockTaskModeState;
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 9c85bc0..1cc1a57 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -20,13 +20,12 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import android.app.ActivityManager.RunningTaskInfo;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
-import java.util.Comparator;
-import java.util.Iterator;
+import java.util.ArrayList;
import java.util.List;
-import java.util.TreeSet;
import java.util.function.Consumer;
/**
@@ -39,15 +38,13 @@
static final int FLAG_CROSS_USERS = 1 << 2;
static final int FLAG_KEEP_INTENT_EXTRA = 1 << 3;
- // Comparator to sort by last active time (descending)
- private static final Comparator<Task> LAST_ACTIVE_TIME_COMPARATOR =
- (o1, o2) -> {
- return o1.lastActiveTime == o2.lastActiveTime
- ? Integer.signum(o2.mTaskId - o1.mTaskId) :
- Long.signum(o2.lastActiveTime - o1.lastActiveTime);
- };
-
- private final TreeSet<Task> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+ // Tasks are sorted in order {focusedVisibleTasks, visibleTasks, invisibleTasks}.
+ private final ArrayList<Task> mTmpSortedTasks = new ArrayList<>();
+ // mTmpVisibleTasks, mTmpInvisibleTasks and mTmpFocusedTasks are sorted from top
+ // to bottom.
+ private final ArrayList<Task> mTmpVisibleTasks = new ArrayList<>();
+ private final ArrayList<Task> mTmpInvisibleTasks = new ArrayList<>();
+ private final ArrayList<Task> mTmpFocusedTasks = new ArrayList<>();
private int mCallingUid;
private int mUserId;
@@ -65,8 +62,6 @@
return;
}
- // Gather all of the tasks across all of the tasks, and add them to the sorted set
- mTmpSortedSet.clear();
mCallingUid = callingUid;
mUserId = UserHandle.getUserId(callingUid);
mCrossUser = (flags & FLAG_CROSS_USERS) == FLAG_CROSS_USERS;
@@ -77,19 +72,64 @@
mRecentTasks = recentTasks;
mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
- root.forAllLeafTasks(this, false /* traverseTopToBottom */);
+ if (root instanceof RootWindowContainer) {
+ ((RootWindowContainer) root).forAllDisplays(dc -> {
+ final Task focusedTask = dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null;
+ if (focusedTask != null) {
+ mTmpFocusedTasks.add(focusedTask);
+ }
+ processTaskInWindowContainer(dc);
+ });
+ } else {
+ final DisplayContent dc = root.getDisplayContent();
+ final Task focusedTask = dc != null
+ ? (dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null)
+ : null;
+ // May not be include focusedTask if root is DisplayArea.
+ final boolean rootContainsFocusedTask = focusedTask != null
+ && focusedTask.isDescendantOf(root);
+ if (rootContainsFocusedTask) {
+ mTmpFocusedTasks.add(focusedTask);
+ }
+ processTaskInWindowContainer(root);
+ }
+
+ final int visibleTaskCount = mTmpVisibleTasks.size();
+ for (int i = 0; i < mTmpFocusedTasks.size(); i++) {
+ final Task focusedTask = mTmpFocusedTasks.get(i);
+ final boolean containsFocusedTask = mTmpVisibleTasks.remove(focusedTask);
+ if (containsFocusedTask) {
+ // Put the visible focused task at the first position.
+ mTmpSortedTasks.add(focusedTask);
+ }
+ }
+ if (!mTmpVisibleTasks.isEmpty()) {
+ mTmpSortedTasks.addAll(mTmpVisibleTasks);
+ }
+ if (!mTmpInvisibleTasks.isEmpty()) {
+ mTmpSortedTasks.addAll(mTmpInvisibleTasks);
+ }
// Take the first {@param maxNum} tasks and create running task infos for them
- final Iterator<Task> iter = mTmpSortedSet.iterator();
- while (iter.hasNext()) {
- if (maxNum == 0) {
- break;
- }
-
- final Task task = iter.next();
- list.add(createRunningTaskInfo(task));
- maxNum--;
+ final int size = Math.min(maxNum, mTmpSortedTasks.size());
+ final long now = SystemClock.elapsedRealtime();
+ for (int i = 0; i < size; i++) {
+ final Task task = mTmpSortedTasks.get(i);
+ // Override the last active to current time for the visible tasks because the visible
+ // tasks can be considered to be currently active, the values are descending as
+ // the item order.
+ final long visibleActiveTime = i < visibleTaskCount ? now + size - i : -1;
+ list.add(createRunningTaskInfo(task, visibleActiveTime));
}
+
+ mTmpFocusedTasks.clear();
+ mTmpVisibleTasks.clear();
+ mTmpInvisibleTasks.clear();
+ mTmpSortedTasks.clear();
+ }
+
+ private void processTaskInWindowContainer(WindowContainer wc) {
+ wc.forAllLeafTasks(this, true /* traverseTopToBottom */);
}
@Override
@@ -117,25 +157,20 @@
// home & recent tasks
return;
}
-
if (task.isVisible()) {
- // For the visible task, update the last active time so that it can be used to determine
- // the order of the tasks (it may not be set for newly created tasks)
- task.touchActiveTime();
- if (!task.isFocused()) {
- // TreeSet doesn't allow the same value and make sure this task is lower than the
- // focused one.
- task.lastActiveTime -= mTmpSortedSet.size();
- }
+ mTmpVisibleTasks.add(task);
+ } else {
+ mTmpInvisibleTasks.add(task);
}
-
- mTmpSortedSet.add(task);
}
/** Constructs a {@link RunningTaskInfo} from a given {@param task}. */
- private RunningTaskInfo createRunningTaskInfo(Task task) {
+ private RunningTaskInfo createRunningTaskInfo(Task task, long visibleActiveTime) {
final RunningTaskInfo rti = new RunningTaskInfo();
task.fillTaskInfo(rti, !mKeepIntentExtra);
+ if (visibleActiveTime > 0) {
+ rti.lastActiveTime = visibleActiveTime;
+ }
// Fill in some deprecated values
rti.id = rti.taskId;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343..300a894 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@
*/
Task mAssociatedTask;
+ /** Whether the starting window is drawn. */
+ boolean mIsDisplayed;
+
protected StartingData(WindowManagerService service, int typeParams) {
mService = service;
mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 435ab97..d06f271 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@
int mLockTaskUid = -1; // The uid of the application that called startLockTask().
+ /**
+ * If non-null, the starting window should cover the associated task. It is assigned when the
+ * parent activity of starting window is put in a partial area of the task. This field will be
+ * cleared when all visible activities in this task are drawn.
+ */
+ StartingData mSharedStartingData;
+
/** The process that had previously hosted the root activity of this task.
* Used to know that we should try harder to keep this process around, in case the
* user wants to return to it. */
@@ -3673,6 +3680,9 @@
if (mRootProcess != null) {
pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
}
+ if (mSharedStartingData != null) {
+ pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+ }
pw.print(prefix); pw.print("taskId=" + mTaskId);
pw.println(" rootTaskId=" + getRootTaskId());
pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 230b760..063f0db 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1891,10 +1891,10 @@
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
- // There may be a trampoline activity without window on top of the existing task
- // which is moving to front. Exclude the finishing activity so the window of next
- // activity can be chosen to create the animation target.
- ? getTopNonFinishingActivity()
+ // There may be a launching (e.g. trampoline or embedded) activity without a window
+ // on top of the existing task which is moving to front. Exclude finishing activity
+ // so the window of next activity can be chosen to create the animation target.
+ ? getActivity(r -> !r.finishing && r.hasChild())
: getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3590e9c2..7415376 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1547,6 +1547,12 @@
return mTransitionController.mTransitionMetricsReporter;
}
+ @Override
+ public IBinder getApplyToken() {
+ enforceTaskPermission("getApplyToken()");
+ return SurfaceControl.Transaction.getDefaultApplyToken();
+ }
+
/** Whether the configuration changes are important to report back to an organizer. */
static boolean configurationsAreEqualForOrganizer(
Configuration newConfig, @Nullable Configuration oldConfig) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f30c435..0168d50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1838,8 +1838,8 @@
* @return {@code true} if one or more windows have been displayed, else false.
*/
boolean hasAppShownWindows() {
- return mActivityRecord != null
- && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+ return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+ || mActivityRecord.isStartingWindowDisplayed());
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5c0557f..a0ba8fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- final ActivityRecord activity = mWin.mActivityRecord;
- if (activity != null) {
- if (mWin == activity.mStartingWindow) {
- activity.startingDisplayed = false;
- }
- }
-
if (mSurfaceController == null) {
return;
}
@@ -602,11 +595,17 @@
return true;
}
- final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
- if (isEntrance && isImeWindow) {
+ if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
mWin.getDisplayContent().adjustForImeIfNeeded();
- mWin.setDisplayLayoutNeeded();
- mService.mWindowPlacerLocked.requestTraversal();
+ if (isEntrance) {
+ mWin.setDisplayLayoutNeeded();
+ mService.mWindowPlacerLocked.requestTraversal();
+ }
+ }
+
+ if (mWin.mControllableInsetProvider != null) {
+ // All our animations should be driven by the insets control target.
+ return false;
}
// Only apply an animation if the display isn't frozen. If it is
@@ -654,14 +653,10 @@
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
- } else if (!isImeWindow) {
+ } else {
mWin.cancelAnimation();
}
- if (!isEntrance && isImeWindow) {
- mWin.getDisplayContent().adjustForImeIfNeeded();
- }
-
return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 40412db..321f022 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
@@ -155,5 +156,14 @@
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
return cancelTransport;
}
+
+ @Override
+ public ICancellationSignal clearCredentialSession(
+ IClearCredentialSessionCallback callback, String callingPackage) {
+ // TODO: implement.
+ Log.i(TAG, "clearCredentialSession");
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ return cancelTransport;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 5b7b8f4..4d92b7f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -25,6 +25,7 @@
import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
+import static com.android.server.am.BroadcastQueueTest.withPriority;
import static com.google.common.truth.Truth.assertThat;
@@ -46,8 +47,10 @@
import android.os.Bundle;
import android.os.BundleMerger;
import android.os.HandlerThread;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import androidx.test.filters.SmallTest;
@@ -59,6 +62,8 @@
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
@@ -283,7 +288,7 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
queue.setProcessCached(false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -305,12 +310,12 @@
// enqueue a bg-priority broadcast then a fg-priority one
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
- queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(timezoneRecord, 0);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -339,9 +344,9 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
- List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1);
+ List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 1);
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -363,7 +368,7 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
@@ -377,6 +382,55 @@
}
/**
+ * Confirm that we always prefer running pending items marked as "urgent",
+ * then "normal", then "offload", dispatching by the relative ordering
+ * within each of those clustering groups.
+ */
+ @Test
+ public void testMakeActiveNextPending() {
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
+
+ // To maximize test coverage, dump current state; we're not worried
+ // about the actual output, just that we don't crash
+ queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED);
+ queue.dumpLocked(SystemClock.uptimeMillis(),
+ new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream())));
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_AIRPLANE_MODE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ assertTrue(queue.isEmpty());
+ }
+
+ /**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index de59603..fd605f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -503,6 +503,11 @@
return ai;
}
+ static ResolveInfo withPriority(ResolveInfo info, int priority) {
+ info.priority = priority;
+ return info;
+ }
+
static ResolveInfo makeManifestReceiver(String packageName, String name) {
return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
}
@@ -1634,4 +1639,22 @@
waitForIdle();
verify(mAms, never()).enqueueOomAdjTargetLocked(any());
}
+
+ /**
+ * Verify that expected events are triggered when a broadcast is finished.
+ */
+ @Test
+ public void testNotifyFinished() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final BroadcastRecord record = makeBroadcastRecord(intent, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)));
+ enqueueBroadcast(record);
+
+ waitForIdle();
+ verify(mAms).notifyBroadcastFinishedLocked(eq(record));
+ verify(mAms).addBroadcastStatLocked(eq(Intent.ACTION_TIMEZONE_CHANGED), eq(PACKAGE_RED),
+ eq(1), eq(0), anyLong());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 11573c5..05ed0e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,9 +24,10 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.isPrioritized;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -99,6 +100,16 @@
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0))));
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10))));
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
+
+ assertArrayEquals(new int[] {-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
+ assertArrayEquals(new int[] {-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
+ assertArrayEquals(new int[] {-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
}
@Test
@@ -111,6 +122,17 @@
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 10),
createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+ assertArrayEquals(new int[] {-1,-1,-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+ assertArrayEquals(new int[] {-1,-1,-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
}
@Test
@@ -123,6 +145,19 @@
createResolveInfo(PACKAGE1, getAppId(1), 0),
createResolveInfo(PACKAGE2, getAppId(2), 0),
createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+ assertArrayEquals(new int[] {0,1,2},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+ assertArrayEquals(new int[] {0,0,2,3,3},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
}
@Test
@@ -543,4 +578,9 @@
private static int getAppId(int i) {
return Process.FIRST_APPLICATION_UID + i;
}
+
+ private static boolean isPrioritized(List<Object> receivers) {
+ return BroadcastRecord.isPrioritized(
+ calculateBlockedUntilTerminalCount(receivers, false), false);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index 0b84a60..e6ab73a 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -49,6 +49,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
/**
@@ -62,6 +63,7 @@
private AnrHelper mAnrHelper;
private ProcessRecord mAnrApp;
+ private ExecutorService mExecutorService;
@Rule
public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
@@ -88,7 +90,9 @@
return mServiceThreadRule.getThread().getThreadHandler();
}
}, mServiceThreadRule.getThread());
- mAnrHelper = new AnrHelper(service);
+ mExecutorService = mock(ExecutorService.class);
+
+ mAnrHelper = new AnrHelper(service, mExecutorService);
});
}
@@ -119,7 +123,7 @@
verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
- eq(parentProcess), eq(aboveSystem), eq(timeoutRecord),
+ eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), eq(mExecutorService),
eq(false) /* onlyDumpSelf */);
}
@@ -133,7 +137,7 @@
processingLatch.await();
return null;
}).when(mAnrApp.mErrorState).appNotResponding(anyString(), any(), any(), any(),
- anyBoolean(), any(), anyBoolean());
+ anyBoolean(), any(), any(), anyBoolean());
final ApplicationInfo appInfo = new ApplicationInfo();
final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
"annotation");
@@ -155,6 +159,7 @@
processingLatch.countDown();
// There is only one ANR reported.
verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS).only()).appNotResponding(
- anyString(), any(), any(), any(), anyBoolean(), any(), anyBoolean());
+ anyString(), any(), any(), any(), anyBoolean(), any(), eq(mExecutorService),
+ anyBoolean());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index 70519e4..9cada91 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -45,6 +45,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.concurrent.ExecutorService;
/**
* Build/Install/Run:
@@ -58,6 +59,7 @@
private ProcessRecord mProcessRecord;
private ProcessErrorStateRecord mProcessErrorState;
+ private ExecutorService mExecutorService;
@BeforeClass
public static void setUpOnce() throws Exception {
@@ -109,6 +111,7 @@
runWithDexmakerShareClassLoader(() -> {
mProcessRecord = new ProcessRecord(sService, sContext.getApplicationInfo(),
"name", 12345);
+ mExecutorService = mock(ExecutorService.class);
mProcessErrorState = spy(mProcessRecord.mErrorState);
doNothing().when(mProcessErrorState).startAppProblemLSP();
doReturn(false).when(mProcessErrorState).isSilentAnr();
@@ -194,11 +197,11 @@
assertTrue(mProcessRecord.isKilled());
}
- private static void appNotResponding(ProcessErrorStateRecord processErrorState,
+ private void appNotResponding(ProcessErrorStateRecord processErrorState,
String annotation) {
TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(annotation);
processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
null /* parentShortComponentName */, null /* parentProcess */,
- false /* aboveSystem */, timeoutRecord, false /* onlyDumpSelf */);
+ false /* aboveSystem */, timeoutRecord, mExecutorService, false /* onlyDumpSelf */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index fe92a1d..935d1d8 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -24,7 +24,6 @@
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.UserHandle.USER_SYSTEM;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -403,7 +402,7 @@
verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+ verifySystemUserVisibilityChangedNotified(/* visible= */ false);
}
@Test
@@ -424,7 +423,7 @@
verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+ verifySystemUserVisibilityChangedNotified(/* visible= */ false);
}
@Test
@@ -531,7 +530,7 @@
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
mUserController.getRunningUsersLU());
- verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+ verifySystemUserVisibilityChangedNotified(/* visible= */ false);
}
/**
@@ -964,8 +963,8 @@
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
}
- private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
- verify(mInjector).onUserStarting(userId, visible);
+ private void verifySystemUserVisibilityChangedNotified(boolean visible) {
+ verify(mInjector).notifySystemUserVisibilityChanged(visible);
}
// Should be public to allow mocking
@@ -1108,6 +1107,11 @@
void onUserStarting(@UserIdInt int userId, boolean visible) {
Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
}
+
+ @Override
+ void notifySystemUserVisibilityChanged(boolean visible) {
+ Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")");
+ }
}
private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 675f0e3..608eeec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -620,6 +620,20 @@
verify(mCallback).onClientFinished(any(), eq(true));
}
+ @Test
+ public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ client.onPowerPressed();
+ mLooper.dispatchAll();
+
+ verify(mCallback, never()).onClientFinished(any(), eq(true));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 2b069e3..1fe267f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -57,7 +57,7 @@
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
@@ -1081,10 +1081,10 @@
public void testUdfpsListenerGetsRegistered() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
- verify(mStatusBarMock, never()).setUdfpsHbmListener(any());
+ verify(mStatusBarMock, never()).setUdfpsRefreshRateCallback(any());
director.onBootCompleted();
- verify(mStatusBarMock).setUdfpsHbmListener(eq(director.getUdpfsObserver()));
+ verify(mStatusBarMock).setUdfpsRefreshRateCallback(eq(director.getUdpfsObserver()));
}
@Test
@@ -1093,10 +1093,9 @@
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
director.start(createMockSensorManager());
director.onBootCompleted();
- ArgumentCaptor<IUdfpsHbmListener> captor =
- ArgumentCaptor.forClass(IUdfpsHbmListener.class);
- verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
- IUdfpsHbmListener hbmListener = captor.getValue();
+ ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+ ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+ verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
// Should be no vote initially
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 3f55f1b..ec61b87 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -20,6 +20,7 @@
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
+import android.content.pm.UserPackage;
import androidx.test.runner.AndroidJUnit4;
@@ -54,7 +55,7 @@
addPackage(target(otherTarget), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
- final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+ final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
// The result should be the same for every time
assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
@@ -67,7 +68,7 @@
addPackage(target(TARGET), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
- final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+ final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */);
expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
@@ -101,7 +102,7 @@
addPackage(overlay(OVERLAY, TARGET), USER);
configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
- final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+ final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -133,7 +134,7 @@
addPackage(target(TARGET), USER);
addPackage(overlay(OVERLAY, TARGET), USER);
- final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+ final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
final Consumer<ConfigState> setOverlay = (state -> {
configureSystemOverlay(OVERLAY, state, 0 /* priority */);
@@ -185,7 +186,7 @@
configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 1 /* priority */);
- final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+ final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -230,7 +231,7 @@
configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */);
configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */);
- final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+ final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index f69141d..ab52928 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -22,7 +22,6 @@
import static android.os.OverlayablePolicy.CONFIG_SIGNATURE;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -30,7 +29,7 @@
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
-import android.util.Pair;
+import android.content.pm.UserPackage;
import androidx.test.runner.AndroidJUnit4;
@@ -66,7 +65,7 @@
@Test
public void testGetOverlayInfo() throws Exception {
installAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -79,11 +78,11 @@
@Test
public void testGetOverlayInfosForTarget() throws Exception {
installAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY2, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY3, TARGET), USER2,
- Set.of(new PackageAndUser(OVERLAY3, USER2), new PackageAndUser(TARGET, USER2)));
+ Set.of(UserPackage.of(USER2, OVERLAY3), UserPackage.of(USER2, TARGET)));
final OverlayManagerServiceImpl impl = getImpl();
final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
@@ -107,13 +106,13 @@
@Test
public void testGetOverlayInfosForUser() throws Exception {
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY2, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY3, TARGET2), USER,
- Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET2, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET2)));
final OverlayManagerServiceImpl impl = getImpl();
final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -138,11 +137,11 @@
@Test
public void testPriority() throws Exception {
installAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY2, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY3, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET)));
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -152,15 +151,15 @@
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertEquals(impl.setLowestPriority(IDENTIFIER3, USER),
- Optional.of(new PackageAndUser(TARGET, USER)));
+ Optional.of(UserPackage.of(USER, TARGET)));
assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
- Optional.of(new PackageAndUser(TARGET, USER)));
+ Optional.of(UserPackage.of(USER, TARGET)));
assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
}
@@ -170,47 +169,47 @@
assertNull(impl.getOverlayInfo(IDENTIFIER, USER));
installAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
assertState(STATE_DISABLED, IDENTIFIER, USER);
assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
assertState(STATE_ENABLED, IDENTIFIER, USER);
// target upgrades do not change the state of the overlay
upgradeAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)),
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)),
+ Set.of(UserPackage.of(USER, TARGET)));
assertState(STATE_ENABLED, IDENTIFIER, USER);
uninstallAndAssert(TARGET, USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
assertState(STATE_ENABLED, IDENTIFIER, USER);
}
@Test
public void testOnOverlayPackageUpgraded() throws Exception {
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
upgradeAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)),
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)),
+ Set.of(UserPackage.of(USER, TARGET)));
// upgrade to a version where the overlay has changed its target
upgradeAndAssert(overlay(OVERLAY, TARGET2), USER,
- Set.of(new PackageAndUser(TARGET, USER)),
- Set.of(new PackageAndUser(TARGET, USER),
- new PackageAndUser(TARGET2, USER)));
+ Set.of(UserPackage.of(USER, TARGET)),
+ Set.of(UserPackage.of(USER, TARGET),
+ UserPackage.of(USER, TARGET2)));
}
@Test
@@ -222,10 +221,10 @@
// request succeeded, and there was a change that needs to be
// propagated to the rest of the system
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
- assertEquals(Set.of(new PackageAndUser(TARGET, USER)),
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
+ assertEquals(Set.of(UserPackage.of(USER, TARGET)),
impl.setEnabled(IDENTIFIER, true, USER));
// request succeeded, but nothing changed
@@ -239,9 +238,9 @@
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -259,9 +258,9 @@
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -276,9 +275,9 @@
public void testConfigSignaturePolicyNoConfig() throws Exception {
addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -292,9 +291,9 @@
@Test
public void testConfigSignaturePolicyNoRefPkg() throws Exception {
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
@@ -312,9 +311,9 @@
addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
installAndAssert(target(TARGET), USER,
- Set.of(new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, TARGET)));
installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
- Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
final FakeIdmapDaemon idmapd = getIdmapd();
final FakeDeviceState state = getState();
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 301697d..bba7669 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -28,6 +28,7 @@
import android.content.om.OverlayInfo;
import android.content.om.OverlayInfo.State;
import android.content.om.OverlayableInfo;
+import android.content.pm.UserPackage;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.text.TextUtils;
@@ -164,7 +165,7 @@
* @throws IllegalStateException if the package is currently installed
*/
void installAndAssert(@NonNull FakeDeviceState.PackageBuilder pkg, int userId,
- @NonNull Set<PackageAndUser> onAddedUpdatedPackages)
+ @NonNull Set<UserPackage> onAddedUpdatedPackages)
throws OperationFailedException {
if (mState.select(pkg.packageName, userId) != null) {
throw new IllegalStateException("package " + pkg.packageName + " already installed");
@@ -185,8 +186,8 @@
* @throws IllegalStateException if the package is not currently installed
*/
void upgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
- @NonNull Set<PackageAndUser> onReplacingUpdatedPackages,
- @NonNull Set<PackageAndUser> onReplacedUpdatedPackages)
+ @NonNull Set<UserPackage> onReplacingUpdatedPackages,
+ @NonNull Set<UserPackage> onReplacedUpdatedPackages)
throws OperationFailedException {
final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
if (replacedPackage == null) {
@@ -207,7 +208,7 @@
* @throws IllegalStateException if the package is not currently installed
*/
void uninstallAndAssert(@NonNull String packageName, int userId,
- @NonNull Set<PackageAndUser> onRemovedUpdatedPackages) {
+ @NonNull Set<UserPackage> onRemovedUpdatedPackages) {
final FakeDeviceState.Package pkg = mState.select(packageName, userId);
if (pkg == null) {
throw new IllegalStateException("package " + packageName + " not installed");
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index fdf9354..0805485 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -74,6 +74,7 @@
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
@@ -97,7 +98,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.uri.UriPermissionOwner;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -692,9 +692,9 @@
protected Map<String, PackageInfo> mInjectedPackages;
- protected Set<PackageWithUser> mUninstalledPackages;
- protected Set<PackageWithUser> mDisabledPackages;
- protected Set<PackageWithUser> mEphemeralPackages;
+ protected Set<UserPackage> mUninstalledPackages;
+ protected Set<UserPackage> mDisabledPackages;
+ protected Set<UserPackage> mEphemeralPackages;
protected Set<String> mSystemPackages;
protected PackageManager mMockPackageManager;
@@ -1200,28 +1200,28 @@
if (ENABLE_DUMP) {
Log.v(TAG, "Uninstall package " + packageName + " / " + userId);
}
- mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
+ mUninstalledPackages.add(UserPackage.of(userId, packageName));
}
protected void installPackage(int userId, String packageName) {
if (ENABLE_DUMP) {
Log.v(TAG, "Install package " + packageName + " / " + userId);
}
- mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
+ mUninstalledPackages.remove(UserPackage.of(userId, packageName));
}
protected void disablePackage(int userId, String packageName) {
if (ENABLE_DUMP) {
Log.v(TAG, "Disable package " + packageName + " / " + userId);
}
- mDisabledPackages.add(PackageWithUser.of(userId, packageName));
+ mDisabledPackages.add(UserPackage.of(userId, packageName));
}
protected void enablePackage(int userId, String packageName) {
if (ENABLE_DUMP) {
Log.v(TAG, "Enable package " + packageName + " / " + userId);
}
- mDisabledPackages.remove(PackageWithUser.of(userId, packageName));
+ mDisabledPackages.remove(UserPackage.of(userId, packageName));
}
PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
@@ -1239,17 +1239,17 @@
ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid);
ret.applicationInfo.packageName = pi.packageName;
- if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
+ if (mUninstalledPackages.contains(UserPackage.of(userId, packageName))) {
ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
}
- if (mEphemeralPackages.contains(PackageWithUser.of(userId, packageName))) {
+ if (mEphemeralPackages.contains(UserPackage.of(userId, packageName))) {
ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT;
}
if (mSystemPackages.contains(packageName)) {
ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
}
ret.applicationInfo.enabled =
- !mDisabledPackages.contains(PackageWithUser.of(userId, packageName));
+ !mDisabledPackages.contains(UserPackage.of(userId, packageName));
if (getSignatures) {
ret.signatures = null;
diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
index eaa0e9b..f0d389b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
@@ -34,6 +34,7 @@
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.pkg.PackageStateUnserialized;
import com.android.server.pm.pkg.PackageUserStateImpl;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -46,12 +47,16 @@
private boolean mCompatibilityModeEnabled;;
private PackageImpl mMockAndroidPackage;
+ private PackageSetting mMockPackageState;
private PackageUserStateImpl mMockUserState;
@Before
public void setUp() {
mCompatibilityModeEnabled = ParsingPackageUtils.sCompatibilityModeEnabled;
mMockAndroidPackage = mock(PackageImpl.class);
+ mMockPackageState = mock(PackageSetting.class);
+ when(mMockPackageState.getTransientState())
+ .thenReturn(new PackageStateUnserialized(mMockPackageState));
mMockUserState = new PackageUserStateImpl();
mMockUserState.setInstalled(true);
}
@@ -221,7 +226,7 @@
info.flags |= flags;
when(mMockAndroidPackage.toAppInfoWithoutState()).thenReturn(info);
return PackageInfoUtils.generateApplicationInfo(mMockAndroidPackage,
- 0 /*flags*/, mMockUserState, 0 /*userId*/, null);
+ 0 /*flags*/, mMockUserState, 0 /*userId*/, mMockPackageState);
}
private void setGlobalCompatibilityMode(boolean enabled) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 9ce99d6..59f27ec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -676,27 +676,32 @@
final File testFile = extractFile(TEST_APP4_APK);
try {
final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+ var pkgSetting = mockPkgSetting(pkg);
ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
- PackageUserStateInternal.DEFAULT, 0, null);
+ PackageUserStateInternal.DEFAULT, 0, pkgSetting);
for (ParsedActivity activity : pkg.getActivities()) {
assertNotNull(activity.getMetaData());
assertNull(PackageInfoUtils.generateActivityInfo(pkg, activity, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
for (ParsedProvider provider : pkg.getProviders()) {
assertNotNull(provider.getMetaData());
assertNull(PackageInfoUtils.generateProviderInfo(pkg, provider, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
for (ParsedActivity receiver : pkg.getReceivers()) {
assertNotNull(receiver.getMetaData());
assertNull(PackageInfoUtils.generateActivityInfo(pkg, receiver, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
for (ParsedService service : pkg.getServices()) {
assertNotNull(service.getMetaData());
assertNull(PackageInfoUtils.generateServiceInfo(pkg, service, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
} finally {
testFile.delete();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 96c0d0a..b20c63c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -83,6 +83,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
+import android.content.pm.UserPackage;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
@@ -107,7 +108,6 @@
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -4081,12 +4081,12 @@
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
hashSet(user10.getAllPackagesForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
+ set(UserPackage.of(USER_0, LAUNCHER_1),
+ UserPackage.of(USER_0, LAUNCHER_2)),
hashSet(user0.getAllLaunchersForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_1),
- PackageWithUser.of(USER_10, LAUNCHER_2)),
+ set(UserPackage.of(USER_10, LAUNCHER_1),
+ UserPackage.of(USER_10, LAUNCHER_2)),
hashSet(user10.getAllLaunchersForTest().keySet()));
assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
"s0_1", "s0_2");
@@ -4113,12 +4113,12 @@
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
hashSet(user10.getAllPackagesForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
+ set(UserPackage.of(USER_0, LAUNCHER_1),
+ UserPackage.of(USER_0, LAUNCHER_2)),
hashSet(user0.getAllLaunchersForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_1),
- PackageWithUser.of(USER_10, LAUNCHER_2)),
+ set(UserPackage.of(USER_10, LAUNCHER_1),
+ UserPackage.of(USER_10, LAUNCHER_2)),
hashSet(user10.getAllLaunchersForTest().keySet()));
assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
"s0_1", "s0_2");
@@ -4145,12 +4145,12 @@
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
hashSet(user10.getAllPackagesForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
+ set(UserPackage.of(USER_0, LAUNCHER_1),
+ UserPackage.of(USER_0, LAUNCHER_2)),
hashSet(user0.getAllLaunchersForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_1),
- PackageWithUser.of(USER_10, LAUNCHER_2)),
+ set(UserPackage.of(USER_10, LAUNCHER_1),
+ UserPackage.of(USER_10, LAUNCHER_2)),
hashSet(user10.getAllLaunchersForTest().keySet()));
assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
"s0_2");
@@ -4176,11 +4176,11 @@
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
hashSet(user10.getAllPackagesForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
+ set(UserPackage.of(USER_0, LAUNCHER_1),
+ UserPackage.of(USER_0, LAUNCHER_2)),
hashSet(user0.getAllLaunchersForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+ set(UserPackage.of(USER_10, LAUNCHER_2)),
hashSet(user10.getAllLaunchersForTest().keySet()));
assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
"s0_2");
@@ -4205,11 +4205,11 @@
assertEquals(set(CALLING_PACKAGE_1),
hashSet(user10.getAllPackagesForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
+ set(UserPackage.of(USER_0, LAUNCHER_1),
+ UserPackage.of(USER_0, LAUNCHER_2)),
hashSet(user0.getAllLaunchersForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+ set(UserPackage.of(USER_10, LAUNCHER_2)),
hashSet(user10.getAllLaunchersForTest().keySet()));
assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
"s0_2");
@@ -4234,8 +4234,8 @@
assertEquals(set(CALLING_PACKAGE_1),
hashSet(user10.getAllPackagesForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
+ set(UserPackage.of(USER_0, LAUNCHER_1),
+ UserPackage.of(USER_0, LAUNCHER_2)),
hashSet(user0.getAllLaunchersForTest().keySet()));
assertEquals(
set(),
@@ -4263,8 +4263,8 @@
assertEquals(set(),
hashSet(user10.getAllPackagesForTest().keySet()));
assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
+ set(UserPackage.of(USER_0, LAUNCHER_1),
+ UserPackage.of(USER_0, LAUNCHER_2)),
hashSet(user0.getAllLaunchersForTest().keySet()));
assertEquals(set(),
hashSet(user10.getAllLaunchersForTest().keySet()));
@@ -5584,12 +5584,12 @@
assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
- PackageWithUser.of(USER_0, LAUNCHER_1)));
+ UserPackage.of(USER_0, LAUNCHER_1)));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
- PackageWithUser.of(USER_0, LAUNCHER_2)));
+ UserPackage.of(USER_0, LAUNCHER_2)));
- assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
- assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+ assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3)));
+ assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1)));
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class),
anyString());
@@ -6146,12 +6146,12 @@
assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
- PackageWithUser.of(USER_0, LAUNCHER_1)));
+ UserPackage.of(USER_0, LAUNCHER_1)));
assertExistsAndShadow(user0.getAllLaunchersForTest().get(
- PackageWithUser.of(USER_0, LAUNCHER_2)));
+ UserPackage.of(USER_0, LAUNCHER_2)));
- assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
- assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+ assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3)));
+ assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1)));
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class),
anyString());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index c786784..15fd73c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -39,6 +39,7 @@
import android.content.pm.Capability;
import android.content.pm.CapabilityParams;
import android.content.pm.ShortcutInfo;
+import android.content.pm.UserPackage;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
@@ -51,7 +52,6 @@
import androidx.test.filters.SmallTest;
import com.android.frameworks.servicestests.R;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
import java.io.File;
import java.io.FileWriter;
@@ -2413,7 +2413,7 @@
assertWith(mManager.getDynamicShortcuts()).isEmpty();
});
// Make package 1 ephemeral.
- mEphemeralPackages.add(PackageWithUser.of(USER_0, CALLING_PACKAGE_1));
+ mEphemeralPackages.add(UserPackage.of(USER_0, CALLING_PACKAGE_1));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertExpectException(IllegalStateException.class, "Ephemeral apps", () -> {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8efcc41..8d2cffa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -59,9 +59,11 @@
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(21)
.setStartWithParent(false)
+ .setShowInSettings(45)
.build();
final UserProperties actualProps = new UserProperties(defaultProps);
actualProps.setShowInLauncher(14);
+ actualProps.setShowInSettings(32);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -99,10 +101,12 @@
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(2145)
.setStartWithParent(true)
+ .setShowInSettings(3452)
.build();
final UserProperties orig = new UserProperties(defaultProps);
orig.setShowInLauncher(2841);
orig.setStartWithParent(false);
+ orig.setShowInSettings(1437);
// Test every permission level. (Currently, it's linear so it's easy.)
for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -140,6 +144,9 @@
assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
// Items requiring hasManagePermission - put them here using hasManagePermission.
+ assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
+ hasManagePermission);
+
// Items requiring hasQueryPermission - put them here using hasQueryPermission.
// Items with no permission requirements.
@@ -182,5 +189,6 @@
assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+ assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c1e778d..df2c5dd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -599,6 +599,7 @@
// Check that this new user has the expected properties (relative to the defaults)
// provided that the test caller has the necessary permissions.
assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+ assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
assertThrows(SecurityException.class, userProps::getStartWithParent);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 9cd97ff3..699601b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -20,21 +20,16 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.apex.ApexInfo;
import android.content.Context;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
-import android.content.pm.SigningDetails;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
-import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.util.SparseIntArray;
@@ -53,7 +48,6 @@
import com.android.server.pm.pkg.component.ParsedIntentInfo;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.google.common.truth.Expect;
@@ -63,7 +57,6 @@
import java.io.File;
import java.io.InputStream;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -567,54 +560,6 @@
}
@Test
- public void testApexPackageInfoGeneration() throws Exception {
- String apexModuleName = "com.android.tzdata.apex";
- File apexFile = copyRawResourceToFile(apexModuleName,
- R.raw.com_android_tzdata);
- ApexInfo apexInfo = new ApexInfo();
- apexInfo.isActive = true;
- apexInfo.isFactory = false;
- apexInfo.moduleName = apexModuleName;
- apexInfo.modulePath = apexFile.getPath();
- apexInfo.versionCode = 191000070;
- int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
-
- ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
- flags, Collections.emptyList(), false /*collectCertificates*/);
- if (result.isError()) {
- throw new IllegalStateException(result.getErrorMessage(), result.getException());
- }
-
- ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- ParsedPackage pkg = result.getResult();
- ParseResult<SigningDetails> ret = ParsingPackageUtils.getSigningDetails(
- input, pkg, false /*skipVerify*/);
- if (ret.isError()) {
- throw new IllegalStateException(ret.getErrorMessage(), ret.getException());
- }
- pkg.setSigningDetails(ret.getResult());
- PackageInfo pi = PackageInfoUtils.generate(pkg.setApex(true).hideAsFinal(), apexInfo,
- flags, null, UserHandle.USER_SYSTEM);
-
- assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
- assertTrue(pi.applicationInfo.enabled);
- assertEquals(28, pi.applicationInfo.targetSdkVersion);
- assertEquals(191000070, pi.applicationInfo.longVersionCode);
- assertNotNull(pi.applicationInfo.metaData);
- assertEquals(apexFile.getPath(), pi.applicationInfo.sourceDir);
- assertEquals("Bundle[{com.android.vending.derived.apk.id=1}]",
- pi.applicationInfo.metaData.toString());
-
- assertEquals("com.google.android.tzdata", pi.packageName);
- assertEquals(191000070, pi.getLongVersionCode());
- assertNotNull(pi.signingInfo);
- assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
- assertTrue(pi.isApex);
- assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
- assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
- }
-
- @Test
public void testUsesSdk() throws Exception {
ParsedPackage pkg;
SparseIntArray minExtVers;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
index a98a43b..93464cd 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
@@ -25,7 +25,7 @@
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import java.util.ArrayList;
import java.util.List;
@@ -33,17 +33,17 @@
/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
- private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+ private final List<StateChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
private ConfigurationInternal mConfigurationInternal;
@Override
- public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void addConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.add(listener);
}
@Override
- public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.remove(listener);
}
@@ -86,7 +86,7 @@
}
void simulateConfigurationChangeForTests() {
- for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
+ for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
listener.onChange();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 62dae48..caef494 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -40,7 +40,7 @@
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import org.junit.Before;
import org.junit.Test;
@@ -1821,7 +1821,7 @@
private long mElapsedRealtimeMillis;
private long mSystemClockMillis;
private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
- private ConfigurationChangeListener mConfigurationInternalChangeListener;
+ private StateChangeListener mConfigurationInternalChangeListener;
// Tracking operations.
private boolean mSystemClockWasSet;
@@ -1837,7 +1837,7 @@
}
@Override
- public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void setConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 7140097..153d746 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -57,7 +57,8 @@
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_autoDetectionSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
@@ -82,10 +83,7 @@
assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -101,7 +99,7 @@
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -117,10 +115,8 @@
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ autoOffConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -136,7 +132,7 @@
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -150,7 +146,8 @@
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_autoDetectNotSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
@@ -175,10 +172,7 @@
assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
if (userRestrictionsExpected) {
@@ -189,7 +183,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -205,10 +199,8 @@
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ autoOffConfig.asCapabilities(bypassUserPolicyChecks);
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
if (userRestrictionsExpected) {
@@ -219,7 +211,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -233,7 +225,8 @@
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
@@ -258,10 +251,7 @@
assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -276,7 +266,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -292,10 +282,8 @@
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ autoOffConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -308,7 +296,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -316,7 +304,8 @@
@Test
public void test_telephonyFallbackSupported() {
- ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal config = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
@@ -331,7 +320,8 @@
/** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
@Test
public void test_geoDetectionRunInBackgroundEnabled() {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
index fdee86e..fc6afe4 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
@@ -16,14 +16,11 @@
package com.android.server.timezonedetector;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import java.time.Duration;
@@ -31,76 +28,104 @@
import java.util.List;
import java.util.Optional;
-/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
+/**
+ * A partially implemented, fake implementation of ServiceConfigAccessor for tests.
+ *
+ * <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
+ * simulate that some settings are global and shared between users. It also delivers config updates
+ * synchronously.
+ */
public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
- private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+ private final List<StateChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
- private ConfigurationInternal mConfigurationInternal;
+ private ConfigurationInternal mCurrentUserConfigurationInternal;
+ private ConfigurationInternal mOtherUserConfigurationInternal;
@Override
- public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void addConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.add(listener);
}
@Override
- public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.remove(listener);
}
@Override
public ConfigurationInternal getCurrentUserConfigurationInternal() {
- return mConfigurationInternal;
+ return getConfigurationInternal(mCurrentUserConfigurationInternal.getUserId());
}
@Override
public boolean updateConfiguration(
- @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges,
+ @UserIdInt int userId, @NonNull TimeZoneConfiguration requestedChanges,
boolean bypassUserPolicyChecks) {
- assertNotNull(mConfigurationInternal);
+ assertNotNull(mCurrentUserConfigurationInternal);
assertNotNull(requestedChanges);
+ ConfigurationInternal toUpdate = getConfigurationInternal(userId);
+
// Simulate the real strategy's behavior: the new configuration will be updated to be the
- // old configuration merged with the new if the user has the capability to up the settings.
- // Then, if the configuration changed, the change listener is invoked.
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ // old configuration merged with the new if the user has the capability to update the
+ // settings. Then, if the configuration changed, the change listener is invoked.
+ TimeZoneCapabilities capabilities = toUpdate.asCapabilities(bypassUserPolicyChecks);
+ TimeZoneConfiguration configuration = toUpdate.asConfiguration();
TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(configuration, requestedChanges);
if (newConfiguration == null) {
return false;
}
- if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
- mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
-
+ if (!newConfiguration.equals(configuration)) {
+ ConfigurationInternal updatedConfiguration = toUpdate.merge(newConfiguration);
+ if (updatedConfiguration.getUserId() == mCurrentUserConfigurationInternal.getUserId()) {
+ mCurrentUserConfigurationInternal = updatedConfiguration;
+ } else if (mOtherUserConfigurationInternal != null
+ && updatedConfiguration.getUserId()
+ == mOtherUserConfigurationInternal.getUserId()) {
+ mOtherUserConfigurationInternal = updatedConfiguration;
+ }
// Note: Unlike the real strategy, the listeners are invoked synchronously.
- simulateConfigurationChangeForTests();
+ notifyConfigurationChange();
}
return true;
}
- void initializeConfiguration(ConfigurationInternal configurationInternal) {
- mConfigurationInternal = configurationInternal;
+ void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
+ mCurrentUserConfigurationInternal = configurationInternal;
}
- void simulateConfigurationChangeForTests() {
- for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
- listener.onChange();
- }
+ void initializeOtherUserConfiguration(ConfigurationInternal configurationInternal) {
+ mOtherUserConfigurationInternal = configurationInternal;
+ }
+
+ void simulateCurrentUserConfigurationInternalChange(
+ ConfigurationInternal configurationInternal) {
+ mCurrentUserConfigurationInternal = configurationInternal;
+ // Note: Unlike the real strategy, the listeners are invoked synchronously.
+ notifyConfigurationChange();
+ }
+
+ void simulateOtherUserConfigurationInternalChange(ConfigurationInternal configurationInternal) {
+ mOtherUserConfigurationInternal = configurationInternal;
+ // Note: Unlike the real strategy, the listeners are invoked synchronously.
+ notifyConfigurationChange();
}
@Override
public ConfigurationInternal getConfigurationInternal(int userId) {
- assertEquals("Multi-user testing not supported currently",
- userId, mConfigurationInternal.getUserId());
- return mConfigurationInternal;
+ if (userId == mCurrentUserConfigurationInternal.getUserId()) {
+ return mCurrentUserConfigurationInternal;
+ } else if (mOtherUserConfigurationInternal != null
+ && userId == mOtherUserConfigurationInternal.getUserId()) {
+ return mOtherUserConfigurationInternal;
+ }
+ throw new AssertionError("userId not known: " + userId);
}
@Override
- public void addLocationTimeZoneManagerConfigListener(ConfigurationChangeListener listener) {
+ public void addLocationTimeZoneManagerConfigListener(StateChangeListener listener) {
failUnimplemented();
}
@@ -206,9 +231,14 @@
failUnimplemented();
}
+ private void notifyConfigurationChange() {
+ for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
+ listener.onChange();
+ }
+ }
+
@SuppressWarnings("UnusedReturnValue")
private static <T> T failUnimplemented() {
- fail("Unimplemented");
- return null;
+ throw new AssertionError("Unimplemented");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 228dc95..fed8b40 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -15,23 +15,71 @@
*/
package com.android.server.timezonedetector;
+import static org.junit.Assert.assertEquals;
+
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
+import java.util.ArrayList;
+
public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
+ private final FakeServiceConfigAccessor mFakeServiceConfigAccessor =
+ new FakeServiceConfigAccessor();
+ private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
private TimeZoneState mTimeZoneState;
+ public FakeTimeZoneDetectorStrategy() {
+ mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
+ this::notifyChangeListeners);
+ }
+
+ public void initializeConfiguration(ConfigurationInternal configuration) {
+ mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+ }
+
@Override
public boolean confirmTimeZone(String timeZoneId) {
return false;
}
@Override
+ public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+ boolean bypassUserPolicyChecks) {
+ ConfigurationInternal configurationInternal =
+ mFakeServiceConfigAccessor.getCurrentUserConfigurationInternal();
+ assertEquals("Multi-user testing not supported",
+ configurationInternal.getUserId(), userId);
+ return new TimeZoneCapabilitiesAndConfig(
+ configurationInternal.asCapabilities(bypassUserPolicyChecks),
+ configurationInternal.asConfiguration());
+ }
+
+ @Override
+ public boolean updateConfiguration(int userId, TimeZoneConfiguration requestedChanges,
+ boolean bypassUserPolicyChecks) {
+ return mFakeServiceConfigAccessor.updateConfiguration(
+ userId, requestedChanges, bypassUserPolicyChecks);
+ }
+
+ @Override
+ public void addChangeListener(StateChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ private void notifyChangeListeners() {
+ for (StateChangeListener listener : mListeners) {
+ listener.onChange();
+ }
+ }
+
+ @Override
public TimeZoneState getTimeZoneState() {
return mTimeZoneState;
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 782eebf..223c532 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -161,7 +161,8 @@
private static ConfigurationInternal createConfigurationInternal(
boolean enhancedMetricsCollectionEnabled) {
- return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ return new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
index 21c9685..eb6f00c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
@@ -66,10 +66,14 @@
/**
* Waits for all enqueued work to be completed before returning.
*/
- public void waitForMessagesToBeProcessed() throws InterruptedException {
+ public void waitForMessagesToBeProcessed() {
synchronized (mMonitor) {
if (mMessagesSent != mMessagesProcessed) {
- mMonitor.wait();
+ try {
+ mMonitor.wait();
+ } catch (InterruptedException e) {
+ throw new AssertionError("Unexpected exception", e);
+ }
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 276fdb9..8909832 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -50,7 +50,6 @@
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCurrentUserIdentityInjector mTestCurrentUserIdentityInjector;
- private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
private TimeZoneDetectorInternalImpl mTimeZoneDetectorInternal;
@@ -65,12 +64,11 @@
mTestHandler = new TestHandler(mHandlerThread.getLooper());
mTestCurrentUserIdentityInjector = new TestCurrentUserIdentityInjector();
mTestCurrentUserIdentityInjector.initializeCurrentUserId(ARBITRARY_USER_ID);
- mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
mTimeZoneDetectorInternal = new TimeZoneDetectorInternalImpl(
mMockContext, mTestHandler, mTestCurrentUserIdentityInjector,
- mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+ mFakeTimeZoneDetectorStrategySpy);
}
@After
@@ -83,17 +81,20 @@
public void testGetCapabilitiesAndConfigForDpm() throws Exception {
final boolean autoDetectionEnabled = true;
ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
int expectedUserId = mTestCurrentUserIdentityInjector.getCurrentUserId();
- verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
+ final boolean expectedBypassUserPolicyChecks = true;
+ verify(mFakeTimeZoneDetectorStrategySpy).getCapabilitiesAndConfig(
+ expectedUserId, expectedBypassUserPolicyChecks);
- final boolean bypassUserPolicyChecks = true;
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
- testConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+ new TimeZoneCapabilitiesAndConfig(
+ testConfig.asCapabilities(expectedBypassUserPolicyChecks),
+ testConfig.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
}
@@ -102,7 +103,7 @@
final boolean autoDetectionEnabled = false;
ConfigurationInternal initialConfigurationInternal =
createConfigurationInternal(autoDetectionEnabled);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
@@ -110,7 +111,7 @@
assertTrue(mTimeZoneDetectorInternal.updateConfigurationForDpm(timeConfiguration));
final boolean expectedBypassUserPolicyChecks = true;
- verify(mFakeServiceConfigAccessorSpy).updateConfiguration(
+ verify(mFakeTimeZoneDetectorStrategySpy).updateConfiguration(
mTestCurrentUserIdentityInjector.getCurrentUserId(),
timeConfiguration,
expectedBypassUserPolicyChecks);
@@ -148,7 +149,8 @@
}
private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
- return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ return new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index bb9d564..d8346ee 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -69,7 +69,6 @@
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCallerIdentityInjector mTestCallerIdentityInjector;
- private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
@@ -85,12 +84,11 @@
mTestCallerIdentityInjector = new TestCallerIdentityInjector();
mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
- mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
mTimeZoneDetectorService = new TimeZoneDetectorService(
mMockContext, mTestHandler, mTestCallerIdentityInjector,
- mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+ mFakeTimeZoneDetectorStrategySpy);
}
@After
@@ -115,7 +113,7 @@
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -124,11 +122,14 @@
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
- verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
-
boolean expectedBypassUserPolicyChecks = false;
+ verify(mFakeTimeZoneDetectorStrategySpy)
+ .getCapabilitiesAndConfig(expectedUserId, expectedBypassUserPolicyChecks);
+
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
- configuration.createCapabilitiesAndConfig(expectedBypassUserPolicyChecks);
+ new TimeZoneCapabilitiesAndConfig(
+ configuration.asCapabilities(expectedBypassUserPolicyChecks),
+ configuration.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
}
@@ -160,7 +161,7 @@
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -455,7 +456,8 @@
// Default geo detection settings from auto detection settings - they are not important to
// the tests.
final boolean geoDetectionEnabled = autoDetectionEnabled;
- return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ return new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index d0a7c92..f50e7fb 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -38,19 +38,28 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
+import android.os.HandlerThread;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -97,7 +106,8 @@
};
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -110,7 +120,8 @@
.build();
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -123,7 +134,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
.setTelephonyFallbackSupported(false)
@@ -136,7 +148,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -149,7 +162,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -162,7 +176,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -174,14 +189,223 @@
.setGeoDetectionEnabledSetting(true)
.build();
- private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
+ private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeEnvironment mFakeEnvironment;
+ private HandlerThread mHandlerThread;
+ private TestHandler mTestHandler;
+
+ private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
@Before
public void setUp() {
mFakeEnvironment = new FakeEnvironment();
- mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED_GEO_DISABLED);
- mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
+ mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
+ mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
+ CONFIG_AUTO_DISABLED_GEO_DISABLED);
+
+ // Create a thread + handler for processing the work that the strategy posts.
+ mHandlerThread = new HandlerThread("TimeZoneDetectorStrategyImplTest");
+ mHandlerThread.start();
+ mTestHandler = new TestHandler(mHandlerThread.getLooper());
+ mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(
+ mFakeServiceConfigAccessorSpy, mTestHandler, mFakeEnvironment);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ mHandlerThread.join();
+ }
+
+ @Test
+ public void testChangeListenerBehavior_currentUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+ // The strategy initializes itself with the current user's config during construction.
+ assertEquals(currentUserConfig,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+ boolean bypassUserPolicyChecks = false;
+
+ // Report a config change, but not one that actually changes anything.
+ {
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ CONFIG_AUTO_DISABLED_GEO_DISABLED);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceived(0);
+ assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
+
+ // Report a config change that actually changes something.
+ {
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ CONFIG_AUTO_ENABLED_GEO_ENABLED);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceived(1);
+ stateChangeListener.resetNotificationsReceivedCount();
+ assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
+
+ // Perform a (current user) update via the strategy.
+ {
+ TimeZoneConfiguration requestedChanges =
+ new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ USER_ID, requestedChanges, bypassUserPolicyChecks);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceived(1);
+ stateChangeListener.resetNotificationsReceivedCount();
+ }
+ }
+
+ // Perform a (not current user) update via the strategy. There's no listener behavior for
+ // updates to "other" users.
+ @Test
+ public void testChangeListenerBehavior_otherUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+ // The strategy initializes itself with the current user's config during construction.
+ assertEquals(currentUserConfig,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+ boolean bypassUserPolicyChecks = false;
+
+ int otherUserId = currentUserConfig.getUserId() + 1;
+ ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+ .setUserId(otherUserId)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ mFakeServiceConfigAccessorSpy.initializeOtherUserConfiguration(otherUserConfig);
+
+ TimeZoneConfiguration requestedChanges =
+ new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ otherUserId, requestedChanges, bypassUserPolicyChecks);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ // Only changes to the current user's config are notified.
+ stateChangeListener.assertNotificationsReceived(0);
+ stateChangeListener.resetNotificationsReceivedCount();
+ }
+
+ // Current user behavior: the strategy caches and returns the latest configuration.
+ @Test
+ public void testReadAndWriteConfiguration_currentUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ currentUserConfig);
+
+ int otherUserId = currentUserConfig.getUserId() + 1;
+ ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+ .setUserId(otherUserId)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+ reset(mFakeServiceConfigAccessorSpy);
+
+ final boolean bypassUserPolicyChecks = false;
+
+ ConfigurationInternal cachedConfigurationInternal =
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+ assertEquals(currentUserConfig, cachedConfigurationInternal);
+
+ // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+ {
+ reset(mFakeServiceConfigAccessorSpy);
+ TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+ mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ currentUserConfig.getUserId(), bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
+ currentUserConfig.getUserId());
+
+ assertEquals(currentUserConfig.asCapabilities(bypassUserPolicyChecks),
+ actualCapabilitiesAndConfig.getCapabilities());
+ assertEquals(currentUserConfig.asConfiguration(),
+ actualCapabilitiesAndConfig.getConfiguration());
+ }
+
+ // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
+ // the cached copy.
+ {
+ boolean newGeoDetectionEnabled =
+ !cachedConfigurationInternal.asConfiguration().isGeoDetectionEnabled();
+ TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(newGeoDetectionEnabled)
+ .build();
+ ConfigurationInternal expectedConfigAfterChange =
+ new ConfigurationInternal.Builder(cachedConfigurationInternal)
+ .setGeoDetectionEnabledSetting(newGeoDetectionEnabled)
+ .build();
+
+ reset(mFakeServiceConfigAccessorSpy);
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ assertEquals(expectedConfigAfterChange,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
+ }
+
+ // Not current user behavior: the strategy reads from the ServiceConfigAccessor.
+ @Test
+ public void testReadAndWriteConfiguration_otherUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ currentUserConfig);
+
+ int otherUserId = currentUserConfig.getUserId() + 1;
+ ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+ .setUserId(otherUserId)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+ reset(mFakeServiceConfigAccessorSpy);
+
+ final boolean bypassUserPolicyChecks = false;
+
+ // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+ {
+ reset(mFakeServiceConfigAccessorSpy);
+ TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+ mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ otherUserId, bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, times(1)).getConfigurationInternal(otherUserId);
+
+ assertEquals(otherUserConfig.asCapabilities(bypassUserPolicyChecks),
+ actualCapabilitiesAndConfig.getCapabilities());
+ assertEquals(otherUserConfig.asConfiguration(),
+ actualCapabilitiesAndConfig.getConfiguration());
+ }
+
+ // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and doesn't
+ // touch the cached copy.
+ {
+ ConfigurationInternal cachedConfigBeforeChange =
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+ boolean newGeoDetectionEnabled =
+ !otherUserConfig.asConfiguration().isGeoDetectionEnabled();
+ TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(newGeoDetectionEnabled)
+ .build();
+
+ reset(mFakeServiceConfigAccessorSpy);
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ assertEquals(cachedConfigBeforeChange,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
}
@Test
@@ -1201,19 +1425,13 @@
private final TestState<String> mTimeZoneId = new TestState<>();
private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
- private ConfigurationInternal mConfigurationInternal;
private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
- private ConfigurationChangeListener mConfigurationInternalChangeListener;
FakeEnvironment() {
// Ensure the fake environment starts with the defaults a fresh device would.
initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
}
- void initializeConfig(ConfigurationInternal configurationInternal) {
- mConfigurationInternal = configurationInternal;
- }
-
void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
@@ -1228,16 +1446,6 @@
}
@Override
- public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
- mConfigurationInternalChangeListener = listener;
- }
-
- @Override
- public ConfigurationInternal getCurrentUserConfigurationInternal() {
- return mConfigurationInternal;
- }
-
- @Override
public String getDeviceTimeZone() {
return mTimeZoneId.getLatest();
}
@@ -1254,11 +1462,6 @@
mTimeZoneConfidence.set(confidence);
}
- void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
- mConfigurationInternal = configurationInternal;
- mConfigurationInternalChangeListener.onChange();
- }
-
void assertTimeZoneNotChanged() {
mTimeZoneId.assertHasNotBeenSet();
mTimeZoneConfidence.assertHasNotBeenSet();
@@ -1322,7 +1525,8 @@
* Simulates the user / user's configuration changing.
*/
Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
- mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ configurationInternal);
return this;
}
@@ -1331,7 +1535,7 @@
*/
Script simulateSetAutoMode(boolean autoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
- mFakeEnvironment.getCurrentUserConfigurationInternal())
+ mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
@@ -1343,7 +1547,7 @@
*/
Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
- mFakeEnvironment.getCurrentUserConfigurationInternal())
+ mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
.setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
@@ -1457,4 +1661,22 @@
@MatchType int matchType, @Quality int quality, int expectedScore) {
return new TelephonyTestCase(matchType, quality, expectedScore);
}
+
+ private static class TestStateChangeListener implements StateChangeListener {
+
+ private int mNotificationsReceived;
+
+ @Override
+ public void onChange() {
+ mNotificationsReceived++;
+ }
+
+ public void resetNotificationsReceivedCount() {
+ mNotificationsReceived = 0;
+ }
+
+ public void assertNotificationsReceived(int expectedCount) {
+ assertEquals(expectedCount, mNotificationsReceived);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index 2c3a7c4..042e3ef8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -42,7 +42,8 @@
private static ConfigurationInternal createUserConfig(
@UserIdInt int userId, boolean geoDetectionEnabledSetting) {
- return new ConfigurationInternal.Builder(userId)
+ return new ConfigurationInternal.Builder()
+ .setUserId(userId)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
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 4ec9762..3a8e1cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2884,6 +2884,7 @@
fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
task.addChild(taskFragment1, POSITION_TOP);
assertEquals(task, activity1.mStartingData.mAssociatedTask);
+ assertEquals(activity1.mStartingData, task.mSharedStartingData);
final TaskFragment taskFragment2 = new TaskFragment(
mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2903,7 +2904,6 @@
verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
eq(task.mSurfaceControl));
- assertEquals(activity1.mStartingData, startingWindow.mStartingData);
assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
assertEquals(taskFragment1.getBounds(), activity1.getBounds());
// The activity was resized by task fragment, but starting window must still cover the task.
@@ -2914,6 +2914,7 @@
activity1.onFirstWindowDrawn(activityWindow);
activity2.onFirstWindowDrawn(activityWindow);
assertNull(activity1.mStartingWindow);
+ assertNull(task.mSharedStartingData);
}
@Test
@@ -2989,10 +2990,10 @@
final WindowManager.LayoutParams attrs =
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
final TestWindowState startingWindow = createWindowState(attrs, activity);
- activity.startingDisplayed = true;
+ activity.mStartingData = mock(StartingData.class);
activity.addWindow(startingWindow);
assertTrue("Starting window should be present", activity.hasStartingWindow());
- activity.startingDisplayed = false;
+ activity.mStartingData = null;
assertTrue("Starting window should be present", activity.hasStartingWindow());
activity.removeChild(startingWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d..0332c4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@
activity.allDrawn = true;
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
+ // Assume the activity contains a window.
+ doReturn(true).when(activity).hasChild();
// Make sure activity can create remote animation target.
doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f61effa..d55e53c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -321,7 +321,6 @@
final ActivityRecord activity2 = createActivityRecord(dc2);
activity1.allDrawn = true;
- activity1.startingDisplayed = true;
activity1.startingMoved = true;
// Simulate activity resume / finish flows to prepare app transition & set visibility,
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 bc319db..e3b389b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -98,12 +98,19 @@
@Test
public void backTypeCrossTaskWhenBackToPreviousTask() {
Task taskA = createTask(mDefaultDisplay);
- createActivityRecord(taskA);
+ ActivityRecord recordA = createActivityRecord(taskA);
+ Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+
withSystemCallback(createTopTaskWithActivity());
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
+
+ // verify if back animation would start.
+ verify(mBackNavigationController).scheduleAnimationLocked(
+ eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
+ any());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index ef84a4b..e85b574 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -24,7 +24,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -139,34 +138,12 @@
}
@Test
- public void testDimAboveNoChildCreatesSurface() {
- final float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- SurfaceControl dimLayer = getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mTransaction).setAlpha(dimLayer, alpha);
- verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
- }
-
- @Test
- public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() {
- float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
- final SurfaceControl firstSurface = getDimLayer();
-
- alpha = 0.9f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- assertEquals(firstSurface, getDimLayer());
- verify(mTransaction).setAlpha(firstSurface, 0.9f);
- }
-
- @Test
public void testUpdateDimsAppliesCrop() {
- mDimmer.dimAbove(mTransaction, 0.8f);
+ TestWindowContainer child = new TestWindowContainer(mWm);
+ mHost.addChild(child, 0);
+
+ final float alpha = 0.8f;
+ mDimmer.dimAbove(mTransaction, child, alpha);
int width = 100;
int height = 300;
@@ -178,17 +155,6 @@
}
@Test
- public void testDimAboveNoChildNotReset() {
- mDimmer.dimAbove(mTransaction, 0.8f);
- SurfaceControl dimLayer = getDimLayer();
- mDimmer.resetDimStates();
-
- mDimmer.updateDims(mTransaction, new Rect());
- verify(mTransaction).show(getDimLayer());
- verify(mTransaction, never()).remove(dimLayer);
- }
-
- @Test
public void testDimAboveWithChildCreatesSurfaceAboveChild() {
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index b1acae2..eab2e15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -42,7 +42,6 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.List;
/**
* Build/Install/Run:
@@ -66,55 +65,6 @@
}
@Test
- public void testCollectTasksByLastActiveTime() {
- // Create a number of stacks with tasks (of incrementing active time)
- final ArrayList<DisplayContent> displays = new ArrayList<>();
- final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
- displays.add(display);
-
- final int numStacks = 2;
- for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
- final Task stack = new TaskBuilder(mSupervisor)
- .setDisplay(display)
- .setOnTop(false)
- .build();
- }
-
- final int numTasks = 10;
- int activeTime = 0;
- final List<Task> rootTasks = new ArrayList<>();
- display.getDefaultTaskDisplayArea().forAllRootTasks(task -> {
- rootTasks.add(task);
- }, false /* traverseTopToBottom */);
- for (int i = 0; i < numTasks; i++) {
- final Task task =
- createTask(rootTasks.get(i % numStacks), ".Task" + i, i, activeTime++, null);
- doReturn(false).when(task).isVisible();
- }
-
- // Ensure that the latest tasks were returned in order of decreasing last active time,
- // collected from all tasks across all the stacks
- final int numFetchTasks = 5;
- ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
- mRunningTasks.getTasks(5, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
- mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
- assertThat(tasks).hasSize(numFetchTasks);
- for (int i = 0; i < numFetchTasks; i++) {
- assertEquals(numTasks - i - 1, tasks.get(i).id);
- }
-
- // Ensure that requesting more than the total number of tasks only returns the subset
- // and does not crash
- tasks.clear();
- mRunningTasks.getTasks(100, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
- mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
- assertThat(tasks).hasSize(numTasks);
- for (int i = 0; i < numTasks; i++) {
- assertEquals(numTasks - i - 1, tasks.get(i).id);
- }
- }
-
- @Test
public void testTaskInfo_expectNoExtrasByDefault() {
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
final int numTasks = 10;
@@ -125,7 +75,7 @@
.build();
final Bundle data = new Bundle();
data.putInt("key", 100);
- createTask(stack, ".Task" + i, i, i, data);
+ createTask(stack, ".Task" + i, i, data);
}
final int numFetchTasks = 5;
@@ -150,7 +100,7 @@
.build();
final Bundle data = new Bundle();
data.putInt("key", 100);
- createTask(stack, ".Task" + i, i, i, data);
+ createTask(stack, ".Task" + i, i, data);
}
final int numFetchTasks = 5;
@@ -167,46 +117,63 @@
}
@Test
- public void testUpdateLastActiveTimeOfVisibleTasks() {
+ public void testGetTasksSortByFocusAndVisibility() {
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
+ final Task stack = new TaskBuilder(mSupervisor)
+ .setDisplay(display)
+ .setOnTop(true)
+ .build();
+
final int numTasks = 10;
final ArrayList<Task> tasks = new ArrayList<>();
for (int i = 0; i < numTasks; i++) {
- final Task task = createTask(null, ".Task" + i, i, i, null);
+ final Task task = createTask(stack, ".Task" + i, i, null);
doReturn(false).when(task).isVisible();
tasks.add(task);
}
- final Task visibleTask = tasks.get(0);
- doReturn(true).when(visibleTask).isVisible();
-
- final Task focusedTask = tasks.get(1);
+ final Task focusedTask = tasks.get(numTasks - 1);
doReturn(true).when(focusedTask).isVisible();
- doReturn(true).when(focusedTask).isFocused();
+ display.mFocusedApp = focusedTask.getTopNonFinishingActivity();
- // Ensure that the last active time of visible tasks were updated while the focused one had
- // the largest last active time.
+ final Task visibleTaskTop = tasks.get(numTasks - 2);
+ doReturn(true).when(visibleTaskTop).isVisible();
+
+ final Task visibleTaskBottom = tasks.get(numTasks - 3);
+ doReturn(true).when(visibleTaskBottom).isVisible();
+
+ // Ensure that the focused Task is on top, visible tasks below, then invisible tasks.
final int numFetchTasks = 5;
final ArrayList<RunningTaskInfo> fetchTasks = new ArrayList<>();
mRunningTasks.getTasks(numFetchTasks, fetchTasks,
FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
assertThat(fetchTasks).hasSize(numFetchTasks);
- assertEquals(fetchTasks.get(0).id, focusedTask.mTaskId);
- assertEquals(fetchTasks.get(1).id, visibleTask.mTaskId);
+ for (int i = 0; i < numFetchTasks; i++) {
+ assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+ }
+
+ // Ensure that requesting more than the total number of tasks only returns the subset
+ // and does not crash
+ fetchTasks.clear();
+ mRunningTasks.getTasks(100, fetchTasks,
+ FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
+ mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
+ assertThat(fetchTasks).hasSize(numTasks);
+ for (int i = 0; i < numTasks; i++) {
+ assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+ }
}
/**
- * Create a task with a single activity in it, with the given last active time.
+ * Create a task with a single activity in it.
*/
- private Task createTask(Task stack, String className, int taskId,
- int lastActiveTime, Bundle extras) {
+ private Task createTask(Task stack, String className, int taskId, Bundle extras) {
final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
.setComponent(new ComponentName(mContext.getPackageName(), className))
.setTaskId(taskId)
.setParentTaskFragment(stack)
.build();
- task.lastActiveTime = lastActiveTime;
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setTask(task)
.setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
@@ -227,7 +194,7 @@
.setDisplay(i % 2 == 0 ? display0 : display1)
.setOnTop(true)
.build();
- final Task task = createTask(stack, ".Task" + i, i, i, null);
+ final Task task = createTask(stack, ".Task" + i, i, null);
tasks.add(task);
}
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 d59fce0..c906abc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -87,7 +87,6 @@
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
-import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -2302,8 +2301,7 @@
// 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());
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), isNull());
}
@@ -2331,8 +2329,7 @@
// 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));
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
}
@Test
@@ -2367,8 +2364,7 @@
// Check that letterboxDetails actually gets passed to SysUI
StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
- any(), anyBoolean(), anyInt(),
- any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
}
@Test
@@ -2420,8 +2416,7 @@
// 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));
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
}
private void recomputeNaturalConfigurationOfUnresizableActivity() {
diff --git a/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java
new file mode 100644
index 0000000..cea3d87
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.server.utils.EventLogger;
+
+import java.util.List;
+
+/**
+ * Writes logs to {@link DualDumpOutputStream}.
+ *
+ * @see EventLogger.DumpSink
+ * @see DualDumpOutputStream
+ */
+final class DualOutputStreamDumpSink implements EventLogger.DumpSink {
+
+ private final long mId;
+ private final DualDumpOutputStream mDumpOutputStream;
+
+ /* package */ DualOutputStreamDumpSink(DualDumpOutputStream dualDumpOutputStream, long id) {
+ mDumpOutputStream = dualDumpOutputStream;
+ mId = id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void sink(String tag, List<EventLogger.Event> events) {
+ mDumpOutputStream.write("USB Event Log", mId, tag);
+ for (EventLogger.Event evt: events) {
+ mDumpOutputStream.write("USB Event", mId, evt.toString());
+ }
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java b/services/usb/java/com/android/server/usb/UsbDeviceLogger.java
deleted file mode 100644
index fab00bc..0000000
--- a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.usb;
-
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.dump.DualDumpOutputStream;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.LinkedList;
-
-/**
-* Constructor UsbDeviceLogger class
-*/
-public class UsbDeviceLogger {
- private final Object mLock = new Object();
-
- // ring buffer of events to log.
- @GuardedBy("mLock")
- private final LinkedList<Event> mEvents;
-
- private final String mTitle;
-
- // the maximum number of events to keep in log
- private final int mMemSize;
-
- /**
- * Constructor for Event class.
- */
- public abstract static class Event {
- // formatter for timestamps
- private static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
-
- private final Calendar mCalendar;
-
- Event() {
- mCalendar = Calendar.getInstance();
- }
-
- /**
- * Convert event to String
- * @return StringBuilder
- */
- public String toString() {
- return (new StringBuilder(String.format("%tm-%td %tH:%tM:%tS.%tL",
- mCalendar, mCalendar, mCalendar, mCalendar, mCalendar, mCalendar)))
- .append(" ").append(eventToString()).toString();
- }
-
- /**
- * Causes the string message for the event to appear in the logcat.
- * Here is an example of how to create a new event (a StringEvent), adding it to the logger
- * (an instance of UsbDeviceLoggerr) while also making it show in the logcat:
- * <pre>
- * myLogger.log(
- * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
- * </pre>
- * @param tag the tag for the android.util.Log.v
- * @return the same instance of the event
- */
- public Event printLog(String tag) {
- Log.i(tag, eventToString());
- return this;
- }
-
- /**
- * Convert event to String.
- * This method is only called when the logger history is about to the dumped,
- * so this method is where expensive String conversions should be made, not when the Event
- * subclass is created.
- * Timestamp information will be automatically added, do not include it.
- * @return a string representation of the event that occurred.
- */
- public abstract String eventToString();
- }
-
- /**
- * Constructor StringEvent class
- */
- public static class StringEvent extends Event {
- private final String mMsg;
-
- public StringEvent(String msg) {
- mMsg = msg;
- }
-
- @Override
- public String eventToString() {
- return mMsg;
- }
- }
-
- /**
- * Constructor for logger.
- * @param size the maximum number of events to keep in log
- * @param title the string displayed before the recorded log
- */
- public UsbDeviceLogger(int size, String title) {
- mEvents = new LinkedList<Event>();
- mMemSize = size;
- mTitle = title;
- }
-
- /**
- * Constructor for logger.
- * @param evt the maximum number of events to keep in log
- */
- public synchronized void log(Event evt) {
- synchronized (mLock) {
- if (mEvents.size() >= mMemSize) {
- mEvents.removeFirst();
- }
- mEvents.add(evt);
- }
- }
-
- /**
- * Constructor for logger.
- * @param dump the maximum number of events to keep in log
- * @param id the category of events
- */
- public synchronized void dump(DualDumpOutputStream dump, long id) {
- dump.write("USB Event Log", id, mTitle);
- for (Event evt : mEvents) {
- dump.write("USB Event", id, evt.toString());
- }
- }
-}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e90a376..1c081c1 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -91,6 +91,7 @@
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.utils.EventLogger;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.File;
@@ -213,7 +214,7 @@
private static Set<Integer> sDenyInterfaces;
private HashMap<Long, FileDescriptor> mControlFds;
- private static UsbDeviceLogger sEventLogger;
+ private static EventLogger sEventLogger;
static {
sDenyInterfaces = new HashSet<>();
@@ -238,7 +239,7 @@
public void onUEvent(UEventObserver.UEvent event) {
if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
if (sEventLogger != null) {
- sEventLogger.log(new UsbDeviceLogger.StringEvent("USB UEVENT: "
+ sEventLogger.enqueue(new EventLogger.StringEvent("USB UEVENT: "
+ event.toString()));
} else {
if (DEBUG) Slog.d(TAG, "sEventLogger == null");
@@ -395,7 +396,7 @@
mUEventObserver.startObserving(USB_STATE_MATCH);
mUEventObserver.startObserving(ACCESSORY_START_MATCH);
- sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
+ sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
}
UsbProfileGroupSettingsManager getCurrentSettings() {
@@ -837,7 +838,7 @@
protected void sendStickyBroadcast(Intent intent) {
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- sEventLogger.log(new UsbDeviceLogger.StringEvent("USB intent: " + intent));
+ sEventLogger.enqueue(new EventLogger.StringEvent("USB intent: " + intent));
}
private void updateUsbFunctions() {
@@ -2350,7 +2351,7 @@
if (mHandler != null) {
mHandler.dump(dump, "handler", UsbDeviceManagerProto.HANDLER);
- sEventLogger.dump(dump, UsbHandlerProto.UEVENT);
+ sEventLogger.dump(new DualOutputStreamDumpSink(dump, UsbHandlerProto.UEVENT));
}
dump.end(token);
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 47b09fe..f916660 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -62,6 +62,7 @@
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.utils.EventLogger;
import libcore.io.IoUtils;
@@ -130,7 +131,7 @@
@GuardedBy("mLock")
private boolean mIsWriteSettingsScheduled;
- private static UsbDeviceLogger sEventLogger;
+ private static EventLogger sEventLogger;
/**
* A package of a user.
@@ -263,7 +264,7 @@
mUsbHandlerManager = usbResolveActivityManager;
- sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER,
+ sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER,
"UsbProfileGroupSettingsManager activity");
}
@@ -970,7 +971,7 @@
matches, mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)));
}
- sEventLogger.log(new UsbDeviceLogger.StringEvent("accessoryAttached: " + intent));
+ sEventLogger.enqueue(new EventLogger.StringEvent("accessoryAttached: " + intent));
resolveActivity(intent, matches, defaultActivity, null, accessory);
}
@@ -1524,7 +1525,8 @@
}
}
- sEventLogger.dump(dump, UsbProfileGroupSettingsManagerProto.INTENT);
+ sEventLogger.dump(new DualOutputStreamDumpSink(dump,
+ UsbProfileGroupSettingsManagerProto.INTENT));
dump.end(token);
}
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index c083e90..af67637 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -23,6 +23,14 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsLocalVoiceInteraction",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
}
]
}
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index dc695d6..e19117b 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -730,6 +730,25 @@
}
/**
+ * Result code to string
+ *
+ * @param result The result code.
+ * @return The result code in string format.
+ *
+ * @hide
+ */
+ public static String resultToString(@Result int result) {
+ switch (result) {
+ case RESULT_OK: return "OK";
+ case RESULT_MUST_DEACTIVATE_SIM : return "MUST_DEACTIVATE_SIM";
+ case RESULT_RESOLVABLE_ERRORS: return "RESOLVABLE_ERRORS";
+ case RESULT_FIRST_USER: return "FIRST_USER";
+ default:
+ return "UNKNOWN(" + result + ")";
+ }
+ }
+
+ /**
* Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
*/
private class IEuiccServiceWrapper extends IEuiccService.Stub {
diff --git a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
index 9add38e..46a049c 100644
--- a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
+++ b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
@@ -123,4 +123,16 @@
public int describeContents() {
return 0;
}
+
+ /**
+ * @hide
+ *
+ * @return String representation of {@link GetEuiccProfileInfoListResult}
+ */
+ @Override
+ public String toString() {
+ return "[GetEuiccProfileInfoListResult: result=" + EuiccService.resultToString(result)
+ + ", isRemovable=" + mIsRemovable + ", mProfiles=" + Arrays.toString(mProfiles)
+ + "]";
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/DomainSelectionService.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/DomainSelectionService.aidl
index 1550ab3..b9d2ba8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/DomainSelectionService.aidl
@@ -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.
@@ -11,9 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.systemui.shared.system;
+package android.telephony;
-parcelable RemoteTransitionCompat;
+parcelable DomainSelectionService.SelectionAttributes;
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
new file mode 100644
index 0000000..383561a
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -0,0 +1,857 @@
+/*
+ * 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.Annotation.PreciseDisconnectCauses;
+import android.telephony.ims.ImsReasonInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IDomainSelectionServiceController;
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Main domain selection implementation for various telephony features.
+ *
+ * The telephony framework will bind to the {@link DomainSelectionService}.
+ *
+ * @hide
+ */
+public class DomainSelectionService extends Service {
+
+ private static final String LOG_TAG = "DomainSelectionService";
+
+ /**
+ * The intent that must be defined as an intent-filter in the AndroidManifest of the
+ * {@link DomainSelectionService}.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE = "android.telephony.DomainSelectionService";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SELECTOR_TYPE_",
+ value = {
+ SELECTOR_TYPE_CALLING,
+ SELECTOR_TYPE_SMS,
+ SELECTOR_TYPE_UT})
+ public @interface SelectorType {}
+
+ /** Indicates the domain selector type for calling. */
+ public static final int SELECTOR_TYPE_CALLING = 1;
+ /** Indicates the domain selector type for sms. */
+ public static final int SELECTOR_TYPE_SMS = 2;
+ /** Indicates the domain selector type for supplementary services. */
+ public static final int SELECTOR_TYPE_UT = 3;
+
+ /** Indicates that the modem can scan for emergency service as per modem’s implementation. */
+ public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+ /** Indicates that the modem will scan for emergency service in limited service mode. */
+ public static final int SCAN_TYPE_LIMITED_SERVICE = 1;
+
+ /** Indicates that the modem will scan for emergency service in full service mode. */
+ public static final int SCAN_TYPE_FULL_SERVICE = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SCAN_TYPE_",
+ value = {
+ SCAN_TYPE_NO_PREFERENCE,
+ SCAN_TYPE_LIMITED_SERVICE,
+ SCAN_TYPE_FULL_SERVICE})
+ public @interface EmergencyScanType {}
+
+ /**
+ * Contains attributes required to determine the domain for a telephony service.
+ */
+ public static final class SelectionAttributes implements Parcelable {
+
+ private static final String TAG = "SelectionAttributes";
+
+ private int mSlotId;
+ private int mSubId;
+ private @Nullable String mCallId;
+ private @Nullable String mNumber;
+ private @SelectorType int mSelectorType;
+ private boolean mIsVideoCall;
+ private boolean mIsEmergency;
+ private boolean mIsExitedFromAirplaneMode;
+ //private @Nullable UtAttributes mUtAttributes;
+ private @Nullable ImsReasonInfo mImsReasonInfo;
+ private @PreciseDisconnectCauses int mCause;
+ private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+ /**
+ * @param slotId The slot identifier.
+ * @param subId The subscription identifier.
+ * @param callId The call identifier.
+ * @param number The dialed number.
+ * @param selectorType Indicates the requested domain selector type.
+ * @param video Indicates it's a video call.
+ * @param emergency Indicates it's emergency service.
+ * @param exited {@code true} if the request caused the device to move out of airplane mode.
+ * @param imsReasonInfo The reason why the last PS attempt failed.
+ * @param cause The reason why the last CS attempt failed.
+ * @param regResult The current registration result for emergency services.
+ */
+ private SelectionAttributes(int slotId, int subId, @Nullable String callId,
+ @Nullable String number, @SelectorType int selectorType,
+ boolean video, boolean emergency, boolean exited,
+ /*UtAttributes attr,*/
+ @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
+ @Nullable EmergencyRegResult regResult) {
+ mSlotId = slotId;
+ mSubId = subId;
+ mCallId = callId;
+ mNumber = number;
+ mSelectorType = selectorType;
+ mIsVideoCall = video;
+ mIsEmergency = emergency;
+ mIsExitedFromAirplaneMode = exited;
+ //mUtAttributes = attr;
+ mImsReasonInfo = imsReasonInfo;
+ mCause = cause;
+ mEmergencyRegResult = regResult;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param s Source selection attributes.
+ * @hide
+ */
+ public SelectionAttributes(@NonNull SelectionAttributes s) {
+ mSlotId = s.mSlotId;
+ mSubId = s.mSubId;
+ mCallId = s.mCallId;
+ mNumber = s.mNumber;
+ mSelectorType = s.mSelectorType;
+ mIsEmergency = s.mIsEmergency;
+ mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
+ //mUtAttributes = s.mUtAttributes;
+ mImsReasonInfo = s.mImsReasonInfo;
+ mCause = s.mCause;
+ mEmergencyRegResult = s.mEmergencyRegResult;
+ }
+
+ /**
+ * Constructs a SelectionAttributes object from the given parcel.
+ */
+ private SelectionAttributes(@NonNull Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * @return The slot identifier.
+ */
+ public int getSlotId() {
+ return mSlotId;
+ }
+
+ /**
+ * @return The subscription identifier.
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * @return The call identifier.
+ */
+ public @Nullable String getCallId() {
+ return mCallId;
+ }
+
+ /**
+ * @return The dialed number.
+ */
+ public @Nullable String getNumber() {
+ return mNumber;
+ }
+
+ /**
+ * @return The domain selector type.
+ */
+ public @SelectorType int getSelectorType() {
+ return mSelectorType;
+ }
+
+ /**
+ * @return {@code true} if the request is for a video call.
+ */
+ public boolean isVideoCall() {
+ return mIsVideoCall;
+ }
+
+ /**
+ * @return {@code true} if the request is for emergency services.
+ */
+ public boolean isEmergency() {
+ return mIsEmergency;
+ }
+
+ /**
+ * @return {@code true} if the request caused the device to move out of airplane mode.
+ */
+ public boolean isExitedFromAirplaneMode() {
+ return mIsExitedFromAirplaneMode;
+ }
+
+ /*
+ public @Nullable UtAttributes getUtAttributes();
+ return mUtAttributes;
+ }
+ */
+
+ /**
+ * @return The PS disconnect cause if trying over PS resulted in a failure and
+ * reselection is required.
+ */
+ public @Nullable ImsReasonInfo getPsDisconnectCause() {
+ return mImsReasonInfo;
+ }
+
+ /**
+ * @return The CS disconnect cause if trying over CS resulted in a failure and
+ * reselection is required.
+ */
+ public @PreciseDisconnectCauses int getCsDisconnectCause() {
+ return mCause;
+ }
+
+ /**
+ * @return The current registration state of cellular network.
+ */
+ public @Nullable EmergencyRegResult getEmergencyRegResult() {
+ return mEmergencyRegResult;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "{ slotId=" + mSlotId
+ + ", subId=" + mSubId
+ + ", callId=" + mCallId
+ + ", number=" + (Build.IS_DEBUGGABLE ? mNumber : "***")
+ + ", type=" + mSelectorType
+ + ", videoCall=" + mIsVideoCall
+ + ", emergency=" + mIsEmergency
+ + ", airplaneMode=" + mIsExitedFromAirplaneMode
+ + ", reasonInfo=" + mImsReasonInfo
+ + ", cause=" + mCause
+ + ", regResult=" + mEmergencyRegResult
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SelectionAttributes that = (SelectionAttributes) o;
+ return mSlotId == that.mSlotId && mSubId == that.mSubId
+ && TextUtils.equals(mCallId, that.mCallId)
+ && TextUtils.equals(mNumber, that.mNumber)
+ && mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
+ && mIsEmergency == that.mIsEmergency
+ && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
+ //&& equalsHandlesNulls(mUtAttributes, that.mUtAttributes)
+ && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
+ && mCause == that.mCause
+ && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCallId, mNumber, mImsReasonInfo,
+ mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, mEmergencyRegResult,
+ mSlotId, mSubId, mSelectorType, mCause);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mSlotId);
+ out.writeInt(mSubId);
+ out.writeString8(mCallId);
+ out.writeString8(mNumber);
+ out.writeInt(mSelectorType);
+ out.writeBoolean(mIsVideoCall);
+ out.writeBoolean(mIsEmergency);
+ out.writeBoolean(mIsExitedFromAirplaneMode);
+ //out.writeParcelable(mUtAttributes, 0);
+ out.writeParcelable(mImsReasonInfo, 0);
+ out.writeInt(mCause);
+ out.writeParcelable(mEmergencyRegResult, 0);
+ }
+
+ private void readFromParcel(@NonNull Parcel in) {
+ mSlotId = in.readInt();
+ mSubId = in.readInt();
+ mCallId = in.readString8();
+ mNumber = in.readString8();
+ mSelectorType = in.readInt();
+ mIsVideoCall = in.readBoolean();
+ mIsEmergency = in.readBoolean();
+ mIsExitedFromAirplaneMode = in.readBoolean();
+ //mUtAttributes = s.mUtAttributes;
+ mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
+ android.telephony.ims.ImsReasonInfo.class);
+ mCause = in.readInt();
+ mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(),
+ EmergencyRegResult.class);
+ }
+
+ public static final @NonNull Creator<SelectionAttributes> CREATOR =
+ new Creator<SelectionAttributes>() {
+ @Override
+ public SelectionAttributes createFromParcel(@NonNull Parcel in) {
+ return new SelectionAttributes(in);
+ }
+
+ @Override
+ public SelectionAttributes[] newArray(int size) {
+ return new SelectionAttributes[size];
+ }
+ };
+
+ private static boolean equalsHandlesNulls(Object a, Object b) {
+ return (a == null) ? (b == null) : a.equals(b);
+ }
+
+ /**
+ * Builder class creating a new instance.
+ */
+ public static final class Builder {
+ private final int mSlotId;
+ private final int mSubId;
+ private @Nullable String mCallId;
+ private @Nullable String mNumber;
+ private final @SelectorType int mSelectorType;
+ private boolean mIsVideoCall;
+ private boolean mIsEmergency;
+ private boolean mIsExitedFromAirplaneMode;
+ //private @Nullable UtAttributes mUtAttributes;
+ private @Nullable ImsReasonInfo mImsReasonInfo;
+ private @PreciseDisconnectCauses int mCause;
+ private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+ /**
+ * Default constructor for Builder.
+ */
+ public Builder(int slotId, int subId, @SelectorType int selectorType) {
+ mSlotId = slotId;
+ mSubId = subId;
+ mSelectorType = selectorType;
+ }
+
+ /**
+ * Sets the call identifier.
+ *
+ * @param callId The call identifier.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setCallId(@NonNull String callId) {
+ mCallId = callId;
+ return this;
+ }
+
+ /**
+ * Sets the dialed number.
+ *
+ * @param number The dialed number.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setNumber(@NonNull String number) {
+ mNumber = number;
+ return this;
+ }
+
+ /**
+ * Sets whether it's a video call or not.
+ *
+ * @param video Indicates it's a video call.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setVideoCall(boolean video) {
+ mIsVideoCall = video;
+ return this;
+ }
+
+ /**
+ * Sets whether it's an emergency service or not.
+ *
+ * @param emergency Indicates it's emergency service.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setEmergency(boolean emergency) {
+ mIsEmergency = emergency;
+ return this;
+ }
+
+ /**
+ * Sets whether the request caused the device to move out of airplane mode.
+ *
+ * @param exited {@code true} if the request caused the device to move out of
+ * airplane mode.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setExitedFromAirplaneMode(boolean exited) {
+ mIsExitedFromAirplaneMode = exited;
+ return this;
+ }
+
+ /**
+ * Sets the Ut service attributes.
+ * Only applicable for SELECTOR_TYPE_UT
+ *
+ * @param attr Ut services attributes.
+ * @return The same instance of the builder.
+ */
+ /*
+ public @NonNull Builder setUtAttributes(@NonNull UtAttributes attr);
+ mUtAttributes = attr;
+ return this;
+ }
+ */
+
+ /**
+ * Sets an optional reason why the last PS attempt failed.
+ *
+ * @param info The reason why the last PS attempt failed.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) {
+ mImsReasonInfo = info;
+ return this;
+ }
+
+ /**
+ * Sets an optional reason why the last CS attempt failed.
+ *
+ * @param cause The reason why the last CS attempt failed.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setCsDisconnectCause(@PreciseDisconnectCauses int cause) {
+ mCause = cause;
+ return this;
+ }
+
+ /**
+ * Sets the current registration result for emergency services.
+ *
+ * @param regResult The current registration result for emergency services.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) {
+ mEmergencyRegResult = regResult;
+ return this;
+ }
+
+ /**
+ * Build the SelectionAttributes.
+ * @return The SelectionAttributes object.
+ */
+ public @NonNull SelectionAttributes build() {
+ return new SelectionAttributes(mSlotId, mSubId, mCallId, mNumber, mSelectorType,
+ mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, /*mUtAttributes,*/
+ mImsReasonInfo, mCause, mEmergencyRegResult);
+ }
+ }
+ }
+
+ /**
+ * A wrapper class for ITransportSelectorCallback interface.
+ */
+ private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
+ private static final String TAG = "TransportSelectorCallbackWrapper";
+
+ private final @NonNull ITransportSelectorCallback mCallback;
+ private final @NonNull Executor mExecutor;
+
+ private @Nullable ITransportSelectorResultCallbackAdapter mResultCallback;
+ private @Nullable DomainSelectorWrapper mSelectorWrapper;
+
+ TransportSelectorCallbackWrapper(@NonNull ITransportSelectorCallback cb,
+ @NonNull Executor executor) {
+ mCallback = cb;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCreated(@NonNull DomainSelector selector) {
+ try {
+ mSelectorWrapper = new DomainSelectorWrapper(selector, mExecutor);
+ mCallback.onCreated(mSelectorWrapper.getCallbackBinder());
+ } catch (Exception e) {
+ Rlog.e(TAG, "onCreated e=" + e);
+ }
+ }
+
+ @Override
+ public void onWlanSelected() {
+ try {
+ mCallback.onWlanSelected();
+ } catch (Exception e) {
+ Rlog.e(TAG, "onWlanSelected e=" + e);
+ }
+ }
+
+ @Override
+ public @NonNull WwanSelectorCallback onWwanSelected() {
+ WwanSelectorCallback callback = null;
+ try {
+ IWwanSelectorCallback cb = mCallback.onWwanSelected();
+ callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onWwanSelected e=" + e);
+ }
+
+ return callback;
+ }
+
+ @Override
+ public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
+ try {
+ mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
+ mCallback.onWwanSelectedAsync(mResultCallback);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onWwanSelected e=" + e);
+ executeMethodAsyncNoException(mExecutor,
+ () -> consumer.accept(null), TAG, "onWwanSelectedAsync-Exception");
+ }
+ }
+
+ @Override
+ public void onSelectionTerminated(@DisconnectCauses int cause) {
+ try {
+ mCallback.onSelectionTerminated(cause);
+ mSelectorWrapper = null;
+ } catch (Exception e) {
+ Rlog.e(TAG, "onSelectionTerminated e=" + e);
+ }
+ }
+
+ private class ITransportSelectorResultCallbackAdapter
+ extends ITransportSelectorResultCallback.Stub {
+ private final @NonNull Consumer<WwanSelectorCallback> mConsumer;
+ private final @NonNull Executor mExecutor;
+
+ ITransportSelectorResultCallbackAdapter(
+ @NonNull Consumer<WwanSelectorCallback> consumer,
+ @NonNull Executor executor) {
+ mConsumer = consumer;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCompleted(@NonNull IWwanSelectorCallback cb) {
+ if (mConsumer == null) return;
+
+ WwanSelectorCallback callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+ executeMethodAsyncNoException(mExecutor,
+ () -> mConsumer.accept(callback), TAG, "onWwanSelectedAsync-Completed");
+ }
+ }
+ }
+
+ /**
+ * A wrapper class for IDomainSelector interface.
+ */
+ private final class DomainSelectorWrapper {
+ private static final String TAG = "DomainSelectorWrapper";
+
+ private @NonNull IDomainSelector mCallbackBinder;
+
+ DomainSelectorWrapper(@NonNull DomainSelector cb, @NonNull Executor executor) {
+ mCallbackBinder = new IDomainSelectorAdapter(cb, executor);
+ }
+
+ private class IDomainSelectorAdapter extends IDomainSelector.Stub {
+ private final @NonNull WeakReference<DomainSelector> mDomainSelectorWeakRef;
+ private final @NonNull Executor mExecutor;
+
+ IDomainSelectorAdapter(@NonNull DomainSelector domainSelector,
+ @NonNull Executor executor) {
+ mDomainSelectorWeakRef =
+ new WeakReference<DomainSelector>(domainSelector);
+ mExecutor = executor;
+ }
+
+ @Override
+ public void cancelSelection() {
+ final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+ if (domainSelector == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> domainSelector.cancelSelection(), TAG, "cancelSelection");
+ }
+
+ @Override
+ public void reselectDomain(@NonNull SelectionAttributes attr) {
+ final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+ if (domainSelector == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> domainSelector.reselectDomain(attr), TAG, "reselectDomain");
+ }
+
+ @Override
+ public void finishSelection() {
+ final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+ if (domainSelector == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> domainSelector.finishSelection(), TAG, "finishSelection");
+ }
+ }
+
+ public @NonNull IDomainSelector getCallbackBinder() {
+ return mCallbackBinder;
+ }
+ }
+
+ /**
+ * A wrapper class for IWwanSelectorCallback and IWwanSelectorResultCallback.
+ */
+ private final class WwanSelectorCallbackWrapper
+ implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
+ private static final String TAG = "WwanSelectorCallbackWrapper";
+
+ private final @NonNull IWwanSelectorCallback mCallback;
+ private final @NonNull Executor mExecutor;
+
+ private @Nullable IWwanSelectorResultCallbackAdapter mResultCallback;
+
+ WwanSelectorCallbackWrapper(@NonNull IWwanSelectorCallback cb,
+ @NonNull Executor executor) {
+ mCallback = cb;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCancel() {
+ try {
+ mCallback.onCancel();
+ } catch (Exception e) {
+ Rlog.e(TAG, "onCancel e=" + e);
+ }
+ }
+
+ @Override
+ public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+ @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+ @NonNull Consumer<EmergencyRegResult> consumer) {
+ try {
+ if (signal != null) signal.setOnCancelListener(this);
+ mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
+ mCallback.onRequestEmergencyNetworkScan(
+ preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
+ scanType, mResultCallback);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
+ }
+ }
+
+ @Override
+ public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+ try {
+ mCallback.onDomainSelected(accessNetworkType);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onDomainSelected e=" + e);
+ }
+ }
+
+ private class IWwanSelectorResultCallbackAdapter
+ extends IWwanSelectorResultCallback.Stub {
+ private final @NonNull Consumer<EmergencyRegResult> mConsumer;
+ private final @NonNull Executor mExecutor;
+
+ IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer,
+ @NonNull Executor executor) {
+ mConsumer = consumer;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onComplete(@NonNull EmergencyRegResult result) {
+ if (mConsumer == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> mConsumer.accept(result), TAG, "onScanComplete");
+ }
+ }
+ }
+
+ private final Object mExecutorLock = new Object();
+
+ /** Executor used to execute methods called remotely by the framework. */
+ private @NonNull Executor mExecutor;
+
+ /**
+ * Selects a domain for the given operation.
+ *
+ * @param attr Required to determine the domain.
+ * @param callback The callback instance being registered.
+ */
+ public void onDomainSelection(@NonNull SelectionAttributes attr,
+ @NonNull TransportSelectorCallback callback) {
+ }
+
+ /**
+ * Notifies the change in {@link ServiceState} for a specific slot.
+ *
+ * @param slotId For which the state changed.
+ * @param subId For which the state changed.
+ * @param serviceState Updated {@link ServiceState}.
+ */
+ public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+ }
+
+ /**
+ * Notifies the change in {@link BarringInfo} for a specific slot.
+ *
+ * @param slotId For which the state changed.
+ * @param subId For which the state changed.
+ * @param info Updated {@link BarringInfo}.
+ */
+ public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo info) {
+ }
+
+ private final IBinder mDomainSelectionServiceController =
+ new IDomainSelectionServiceController.Stub() {
+ @Override
+ public void selectDomain(@NonNull SelectionAttributes attr,
+ @NonNull ITransportSelectorCallback callback) throws RemoteException {
+ executeMethodAsync(getCachedExecutor(),
+ () -> DomainSelectionService.this.onDomainSelection(attr,
+ new TransportSelectorCallbackWrapper(callback, getCachedExecutor())),
+ LOG_TAG, "onDomainSelection");
+ }
+
+ @Override
+ public void updateServiceState(int slotId, int subId, @NonNull ServiceState serviceState) {
+ executeMethodAsyncNoException(getCachedExecutor(),
+ () -> DomainSelectionService.this.onServiceStateUpdated(slotId,
+ subId, serviceState), LOG_TAG, "onServiceStateUpdated");
+ }
+
+ @Override
+ public void updateBarringInfo(int slotId, int subId, @NonNull BarringInfo info) {
+ executeMethodAsyncNoException(getCachedExecutor(),
+ () -> DomainSelectionService.this.onBarringInfoUpdated(slotId, subId, info),
+ LOG_TAG, "onBarringInfoUpdated");
+ }
+ };
+
+ private static void executeMethodAsync(@NonNull Executor executor, @NonNull Runnable r,
+ @NonNull String tag, @NonNull String errorLogName) throws RemoteException {
+ try {
+ CompletableFuture.runAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+ } catch (CancellationException | CompletionException e) {
+ Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ private void executeMethodAsyncNoException(@NonNull Executor executor, @NonNull Runnable r,
+ @NonNull String tag, @NonNull String errorLogName) {
+ try {
+ CompletableFuture.runAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+ } catch (CancellationException | CompletionException e) {
+ Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+ }
+ }
+
+ /** @hide */
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ Log.i(LOG_TAG, "DomainSelectionService Bound.");
+ return mDomainSelectionServiceController;
+ }
+ return null;
+ }
+
+ /**
+ * The DomainSelectionService will be able to define an {@link Executor} that the service
+ * can use to execute the methods. It has set the default executor as Runnable::run,
+ *
+ * @return An {@link Executor} to be used.
+ */
+ @SuppressLint("OnNameExpected")
+ public @NonNull Executor getExecutor() {
+ return Runnable::run;
+ }
+
+ private @NonNull Executor getCachedExecutor() {
+ synchronized (mExecutorLock) {
+ if (mExecutor == null) {
+ Executor e = getExecutor();
+ mExecutor = (e != null) ? e : Runnable::run;
+ }
+ return mExecutor;
+ }
+ }
+
+ /**
+ * Returns a string representation of the domain.
+ * @param domain The domain.
+ * @return The name of the domain.
+ * @hide
+ */
+ public static @NonNull String getDomainName(@NetworkRegistrationInfo.Domain int domain) {
+ return NetworkRegistrationInfo.domainToString(domain);
+ }
+}
diff --git a/telephony/java/android/telephony/DomainSelector.java b/telephony/java/android/telephony/DomainSelector.java
new file mode 100644
index 0000000..0871831
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+/**
+ * Implemented as part of the {@link DomainSelectionService} to implement domain selection
+ * for a specific use case.
+ * @hide
+ */
+public interface DomainSelector {
+ /**
+ * Cancel an ongoing selection operation. It is up to the DomainSelectionService
+ * to clean up all ongoing operations with the framework.
+ */
+ void cancelSelection();
+
+ /**
+ * Reselect a domain due to the call not setting up properly.
+ *
+ * @param attr attributes required to select the domain.
+ */
+ void reselectDomain(@NonNull SelectionAttributes attr);
+
+ /**
+ * Finish the selection procedure and clean everything up.
+ */
+ void finishSelection();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/EmergencyRegResult.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/EmergencyRegResult.aidl
index 1550ab3..f722962 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/EmergencyRegResult.aidl
@@ -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.
@@ -11,9 +11,9 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package com.android.systemui.shared.system;
+package android.telephony;
-parcelable RemoteTransitionCompat;
+parcelable EmergencyRegResult;
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegResult.java
new file mode 100644
index 0000000..5aed412
--- /dev/null
+++ b/telephony/java/android/telephony/EmergencyRegResult.java
@@ -0,0 +1,318 @@
+/*
+ * 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.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Contains attributes required to determine the domain for a telephony service
+ * @hide
+ */
+public final class EmergencyRegResult implements Parcelable {
+
+ /**
+ * Indicates the cellular network type of the acquired system.
+ */
+ private @AccessNetworkConstants.RadioAccessNetworkType int mAccessNetworkType;
+
+ /**
+ * Registration state of the acquired system.
+ */
+ private @NetworkRegistrationInfo.RegistrationState int mRegState;
+
+ /**
+ * EMC domain indicates the current domain of the acquired system.
+ */
+ private @NetworkRegistrationInfo.Domain int mDomain;
+
+ /**
+ * Indicates whether the network supports voice over PS network.
+ */
+ private boolean mIsVopsSupported;
+
+ /**
+ * This indicates if camped network support VoLTE emergency bearers.
+ * This should only be set if the UE is in LTE mode.
+ */
+ private boolean mIsEmcBearerSupported;
+
+ /**
+ * The value of the network provided EMC in 5G Registration ACCEPT.
+ * This should be set only if the UE is in 5G mode.
+ */
+ private int mNwProvidedEmc;
+
+ /**
+ * The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+ * This should be set only if the UE is in 5G mode.
+ */
+ private int mNwProvidedEmf;
+
+ /** 3-digit Mobile Country Code, 000..999, empty string if unknown. */
+ private @NonNull String mMcc;
+
+ /** 2 or 3-digit Mobile Network Code, 00..999, empty string if unknown. */
+ private @NonNull String mMnc;
+
+ /**
+ * The ISO-3166-1 alpha-2 country code equivalent for the network's country code,
+ * empty string if unknown.
+ */
+ private @NonNull String mIso;
+
+ /**
+ * Constructor
+ * @param accessNetwork Indicates the network type of the acquired system.
+ * @param regState Indicates the registration state of the acquired system.
+ * @param domain Indicates the current domain of the acquired system.
+ * @param isVopsSupported Indicates whether the network supports voice over PS network.
+ * @param isEmcBearerSupported Indicates if camped network support VoLTE emergency bearers.
+ * @param emc The value of the network provided EMC in 5G Registration ACCEPT.
+ * @param emf The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+ * @param mcc Mobile country code, empty string if unknown.
+ * @param mnc Mobile network code, empty string if unknown.
+ * @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown.
+ * @hide
+ */
+ public EmergencyRegResult(
+ @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+ @NetworkRegistrationInfo.RegistrationState int regState,
+ @NetworkRegistrationInfo.Domain int domain,
+ boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+ @NonNull String mcc, @NonNull String mnc, @NonNull String iso) {
+ mAccessNetworkType = accessNetwork;
+ mRegState = regState;
+ mDomain = domain;
+ mIsVopsSupported = isVopsSupported;
+ mIsEmcBearerSupported = isEmcBearerSupported;
+ mNwProvidedEmc = emc;
+ mNwProvidedEmf = emf;
+ mMcc = mcc;
+ mMnc = mnc;
+ mIso = iso;
+ }
+
+ /**
+ * Copy constructors
+ *
+ * @param s Source emergency scan result
+ * @hide
+ */
+ public EmergencyRegResult(@NonNull EmergencyRegResult s) {
+ mAccessNetworkType = s.mAccessNetworkType;
+ mRegState = s.mRegState;
+ mDomain = s.mDomain;
+ mIsVopsSupported = s.mIsVopsSupported;
+ mIsEmcBearerSupported = s.mIsEmcBearerSupported;
+ mNwProvidedEmc = s.mNwProvidedEmc;
+ mNwProvidedEmf = s.mNwProvidedEmf;
+ mMcc = s.mMcc;
+ mMnc = s.mMnc;
+ mIso = s.mIso;
+ }
+
+ /**
+ * Construct a EmergencyRegResult object from the given parcel.
+ */
+ private EmergencyRegResult(@NonNull Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * Returns the cellular access network type of the acquired system.
+ *
+ * @return the cellular network type.
+ */
+ public @AccessNetworkConstants.RadioAccessNetworkType int getAccessNetwork() {
+ return mAccessNetworkType;
+ }
+
+ /**
+ * Returns the registration state of the acquired system.
+ *
+ * @return the registration state.
+ */
+ public @NetworkRegistrationInfo.RegistrationState int getRegState() {
+ return mRegState;
+ }
+
+ /**
+ * Returns the current domain of the acquired system.
+ *
+ * @return the current domain.
+ */
+ public @NetworkRegistrationInfo.Domain int getDomain() {
+ return mDomain;
+ }
+
+ /**
+ * Returns whether the network supports voice over PS network.
+ *
+ * @return {@code true} if the network supports voice over PS network.
+ */
+ public boolean isVopsSupported() {
+ return mIsVopsSupported;
+ }
+
+ /**
+ * Returns whether camped network support VoLTE emergency bearers.
+ * This is not valid if the UE is not in LTE mode.
+ *
+ * @return {@code true} if the network supports VoLTE emergency bearers.
+ */
+ public boolean isEmcBearerSupported() {
+ return mIsEmcBearerSupported;
+ }
+
+ /**
+ * Returns the value of the network provided EMC in 5G Registration ACCEPT.
+ * This is not valid if UE is not in 5G mode.
+ *
+ * @return the value of the network provided EMC.
+ */
+ public int getNwProvidedEmc() {
+ return mNwProvidedEmc;
+ }
+
+ /**
+ * Returns the value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+ * This is not valid if UE is not in 5G mode.
+ *
+ * @return the value of the network provided EMF.
+ */
+ public int getNwProvidedEmf() {
+ return mNwProvidedEmf;
+ }
+
+ /**
+ * Returns 3-digit Mobile Country Code.
+ *
+ * @return Mobile Country Code.
+ */
+ public @NonNull String getMcc() {
+ return mMcc;
+ }
+
+ /**
+ * Returns 2 or 3-digit Mobile Network Code.
+ *
+ * @return Mobile Network Code.
+ */
+ public @NonNull String getMnc() {
+ return mMnc;
+ }
+
+ /**
+ * Returns the ISO-3166-1 alpha-2 country code is provided in lowercase 2 character format.
+ *
+ * @return Country code.
+ */
+ public @NonNull String getIso() {
+ return mIso;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "{ accessNetwork="
+ + AccessNetworkConstants.AccessNetworkType.toString(mAccessNetworkType)
+ + ", regState=" + NetworkRegistrationInfo.registrationStateToString(mRegState)
+ + ", domain=" + NetworkRegistrationInfo.domainToString(mDomain)
+ + ", vops=" + mIsVopsSupported
+ + ", emcBearer=" + mIsEmcBearerSupported
+ + ", emc=" + mNwProvidedEmc
+ + ", emf=" + mNwProvidedEmf
+ + ", mcc=" + mMcc
+ + ", mnc=" + mMnc
+ + ", iso=" + mIso
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EmergencyRegResult that = (EmergencyRegResult) o;
+ return mAccessNetworkType == that.mAccessNetworkType
+ && mRegState == that.mRegState
+ && mDomain == that.mDomain
+ && mIsVopsSupported == that.mIsVopsSupported
+ && mIsEmcBearerSupported == that.mIsEmcBearerSupported
+ && mNwProvidedEmc == that.mNwProvidedEmc
+ && mNwProvidedEmf == that.mNwProvidedEmf
+ && TextUtils.equals(mMcc, that.mMcc)
+ && TextUtils.equals(mMnc, that.mMnc)
+ && TextUtils.equals(mIso, that.mIso);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAccessNetworkType, mRegState, mDomain,
+ mIsVopsSupported, mIsEmcBearerSupported,
+ mNwProvidedEmc, mNwProvidedEmf,
+ mMcc, mMnc, mIso);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mAccessNetworkType);
+ out.writeInt(mRegState);
+ out.writeInt(mDomain);
+ out.writeBoolean(mIsVopsSupported);
+ out.writeBoolean(mIsEmcBearerSupported);
+ out.writeInt(mNwProvidedEmc);
+ out.writeInt(mNwProvidedEmf);
+ out.writeString8(mMcc);
+ out.writeString8(mMnc);
+ out.writeString8(mIso);
+ }
+
+ private void readFromParcel(@NonNull Parcel in) {
+ mAccessNetworkType = in.readInt();
+ mRegState = in.readInt();
+ mDomain = in.readInt();
+ mIsVopsSupported = in.readBoolean();
+ mIsEmcBearerSupported = in.readBoolean();
+ mNwProvidedEmc = in.readInt();
+ mNwProvidedEmf = in.readInt();
+ mMcc = in.readString8();
+ mMnc = in.readString8();
+ mIso = in.readString8();
+ }
+
+ public static final @NonNull Creator<EmergencyRegResult> CREATOR =
+ new Creator<EmergencyRegResult>() {
+ @Override
+ public EmergencyRegResult createFromParcel(@NonNull Parcel in) {
+ return new EmergencyRegResult(in);
+ }
+
+ @Override
+ public EmergencyRegResult[] newArray(int size) {
+ return new EmergencyRegResult[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
new file mode 100644
index 0000000..d396790
--- /dev/null
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -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 android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.Annotation.DisconnectCauses;
+
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the transport selection result.
+ * @hide
+ */
+public interface TransportSelectorCallback {
+ /**
+ * Notify that {@link DomainSelector} instance has been created for the selection request.
+ *
+ * @param selector the {@link DomainSelector} instance created.
+ */
+ void onCreated(@NonNull DomainSelector selector);
+
+ /**
+ * Notify that WLAN transport has been selected.
+ */
+ void onWlanSelected();
+
+ /**
+ * Notify that WWAN transport has been selected.
+ */
+ @NonNull WwanSelectorCallback onWwanSelected();
+
+ /**
+ * Notify that WWAN transport has been selected.
+ *
+ * @param consumer The callback to receive the result.
+ */
+ void onWwanSelected(Consumer<WwanSelectorCallback> consumer);
+
+ /**
+ * Notify that selection has terminated because there is no decision that can be made
+ * or a timeout has occurred. The call should be terminated when this method is called.
+ *
+ * @param cause indicates the reason.
+ */
+ void onSelectionTerminated(@DisconnectCauses int cause);
+}
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
new file mode 100644
index 0000000..489a589
--- /dev/null
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -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 android.telephony;
+
+import android.annotation.NonNull;
+import android.os.CancellationSignal;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.DomainSelectionService.EmergencyScanType;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the domain selection result.
+ * @hide
+ */
+public interface WwanSelectorCallback {
+ /**
+ * Notify the framework that the {@link DomainSelectionService} has requested an emergency
+ * network scan as part of selection.
+ *
+ * @param preferredNetworks the ordered list of preferred networks to scan.
+ * @param scanType indicates the scan preference, such as full service or limited service.
+ * @param signal notifies when the operation is canceled.
+ * @param consumer the handler of the response.
+ */
+ void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+ @EmergencyScanType int scanType,
+ @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
+
+ /**
+ * Notifies the FW that the domain has been selected. After this method is called,
+ * this interface can be discarded.
+ *
+ * @param accessNetworkType the selected network type.
+ */
+ void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+}
diff --git a/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
new file mode 100644
index 0000000..8ad79ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
@@ -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.internal.telephony;
+
+import android.telephony.BarringInfo;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.ServiceState;
+
+import com.android.internal.telephony.ITransportSelectorCallback;
+
+oneway interface IDomainSelectionServiceController {
+ void selectDomain(in SelectionAttributes attr, in ITransportSelectorCallback callback);
+ void updateServiceState(int slotId, int subId, in ServiceState serviceState);
+ void updateBarringInfo(int slotId, int subId, in BarringInfo info);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/IDomainSelector.aidl
index 1550ab3..d94840b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -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,6 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
-parcelable RemoteTransitionCompat;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+oneway interface IDomainSelector {
+ void cancelSelection();
+ void reselectDomain(in SelectionAttributes attr);
+ void finishSelection();
+}
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
new file mode 100644
index 0000000..aca83f4
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -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.internal.telephony;
+
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+interface ITransportSelectorCallback {
+ oneway void onCreated(in IDomainSelector selector);
+ oneway void onWlanSelected();
+ IWwanSelectorCallback onWwanSelected();
+ oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
+ oneway void onSelectionTerminated(int cause);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
index 1550ab3..1460b45 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
@@ -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,6 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
-parcelable RemoteTransitionCompat;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+oneway interface ITransportSelectorResultCallback {
+ void onCompleted(in IWwanSelectorCallback cb);
+}
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
new file mode 100644
index 0000000..65d994b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -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.internal.telephony;
+
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+
+oneway interface IWwanSelectorCallback {
+ void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
+ int scanType, in IWwanSelectorResultCallback cb);
+ void onDomainSelected(int accessNetworkType);
+ void onCancel();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
similarity index 69%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
index 1550ab3..0d61fcb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -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,6 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
-parcelable RemoteTransitionCompat;
+import android.telephony.EmergencyRegResult;
+
+oneway interface IWwanSelectorResultCallback {
+ void onComplete(in EmergencyRegResult result);
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b2f7be6..0f83a05 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -239,4 +239,10 @@
public static final int CELL_OFF_FLAG = 0;
public static final int CELL_ON_FLAG = 1;
public static final int CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG = 2;
+
+ /** The key to specify the selected domain for dialing calls. */
+ public static final String EXTRA_DIAL_DOMAIN = "dial_domain";
+
+ /** The key to specify the emergency service category */
+ public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 6e935d1..83823ea 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -254,6 +254,8 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
+ <meta-data android:name="android.app.shortcuts"
+ android:resource="@xml/shortcuts" />
</activity>
<activity android:name=".SendNotificationActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml
new file mode 100644
index 0000000..24830de
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Label for split screen shortcut-->
+ <string name="split_screen_shortcut_label">Split Screen Secondary Activity</string>
+</resources>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml
new file mode 100644
index 0000000..804ec99
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+ <shortcut
+ android:shortcutId="split_screen_shortcut"
+ android:shortcutShortLabel="@string/split_screen_shortcut_label">
+ <intent
+ android:action="android.intent.action.VIEW"
+ android:targetPackage="com.android.server.wm.flicker.testapp"
+ android:targetClass="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" />
+ </shortcut>
+</shortcuts>
\ No newline at end of file
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index 29d87a2..ec1709c 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -25,6 +25,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -98,6 +99,7 @@
}
@Test
+ @Ignore
public void testBadUpdateRollback() throws Exception {
// Need to switch user in order to send broadcasts in device tests
assertTrue(getDevice().switchUser(mSecondaryUserId));
diff --git a/tests/RollbackTest/SampleRollbackApp/Android.bp b/tests/RollbackTest/SampleRollbackApp/Android.bp
index a18488d..074c7bc 100644
--- a/tests/RollbackTest/SampleRollbackApp/Android.bp
+++ b/tests/RollbackTest/SampleRollbackApp/Android.bp
@@ -29,4 +29,5 @@
resource_dirs: ["res"],
certificate: "platform",
sdk_version: "system_current",
+ min_sdk_version: "29",
}